forked from OpenWF/SpaceNinjaServer
Compare commits
462 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
541b8d32a8 | |||
74f9d1567f | |||
02a4d2b30a | |||
a8f174bce1 | |||
db1dd21924 | |||
005350bde0 | |||
5cd18db7a7 | |||
bb315eaafe | |||
327b834b07 | |||
39be095818 | |||
ef4973e694 | |||
7f69667171 | |||
dcdeb0cd34 | |||
8ce86ad4fd | |||
a2f1469779 | |||
dd32e082f3 | |||
74c7d86090 | |||
7fd4d50e07 | |||
7f805a1dcc | |||
919f12b8f9 | |||
94993a16aa | |||
002b0cb93f | |||
4362a842ff | |||
5702ab5f3b | |||
fac52bfda1 | |||
2ff535e7ab | |||
9698baa979 | |||
ceb7deec06 | |||
fe0b745066 | |||
8f41d3c13f | |||
f906cdb5e8 | |||
ea6facf3fc | |||
b93a4a6dae | |||
64da8c2e50 | |||
b2497ded19 | |||
5f6b2330af | |||
2bdb722986 | |||
3c79f910a2 | |||
65306e0478 | |||
49edebc1eb | |||
62263efde3 | |||
f66c958a3c | |||
2ef59cd570 | |||
6bb74b026a | |||
743a905de4 | |||
5c22949c6b | |||
651640c4d7 | |||
1d1abf5550 | |||
23267aa641 | |||
d94b4fd946 | |||
d5ff349746 | |||
2746e243c9 | |||
b3374eb66e | |||
c18abab9c4 | |||
61062e433f | |||
92e8ffd709 | |||
0b18932dd8 | |||
abeb17ce44 | |||
710470ca2d | |||
5cc991baca | |||
9eadc7fa21 | |||
ed10a89c1d | |||
0c2f72f9b1 | |||
d918b0c982 | |||
05c0c9909c | |||
92cf85084f | |||
cfa9ec775e | |||
d4d887a5a4 | |||
1b7b5a28bc | |||
6dc54ed893 | |||
2173bdb8b8 | |||
74d9428a66 | |||
dd7805cfb2 | |||
c55aa8a0e1 | |||
24ed580a97 | |||
158310bda2 | |||
2b451a19e6 | |||
bf67a4391d | |||
ea9333279b | |||
9e1a5d50af | |||
2091dabfc3 | |||
3a26d788a2 | |||
367dd3f22d | |||
404c747642 | |||
1a4ad8b7a5 | |||
4a3a3de300 | |||
7d5ea680e4 | |||
e2879a7808 | |||
d033c2bc12 | |||
fb58aeb07f | |||
3d69828610 | |||
a0fa41cd58 | |||
9162522962 | |||
42e08faaaf | |||
9e0dd3e0a5 | |||
054abee62c | |||
04d39ed973 | |||
b0f0b61d49 | |||
23f8901505 | |||
d3d966a503 | |||
48598c145f | |||
01f04c287a | |||
9e99d0370c | |||
516f822e43 | |||
fccdbf4a8e | |||
cfc1524619 | |||
3beb1ecc42 | |||
779bc34082 | |||
b6167165fe | |||
b07e89ed72 | |||
f34e1615e2 | |||
c82cad7b02 | |||
725efcc72e | |||
fa34b99976 | |||
d3819c25c5 | |||
4cb883dabf | |||
c376ff25f3 | |||
f7ada5a7e5 | |||
1bdc5126b3 | |||
a7899d1c18 | |||
895b9381ca | |||
9de0aee6f0 | |||
69f544c8d1 | |||
8cdcb209ae | |||
ab0d472c75 | |||
e266f9e36c | |||
6a1e508109 | |||
a167216730 | |||
30ae95bec8 | |||
dcc2b903ac | |||
3a904753f2 | |||
212b7b1ce9 | |||
92e647a0fd | |||
aa7d5067bc | |||
ae5a540975 | |||
eb332d5e32 | |||
b14927d605 | |||
24c288fe61 | |||
aad3a7bcf7 | |||
692dfaf0a5 | |||
36d2b2dda5 | |||
a56ff89bb9 | |||
ba795150a9 | |||
2b9eb1844d | |||
2516af9acc | |||
fd93f34538 | |||
a622393933 | |||
d9b944175a | |||
36c7b6f8f8 | |||
5f9475f750 | |||
7492ddaad7 | |||
926b87dda0 | |||
83b267bcf5 | |||
401f1ed229 | |||
049f709713 | |||
0f7866a575 | |||
aea1787908 | |||
0fc1326255 | |||
8221674098 | |||
5597bfe876 | |||
06ce4ac695 | |||
bfcd928fde | |||
58508a0260 | |||
eccea4ae54 | |||
31c1fc245f | |||
3ba58114b9 | |||
a12e5968da | |||
2ec2b0278a | |||
4afc8bc8c6 | |||
e65393f433 | |||
3e2e73f6eb | |||
ac25ee5118 | |||
0085c20e11 | |||
a77c1906bf | |||
e7605a2e17 | |||
db8bff20fe | |||
19bfffaa7c | |||
d0df9e3731 | |||
8a29f06207 | |||
cf3007b744 | |||
7f5592e00c | |||
c3d7ae33c2 | |||
aa12708738 | |||
cf125b5355 | |||
5277f7cc37 | |||
b5a0a2297e | |||
e0d31b8988 | |||
bc6f03b7c9 | |||
5817b48db9 | |||
5a56c2e9d3 | |||
7414658340 | |||
4b3b551ba7 | |||
b8e3be5018 | |||
57786bfffb | |||
a0453ca61d | |||
16bfcc44d5 | |||
c6a2785175 | |||
beb02bffb0 | |||
468ede680a | |||
42aca103ed | |||
aa95074ee0 | |||
5038095c13 | |||
3b16ff9b54 | |||
6598318fc5 | |||
e83970d326 | |||
3c87dd56ca | |||
7d3f2e8796 | |||
4cd35ef4d9 | |||
9b16dc2c6a | |||
9d90a3ca26 | |||
b761ff1bff | |||
31ad97e215 | |||
9150d036d7 | |||
1b4aee0b90 | |||
88c5999d07 | |||
f0ebeab74e | |||
352c6df339 | |||
6135fdcdb9 | |||
2334e76453 | |||
3986dac8ef | |||
0e1973e246 | |||
ae05172ad8 | |||
6eebf0aa84 |
@ -15,17 +15,16 @@
|
||||
"@typescript-eslint/restrict-template-expressions": "warn",
|
||||
"@typescript-eslint/restrict-plus-operands": "warn",
|
||||
"@typescript-eslint/no-unsafe-member-access": "warn",
|
||||
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
|
||||
"@typescript-eslint/no-misused-promises": "warn",
|
||||
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "caughtErrors": "none" }],
|
||||
"@typescript-eslint/no-unsafe-argument": "error",
|
||||
"@typescript-eslint/no-unsafe-call": "warn",
|
||||
"@typescript-eslint/no-unsafe-assignment": "warn",
|
||||
"@typescript-eslint/no-explicit-any": "warn",
|
||||
"@typescript-eslint/no-loss-of-precision": "warn",
|
||||
"no-loss-of-precision": "warn",
|
||||
"@typescript-eslint/no-unnecessary-condition": "warn",
|
||||
"@typescript-eslint/no-base-to-string": "off",
|
||||
"no-case-declarations": "error",
|
||||
"prettier/prettier": "error",
|
||||
"@typescript-eslint/semi": "error",
|
||||
"no-mixed-spaces-and-tabs": "error",
|
||||
"require-await": "off",
|
||||
"@typescript-eslint/require-await": "error"
|
||||
|
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
|
||||
|
19
.github/workflows/build.yml
vendored
19
.github/workflows/build.yml
vendored
@ -5,17 +5,22 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
version: [18, 20, 22]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.1.2
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
node-version: ${{ matrix.version }}
|
||||
- run: npm ci
|
||||
- run: cp config.json.example config.json
|
||||
- run: npm run build
|
||||
- run: npm run lint
|
||||
- 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 --no-pager diff
|
||||
exit 1
|
||||
fi
|
||||
|
@ -1,3 +1,4 @@
|
||||
src/routes/api.ts
|
||||
static/webui/libs/
|
||||
*.html
|
||||
*.md
|
||||
|
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["dbaeumer.vscode-eslint"]
|
||||
}
|
@ -10,8 +10,6 @@ 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_UNLOCK_ALL_QUESTS=true
|
||||
ENV APP_COMPLETE_ALL_QUESTS=true
|
||||
ENV APP_INFINITE_RESOURCES=true
|
||||
ENV APP_UNLOCK_ALL_SHIP_FEATURES=true
|
||||
ENV APP_UNLOCK_ALL_SHIP_DECORATIONS=true
|
||||
|
@ -10,5 +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,8 +1,8 @@
|
||||
@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
|
||||
|
||||
if exist static\data\0\ (
|
||||
|
@ -19,6 +19,7 @@
|
||||
"infiniteEndo": false,
|
||||
"infiniteRegalAya": false,
|
||||
"infiniteHelminthMaterials": false,
|
||||
"dontSubtractConsumables": false,
|
||||
"unlockAllShipFeatures": false,
|
||||
"unlockAllShipDecorations": false,
|
||||
"unlockAllFlavourItems": false,
|
||||
@ -29,19 +30,27 @@
|
||||
"unlockExilusEverywhere": false,
|
||||
"unlockArcanesEverywhere": false,
|
||||
"noDailyStandingLimits": false,
|
||||
"noDailyFocusLimit": false,
|
||||
"noArgonCrystalDecay": false,
|
||||
"noMasteryRankUpCooldown": false,
|
||||
"noVendorPurchaseLimits": true,
|
||||
"noDeathMarks": false,
|
||||
"noKimCooldowns": false,
|
||||
"instantResourceExtractorDrones": false,
|
||||
"noResourceExtractorDronesDamage": false,
|
||||
"skipClanKeyCrafting": false,
|
||||
"noDojoRoomBuildStage": false,
|
||||
"noDecoBuildStage": false,
|
||||
"fastDojoRoomDestruction": false,
|
||||
"noDojoResearchCosts": false,
|
||||
"noDojoResearchTime": false,
|
||||
"fastClanAscension": false,
|
||||
"spoofMasteryRank": -1,
|
||||
"events": {
|
||||
"worldState": {
|
||||
"creditBoost": false,
|
||||
"affinityBoost": false,
|
||||
"resourceBoost": false,
|
||||
"starDays": true
|
||||
"starDays": true,
|
||||
"lockTime": 0
|
||||
}
|
||||
}
|
||||
|
645
package-lock.json
generated
645
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
24
package.json
24
package.json
@ -4,10 +4,12 @@
|
||||
"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 && copyfiles static/webui/** build",
|
||||
"build": "tsc --incremental --sourceMap && ncp static/webui build/static/webui",
|
||||
"verify": "tsgo --noEmit",
|
||||
"lint": "eslint --ext .ts .",
|
||||
"lint:ci": "eslint --ext .ts --rule \"prettier/prettier: off\" .",
|
||||
"lint:fix": "eslint --fix --ext .ts .",
|
||||
"prettier": "prettier --write .",
|
||||
"update-translations": "cd scripts && node update-translations.js"
|
||||
@ -16,23 +18,25 @@
|
||||
"dependencies": {
|
||||
"@types/express": "^5",
|
||||
"@types/morgan": "^1.9.9",
|
||||
"copyfiles": "^2.4.1",
|
||||
"crc-32": "^1.2.2",
|
||||
"express": "^5",
|
||||
"json-with-bigint": "^3.2.2",
|
||||
"mongoose": "^8.11.0",
|
||||
"morgan": "^1.10.0",
|
||||
"typescript": ">=5.5 <5.6.0",
|
||||
"warframe-public-export-plus": "^0.5.46",
|
||||
"ncp": "^2.0.0",
|
||||
"typescript": "^5.5",
|
||||
"warframe-public-export-plus": "^0.5.59",
|
||||
"warframe-riven-info": "^0.1.2",
|
||||
"winston": "^3.17.0",
|
||||
"winston-daily-rotate-file": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^7.18",
|
||||
"@typescript-eslint/parser": "^7.18",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-prettier": "^5.2.3",
|
||||
"prettier": "^3.4.2",
|
||||
"@rxliuli/tsgo": "^2025.3.31",
|
||||
"@typescript-eslint/eslint-plugin": "^8.28.0",
|
||||
"@typescript-eslint/parser": "^8.28.0",
|
||||
"eslint": "^8",
|
||||
"eslint-plugin-prettier": "^5.2.5",
|
||||
"prettier": "^3.5.3",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"tsconfig-paths": "^4.2.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");
|
||||
}
|
||||
});
|
||||
|
12
src/app.ts
12
src/app.ts
@ -15,14 +15,24 @@ import { webuiRouter } from "@/src/routes/webui";
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use((req, _res, next) => {
|
||||
// 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" || req.headers["content-encoding"] == "e") {
|
||||
req.headers["content-encoding"] = undefined;
|
||||
}
|
||||
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);
|
||||
app.use("/", cacheRouter);
|
||||
app.use("/custom", customRouter);
|
||||
app.use("/dynamic", dynamicController);
|
||||
app.use("/:id/dynamic", dynamicController);
|
||||
app.use("/pay", payRouter);
|
||||
app.use("/stats", statsRouter);
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -32,7 +32,7 @@ export const abortDojoComponentController: RequestHandler = async (req, res) =>
|
||||
if (request.DecoId) {
|
||||
removeDojoDeco(guild, request.ComponentId, request.DecoId);
|
||||
} else {
|
||||
removeDojoRoom(guild, request.ComponentId);
|
||||
await removeDojoRoom(guild, request.ComponentId);
|
||||
}
|
||||
|
||||
await guild.save();
|
||||
|
@ -17,7 +17,7 @@ export const activateRandomModController: RequestHandler = async (req, res) => {
|
||||
ItemCount: -1
|
||||
}
|
||||
]);
|
||||
const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType]);
|
||||
const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType])!;
|
||||
const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
|
||||
const upgradeIndex =
|
||||
inventory.Upgrades.push({
|
||||
|
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/guildTypes";
|
||||
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;
|
||||
}
|
117
src/controllers/api/addToAllianceController.ts
Normal file
117
src/controllers/api/addToAllianceController.ts
Normal file
@ -0,0 +1,117 @@
|
||||
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 { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||
import { GuildPermission } from "@/src/types/guildTypes";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { RequestHandler } from "express";
|
||||
import { ExportFlavour } from "warframe-public-export-plus";
|
||||
|
||||
export const addToAllianceController: RequestHandler = async (req, res) => {
|
||||
// Check requester is a warlord in their guild
|
||||
const account = await getAccountForRequest(req);
|
||||
const guildMember = (await GuildMember.findOne({ accountId: account._id, status: 0 }))!;
|
||||
if (guildMember.rank > 1) {
|
||||
res.status(400).json({ Error: 104 });
|
||||
return;
|
||||
}
|
||||
|
||||
// Check guild has invite permissions in the alliance
|
||||
const allianceMember = (await AllianceMember.findOne({
|
||||
allianceId: req.query.allianceId,
|
||||
guildId: guildMember.guildId
|
||||
}))!;
|
||||
if (!(allianceMember.Permissions & GuildPermission.Recruiter)) {
|
||||
res.status(400).json({ Error: 104 });
|
||||
return;
|
||||
}
|
||||
|
||||
// Find clan to invite
|
||||
const payload = getJSONfromString<IAddToAllianceRequest>(String(req.body));
|
||||
const guilds = await Guild.find(
|
||||
{
|
||||
Name:
|
||||
payload.clanName.indexOf("#") == -1
|
||||
? new RegExp("^" + regexEscape(payload.clanName) + "#...$")
|
||||
: payload.clanName
|
||||
},
|
||||
"Name"
|
||||
);
|
||||
if (guilds.length == 0) {
|
||||
res.status(400).json({ Error: 101 });
|
||||
return;
|
||||
}
|
||||
if (guilds.length > 1) {
|
||||
const choices: IGuildChoice[] = [];
|
||||
for (const guild of guilds) {
|
||||
choices.push({
|
||||
OriginalPlatform: 0,
|
||||
Name: guild.Name
|
||||
});
|
||||
}
|
||||
res.json(choices);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add clan as a pending alliance member
|
||||
try {
|
||||
await AllianceMember.insertOne({
|
||||
allianceId: req.query.allianceId,
|
||||
guildId: guilds[0]._id,
|
||||
Pending: true,
|
||||
Permissions: 0
|
||||
});
|
||||
} catch (e) {
|
||||
logger.debug(`alliance invite failed due to ${String(e)}`);
|
||||
res.status(400).json({ Error: 102 });
|
||||
return;
|
||||
}
|
||||
|
||||
// Send inbox message to founding warlord
|
||||
// TOVERIFY: Should other warlords get this as well?
|
||||
// TOVERIFY: Who/what should the sender be?
|
||||
// TOVERIFY: Should this message be highPriority?
|
||||
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"))!;
|
||||
await createMessage(invitedClanOwnerMember.accountId, [
|
||||
{
|
||||
sndr: getSuffixedName(account),
|
||||
msg: "/Lotus/Language/Menu/Mailbox_AllianceInvite_Body",
|
||||
arg: [
|
||||
{
|
||||
Key: "THEIR_CLAN",
|
||||
Tag: senderGuild.Name
|
||||
},
|
||||
{
|
||||
Key: "CLAN",
|
||||
Tag: guilds[0].Name
|
||||
},
|
||||
{
|
||||
Key: "ALLIANCE",
|
||||
Tag: alliance.Name
|
||||
}
|
||||
],
|
||||
sub: "/Lotus/Language/Menu/Mailbox_AllianceInvite_Title",
|
||||
icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
|
||||
contextInfo: alliance._id.toString(),
|
||||
highPriority: true,
|
||||
acceptAction: "ALLIANCE_INVITE",
|
||||
declineAction: "ALLIANCE_INVITE",
|
||||
hasAccountAction: true
|
||||
}
|
||||
]);
|
||||
|
||||
res.end();
|
||||
};
|
||||
|
||||
interface IAddToAllianceRequest {
|
||||
clanName: string;
|
||||
}
|
||||
|
||||
interface IGuildChoice {
|
||||
OriginalPlatform: number;
|
||||
Name: string;
|
||||
}
|
@ -3,45 +3,52 @@ import { Account } from "@/src/models/loginModel";
|
||||
import { fillInInventoryDataForGuildMember, hasGuildPermission } from "@/src/services/guildService";
|
||||
import { createMessage } from "@/src/services/inboxService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||
import { getAccountForRequest, getAccountIdForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||
import { IOid } from "@/src/types/commonTypes";
|
||||
import { GuildPermission, IGuildMemberClient } from "@/src/types/guildTypes";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { RequestHandler } from "express";
|
||||
import { ExportFlavour } from "warframe-public-export-plus";
|
||||
|
||||
export const addToGuildController: RequestHandler = async (req, res) => {
|
||||
const payload = JSON.parse(String(req.body)) as IAddToGuildRequest;
|
||||
|
||||
if ("UserName" in payload) {
|
||||
// Clan recruiter sending an invite
|
||||
|
||||
const account = await Account.findOne({ DisplayName: payload.UserName });
|
||||
if (!account) {
|
||||
res.status(400).json("Username does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
const guild = (await Guild.findOne({ _id: payload.GuildId.$oid }, "Name"))!;
|
||||
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") {
|
||||
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");
|
||||
}
|
||||
|
||||
if (
|
||||
await GuildMember.exists({
|
||||
accountId: account._id,
|
||||
guildId: payload.GuildId.$oid
|
||||
})
|
||||
) {
|
||||
res.status(400).json("User already invited to clan");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await GuildMember.insertOne({
|
||||
accountId: account._id,
|
||||
guildId: payload.GuildId.$oid,
|
||||
status: 2 // outgoing invite
|
||||
});
|
||||
} catch (e) {
|
||||
logger.debug(`guild invite failed due to ${String(e)}`);
|
||||
res.status(400).json("User already invited to clan");
|
||||
return;
|
||||
}
|
||||
|
||||
const senderInventory = await getInventory(senderAccount._id.toString(), "ActiveAvatarImageType");
|
||||
await createMessage(account._id.toString(), [
|
||||
await createMessage(account._id, [
|
||||
{
|
||||
sndr: getSuffixedName(senderAccount),
|
||||
msg: "/Lotus/Language/Menu/Mailbox_ClanInvite_Body",
|
||||
@ -69,9 +76,30 @@ export const addToGuildController: RequestHandler = async (req, res) => {
|
||||
};
|
||||
await fillInInventoryDataForGuildMember(member);
|
||||
res.json({ NewMember: member });
|
||||
} else if ("RequestMsg" in payload) {
|
||||
// Player applying to join a clan
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
try {
|
||||
await GuildMember.insertOne({
|
||||
accountId,
|
||||
guildId: payload.GuildId.$oid,
|
||||
status: 1, // incoming invite
|
||||
RequestMsg: payload.RequestMsg,
|
||||
RequestExpiry: new Date(Date.now() + 14 * 86400 * 1000) // TOVERIFY: I can't find any good information about this with regards to live, but 2 weeks seem reasonable.
|
||||
});
|
||||
} catch (e) {
|
||||
logger.debug(`guild invite failed due to ${String(e)}`);
|
||||
res.status(400).send("Already requested");
|
||||
}
|
||||
res.end();
|
||||
} else {
|
||||
logger.error(`data provided to ${req.path}: ${String(req.body)}`);
|
||||
res.status(400).end();
|
||||
}
|
||||
};
|
||||
|
||||
interface IAddToGuildRequest {
|
||||
UserName: string;
|
||||
UserName?: string;
|
||||
GuildId: IOid;
|
||||
RequestMsg?: string;
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
|
||||
});
|
||||
|
||||
const rawRivenType = getRandomRawRivenType();
|
||||
const rivenType = getRandomElement(rivenRawToRealWeighted[rawRivenType]);
|
||||
const rivenType = getRandomElement(rivenRawToRealWeighted[rawRivenType])!;
|
||||
const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
|
||||
|
||||
const upgradeIndex =
|
||||
@ -53,17 +53,38 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
|
||||
RARE: 0,
|
||||
LEGENDARY: 0
|
||||
};
|
||||
let forcedPolarity: string | undefined;
|
||||
payload.Consumed.forEach(upgrade => {
|
||||
const meta = ExportUpgrades[upgrade.ItemType];
|
||||
counts[meta.rarity] += upgrade.ItemCount;
|
||||
if (upgrade.ItemId.$oid != "000000000000000000000000") {
|
||||
inventory.Upgrades.pull({ _id: upgrade.ItemId.$oid });
|
||||
} 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") {
|
||||
forcedPolarity = "AP_DEFENSE";
|
||||
} else if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/TacticTransmuteCore") {
|
||||
forcedPolarity = "AP_TACTIC";
|
||||
}
|
||||
});
|
||||
|
||||
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,
|
||||
@ -74,12 +95,14 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
|
||||
|
||||
const options: { uniqueName: string; rarity: TRarity }[] = [];
|
||||
Object.entries(ExportUpgrades).forEach(([uniqueName, upgrade]) => {
|
||||
if (upgrade.canBeTransmutation) {
|
||||
if (upgrade.canBeTransmutation && (!forcedPolarity || upgrade.polarity == forcedPolarity)) {
|
||||
options.push({ uniqueName, rarity: upgrade.rarity });
|
||||
}
|
||||
});
|
||||
|
||||
const newModType = getRandomWeightedReward(options, weights)!.uniqueName;
|
||||
newModType = getRandomWeightedReward(options, weights)!.uniqueName;
|
||||
}
|
||||
|
||||
addMods(inventory, [
|
||||
{
|
||||
ItemType: newModType,
|
||||
@ -122,3 +145,34 @@ interface IAgnosticUpgradeClient {
|
||||
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"
|
||||
]
|
||||
];
|
||||
|
20
src/controllers/api/cancelGuildAdvertisementController.ts
Normal file
20
src/controllers/api/cancelGuildAdvertisementController.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { GuildAd } from "@/src/models/guildModel";
|
||||
import { getGuildForRequestEx, hasGuildPermission } from "@/src/services/guildService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { GuildPermission } from "@/src/types/guildTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const cancelGuildAdvertisementController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "GuildId");
|
||||
const guild = await getGuildForRequestEx(req, inventory);
|
||||
if (!(await hasGuildPermission(guild, accountId, GuildPermission.Advertiser))) {
|
||||
res.status(400).end();
|
||||
return;
|
||||
}
|
||||
|
||||
await GuildAd.deleteOne({ GuildId: guild._id });
|
||||
|
||||
res.end();
|
||||
};
|
@ -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));
|
||||
|
||||
|
@ -11,15 +11,16 @@ import {
|
||||
getInventory,
|
||||
updateCurrency,
|
||||
addItem,
|
||||
addMiscItems,
|
||||
addRecipes,
|
||||
occupySlot
|
||||
occupySlot,
|
||||
combineInventoryChanges
|
||||
} from "@/src/services/inventoryService";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { IMiscItem, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||
|
||||
export interface IClaimCompletedRecipeRequest {
|
||||
interface IClaimCompletedRecipeRequest {
|
||||
RecipeIds: IOid[];
|
||||
}
|
||||
|
||||
@ -51,14 +52,14 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
|
||||
...updateCurrency(inventory, recipe.buildPrice * -1, false)
|
||||
};
|
||||
|
||||
const nonMiscItemIngredients = new Set();
|
||||
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>());
|
||||
nonMiscItemIngredients.add(item.ItemType);
|
||||
equipmentIngredients.add(item.ItemType);
|
||||
|
||||
occupySlot(inventory, InventorySlot.WEAPONS, false);
|
||||
inventoryChanges.WeaponBin ??= { Slots: 0 };
|
||||
@ -66,20 +67,21 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
|
||||
});
|
||||
}
|
||||
}
|
||||
const miscItemChanges: IMiscItem[] = [];
|
||||
recipe.ingredients.forEach(ingredient => {
|
||||
if (!nonMiscItemIngredients.has(ingredient.ItemType)) {
|
||||
miscItemChanges.push(ingredient);
|
||||
for (const ingredient of recipe.ingredients) {
|
||||
if (!equipmentIngredients.has(ingredient.ItemType)) {
|
||||
combineInventoryChanges(
|
||||
inventoryChanges,
|
||||
await addItem(inventory, ingredient.ItemType, ingredient.ItemCount)
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
addMiscItems(inventory, miscItemChanges);
|
||||
inventoryChanges.MiscItems = miscItemChanges;
|
||||
|
||||
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 | IOid[];
|
||||
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
|
||||
inventory.PendingSpectreLoadouts ??= [];
|
||||
inventory.SpectreLoadouts ??= [];
|
||||
@ -99,9 +101,15 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
|
||||
inventory.SpectreLoadouts.push(inventory.PendingSpectreLoadouts[pendingLoadoutIndex]);
|
||||
inventory.PendingSpectreLoadouts.splice(pendingLoadoutIndex, 1);
|
||||
}
|
||||
} else if (recipe.secretIngredientAction == "SIA_UNBRAND") {
|
||||
inventory.BrandedSuits!.splice(
|
||||
inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)),
|
||||
1
|
||||
);
|
||||
BrandedSuits = [toOid(pendingRecipe.SuitToUnbrand!)];
|
||||
}
|
||||
|
||||
let InventoryChanges = {};
|
||||
let InventoryChanges: IInventoryChanges = {};
|
||||
if (recipe.consumeOnUse) {
|
||||
addRecipes(inventory, [
|
||||
{
|
||||
@ -111,16 +119,31 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
|
||||
]);
|
||||
}
|
||||
if (req.query.rush) {
|
||||
const end = Math.trunc(pendingRecipe.CompletionDate.getTime() / 1000);
|
||||
const start = end - recipe.buildTime;
|
||||
const secondsElapsed = Math.trunc(Date.now() / 1000) - start;
|
||||
const progress = secondsElapsed / recipe.buildTime;
|
||||
logger.debug(`rushing recipe at ${Math.trunc(progress * 100)}% completion`);
|
||||
const cost = Math.round(recipe.skipBuildTimePrice * (1 - (progress - 0.5)));
|
||||
InventoryChanges = {
|
||||
...InventoryChanges,
|
||||
...updateCurrency(inventory, recipe.skipBuildTimePrice, true)
|
||||
...updateCurrency(inventory, cost, true)
|
||||
};
|
||||
}
|
||||
if (recipe.secretIngredientAction != "SIA_UNBRAND") {
|
||||
InventoryChanges = {
|
||||
...InventoryChanges,
|
||||
...(await addItem(inventory, recipe.resultType, recipe.num, false)).InventoryChanges
|
||||
...(await addItem(
|
||||
inventory,
|
||||
recipe.resultType,
|
||||
recipe.num,
|
||||
false,
|
||||
undefined,
|
||||
pendingRecipe.TargetFingerprint
|
||||
))
|
||||
};
|
||||
}
|
||||
await inventory.save();
|
||||
res.json({ InventoryChanges });
|
||||
res.json({ InventoryChanges, BrandedSuits });
|
||||
}
|
||||
};
|
||||
|
@ -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({
|
||||
|
@ -7,6 +7,8 @@ export const clearDialogueHistoryController: RequestHandler = async (req, res) =
|
||||
const inventory = await getInventory(accountId);
|
||||
const request = JSON.parse(String(req.body)) as IClearDialogueRequest;
|
||||
if (inventory.DialogueHistory && inventory.DialogueHistory.Dialogues) {
|
||||
inventory.DialogueHistory.Resets ??= 0;
|
||||
inventory.DialogueHistory.Resets += 1;
|
||||
for (const dialogueName of request.Dialogues) {
|
||||
const index = inventory.DialogueHistory.Dialogues.findIndex(x => x.DialogueName == dialogueName);
|
||||
if (index != -1) {
|
||||
|
6
src/controllers/api/clearNewEpisodeRewardController.ts
Normal file
6
src/controllers/api/clearNewEpisodeRewardController.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
// example req.body: {"NewEpisodeReward":true,"crossPlaySetting":"ENABLED"}
|
||||
export const clearNewEpisodeRewardController: RequestHandler = (_req, res) => {
|
||||
res.status(200).end();
|
||||
};
|
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
|
||||
});
|
||||
};
|
37
src/controllers/api/confirmAllianceInvitationController.ts
Normal file
37
src/controllers/api/confirmAllianceInvitationController.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
|
||||
import { getAllianceClient } from "@/src/services/guildService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const confirmAllianceInvitationController: RequestHandler = async (req, res) => {
|
||||
// Check requester is a warlord in their guild
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const guildMember = (await GuildMember.findOne({ accountId, status: 0 }))!;
|
||||
if (guildMember.rank > 1) {
|
||||
res.status(400).json({ Error: 104 });
|
||||
return;
|
||||
}
|
||||
|
||||
const allianceMember = await AllianceMember.findOne({
|
||||
allianceId: req.query.allianceId,
|
||||
guildId: guildMember.guildId
|
||||
});
|
||||
if (!allianceMember || !allianceMember.Pending) {
|
||||
res.status(400);
|
||||
return;
|
||||
}
|
||||
allianceMember.Pending = false;
|
||||
|
||||
const guild = (await Guild.findById(guildMember.guildId))!;
|
||||
guild.AllianceId = allianceMember.allianceId;
|
||||
|
||||
await Promise.all([allianceMember.save(), guild.save()]);
|
||||
|
||||
// Give client the new alliance data which uses "AllianceId" instead of "_id" in this response
|
||||
const alliance = (await Alliance.findById(allianceMember.allianceId))!;
|
||||
const { _id, ...rest } = await getAllianceClient(alliance, guild);
|
||||
res.json({
|
||||
AllianceId: _id,
|
||||
...rest
|
||||
});
|
||||
};
|
@ -1,26 +1,58 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { Guild, GuildMember } from "@/src/models/guildModel";
|
||||
import { getGuildClient, updateInventoryForConfirmedGuildJoin } from "@/src/services/guildService";
|
||||
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||
import { Account } from "@/src/models/loginModel";
|
||||
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";
|
||||
import { RequestHandler } from "express";
|
||||
import { Types } from "mongoose";
|
||||
|
||||
export const confirmGuildInvitationController: RequestHandler = async (req, res) => {
|
||||
// GET request: A player accepting an invite they got in their inbox.
|
||||
export const confirmGuildInvitationGetController: RequestHandler = async (req, res) => {
|
||||
const account = await getAccountForRequest(req);
|
||||
const guildMember = await GuildMember.findOne({
|
||||
const invitedGuildMember = await GuildMember.findOne({
|
||||
accountId: account._id,
|
||||
guildId: req.query.clanId as string
|
||||
});
|
||||
if (invitedGuildMember && invitedGuildMember.status == 2) {
|
||||
let inventoryChanges: IInventoryChanges = {};
|
||||
|
||||
// If this account is already in a guild, we need to do cleanup first.
|
||||
const guildMember = await GuildMember.findOneAndDelete({ accountId: account._id, status: 0 });
|
||||
if (guildMember) {
|
||||
guildMember.status = 0;
|
||||
await guildMember.save();
|
||||
const inventory = await getInventory(account._id.toString(), "LevelKeys Recipes");
|
||||
inventoryChanges = removeDojoKeyItems(inventory);
|
||||
await inventory.save();
|
||||
|
||||
await updateInventoryForConfirmedGuildJoin(
|
||||
account._id.toString(),
|
||||
new Types.ObjectId(req.query.clanId as string)
|
||||
);
|
||||
if (guildMember.rank == 0) {
|
||||
await deleteGuild(guildMember.guildId);
|
||||
}
|
||||
}
|
||||
|
||||
const guild = (await Guild.findOne({ _id: req.query.clanId as string }))!;
|
||||
// Now that we're sure this account is not in a guild right now, we can just proceed with the normal updates.
|
||||
invitedGuildMember.status = 0;
|
||||
await invitedGuildMember.save();
|
||||
|
||||
// Remove pending applications for this account
|
||||
await GuildMember.deleteMany({ accountId: account._id, status: 1 });
|
||||
|
||||
// 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);
|
||||
giveClanKey(inventory, inventoryChanges);
|
||||
await inventory.save();
|
||||
|
||||
const guild = (await Guild.findById(req.query.clanId as string))!;
|
||||
|
||||
// Add join to clan log
|
||||
guild.RosterActivity ??= [];
|
||||
guild.RosterActivity.push({
|
||||
dateTime: new Date(),
|
||||
@ -31,16 +63,56 @@ export const confirmGuildInvitationController: RequestHandler = async (req, res)
|
||||
|
||||
res.json({
|
||||
...(await getGuildClient(guild, account._id.toString())),
|
||||
InventoryChanges: {
|
||||
Recipes: [
|
||||
{
|
||||
ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
|
||||
ItemCount: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
InventoryChanges: inventoryChanges
|
||||
});
|
||||
} else {
|
||||
res.end();
|
||||
}
|
||||
};
|
||||
|
||||
// POST request: Clan representative accepting invite(s).
|
||||
export const confirmGuildInvitationPostController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const guild = (await Guild.findById(req.query.clanId as string, "Ranks RosterActivity"))!;
|
||||
if (!(await hasGuildPermission(guild, accountId, GuildPermission.Recruiter))) {
|
||||
res.status(400).json("Invalid permission");
|
||||
return;
|
||||
}
|
||||
const payload = getJSONfromString<{ userId: string }>(String(req.body));
|
||||
const filter: { accountId?: string; status: number } = { status: 1 };
|
||||
if (payload.userId != "all") {
|
||||
filter.accountId = payload.userId;
|
||||
}
|
||||
const guildMembers = await GuildMember.find(filter);
|
||||
const newMembers: string[] = [];
|
||||
for (const guildMember of guildMembers) {
|
||||
guildMember.status = 0;
|
||||
guildMember.RequestMsg = undefined;
|
||||
guildMember.RequestExpiry = undefined;
|
||||
await guildMember.save();
|
||||
|
||||
// Remove other pending applications for this account
|
||||
await GuildMember.deleteMany({ accountId: guildMember.accountId, status: 1 });
|
||||
|
||||
// Update inventory of new member
|
||||
const inventory = await getInventory(guildMember.accountId.toString(), "GuildId LevelKeys Recipes");
|
||||
inventory.GuildId = new Types.ObjectId(req.query.clanId as string);
|
||||
giveClanKey(inventory);
|
||||
await inventory.save();
|
||||
|
||||
// Add join to clan log
|
||||
const account = (await Account.findOne({ _id: guildMember.accountId }))!;
|
||||
guild.RosterActivity ??= [];
|
||||
guild.RosterActivity.push({
|
||||
dateTime: new Date(),
|
||||
entryType: 6,
|
||||
details: getSuffixedName(account)
|
||||
});
|
||||
|
||||
newMembers.push(account._id.toString());
|
||||
}
|
||||
await guild.save();
|
||||
res.json({
|
||||
NewMembers: newMembers
|
||||
});
|
||||
};
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { Guild } from "@/src/models/guildModel";
|
||||
import { config } from "@/src/services/configService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
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";
|
||||
@ -10,7 +10,7 @@ import { Types } from "mongoose";
|
||||
export const contributeGuildClassController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const payload = getJSONfromString<IContributeGuildClassRequest>(String(req.body));
|
||||
const guild = (await Guild.findOne({ _id: payload.GuildId }))!;
|
||||
const guild = (await Guild.findById(payload.GuildId))!;
|
||||
|
||||
// First contributor initiates ceremony and locks the pending class.
|
||||
if (!guild.CeremonyContributors) {
|
||||
@ -30,18 +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));
|
||||
}
|
||||
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,6 +1,7 @@
|
||||
import { TGuildDatabaseDocument } from "@/src/models/guildModel";
|
||||
import { GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
|
||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||
import {
|
||||
addGuildMemberMiscItemContribution,
|
||||
getDojoClient,
|
||||
getGuildForRequestEx,
|
||||
hasAccessToDojo,
|
||||
@ -10,7 +11,7 @@ import {
|
||||
} from "@/src/services/guildService";
|
||||
import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { IDojoContributable } from "@/src/types/guildTypes";
|
||||
import { IDojoContributable, IGuildMemberDatabase } from "@/src/types/guildTypes";
|
||||
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { RequestHandler } from "express";
|
||||
@ -35,6 +36,10 @@ export const contributeToDojoComponentController: RequestHandler = async (req, r
|
||||
return;
|
||||
}
|
||||
const guild = await getGuildForRequestEx(req, inventory);
|
||||
const guildMember = (await GuildMember.findOne(
|
||||
{ accountId, guildId: guild._id },
|
||||
"RegularCreditsContributed MiscItemsContributed"
|
||||
))!;
|
||||
const request = JSON.parse(String(req.body)) as IContributeToDojoComponentRequest;
|
||||
const component = guild.DojoComponents.id(request.ComponentId)!;
|
||||
|
||||
@ -45,7 +50,7 @@ export const contributeToDojoComponentController: RequestHandler = async (req, r
|
||||
throw new Error("attempt to contribute to a deco in an unfinished room?!");
|
||||
}
|
||||
const meta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!;
|
||||
processContribution(guild, request, inventory, inventoryChanges, meta, component);
|
||||
processContribution(guild, guildMember, request, inventory, inventoryChanges, meta, component);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (component.CompletionTime) {
|
||||
setDojoRoomLogFunded(guild, component);
|
||||
@ -55,12 +60,11 @@ export const contributeToDojoComponentController: RequestHandler = async (req, r
|
||||
if (request.DecoId) {
|
||||
const deco = component.Decos!.find(x => x._id.equals(request.DecoId))!;
|
||||
const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == deco.Type)!;
|
||||
processContribution(guild, request, inventory, inventoryChanges, meta, deco);
|
||||
processContribution(guild, guildMember, request, inventory, inventoryChanges, meta, deco);
|
||||
}
|
||||
}
|
||||
|
||||
await guild.save();
|
||||
await inventory.save();
|
||||
await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
|
||||
res.json({
|
||||
...(await getDojoClient(guild, 0, component._id)),
|
||||
InventoryChanges: inventoryChanges
|
||||
@ -69,6 +73,7 @@ export const contributeToDojoComponentController: RequestHandler = async (req, r
|
||||
|
||||
const processContribution = (
|
||||
guild: TGuildDatabaseDocument,
|
||||
guildMember: IGuildMemberDatabase,
|
||||
request: IContributeToDojoComponentRequest,
|
||||
inventory: TInventoryDatabaseDocument,
|
||||
inventoryChanges: IInventoryChanges,
|
||||
@ -80,15 +85,18 @@ const processContribution = (
|
||||
component.RegularCredits += request.RegularCredits;
|
||||
inventoryChanges.RegularCredits = -request.RegularCredits;
|
||||
updateCurrency(inventory, request.RegularCredits, false);
|
||||
|
||||
guildMember.RegularCreditsContributed ??= 0;
|
||||
guildMember.RegularCreditsContributed += request.RegularCredits;
|
||||
}
|
||||
if (request.VaultCredits) {
|
||||
component.RegularCredits += request.VaultCredits;
|
||||
guild.VaultRegularCredits! -= request.VaultCredits;
|
||||
}
|
||||
if (component.RegularCredits > scaleRequiredCount(meta.price)) {
|
||||
if (component.RegularCredits > scaleRequiredCount(guild.Tier, meta.price)) {
|
||||
guild.VaultRegularCredits ??= 0;
|
||||
guild.VaultRegularCredits += component.RegularCredits - scaleRequiredCount(meta.price);
|
||||
component.RegularCredits = scaleRequiredCount(meta.price);
|
||||
guild.VaultRegularCredits += component.RegularCredits - scaleRequiredCount(guild.Tier, meta.price);
|
||||
component.RegularCredits = scaleRequiredCount(guild.Tier, meta.price);
|
||||
}
|
||||
|
||||
component.MiscItems ??= [];
|
||||
@ -99,10 +107,10 @@ const processContribution = (
|
||||
const ingredientMeta = meta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
|
||||
if (
|
||||
componentMiscItem.ItemCount + ingredientContribution.ItemCount >
|
||||
scaleRequiredCount(ingredientMeta.ItemCount)
|
||||
scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount)
|
||||
) {
|
||||
ingredientContribution.ItemCount =
|
||||
scaleRequiredCount(ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
|
||||
scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
|
||||
}
|
||||
componentMiscItem.ItemCount += ingredientContribution.ItemCount;
|
||||
} else {
|
||||
@ -120,10 +128,10 @@ const processContribution = (
|
||||
const ingredientMeta = meta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
|
||||
if (
|
||||
componentMiscItem.ItemCount + ingredientContribution.ItemCount >
|
||||
scaleRequiredCount(ingredientMeta.ItemCount)
|
||||
scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount)
|
||||
) {
|
||||
ingredientContribution.ItemCount =
|
||||
scaleRequiredCount(ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
|
||||
scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
|
||||
}
|
||||
componentMiscItem.ItemCount += ingredientContribution.ItemCount;
|
||||
} else {
|
||||
@ -133,16 +141,21 @@ const processContribution = (
|
||||
ItemType: ingredientContribution.ItemType,
|
||||
ItemCount: ingredientContribution.ItemCount * -1
|
||||
});
|
||||
|
||||
addGuildMemberMiscItemContribution(guildMember, ingredientContribution);
|
||||
}
|
||||
addMiscItems(inventory, miscItemChanges);
|
||||
inventoryChanges.MiscItems = miscItemChanges;
|
||||
}
|
||||
|
||||
if (component.RegularCredits >= scaleRequiredCount(meta.price)) {
|
||||
if (component.RegularCredits >= scaleRequiredCount(guild.Tier, meta.price)) {
|
||||
let fullyFunded = true;
|
||||
for (const ingredient of meta.ingredients) {
|
||||
const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredient.ItemType);
|
||||
if (!componentMiscItem || componentMiscItem.ItemCount < scaleRequiredCount(ingredient.ItemCount)) {
|
||||
if (
|
||||
!componentMiscItem ||
|
||||
componentMiscItem.ItemCount < scaleRequiredCount(guild.Tier, ingredient.ItemCount)
|
||||
) {
|
||||
fullyFunded = false;
|
||||
break;
|
||||
}
|
||||
|
@ -1,43 +1,104 @@
|
||||
import { getGuildForRequestEx } from "@/src/services/guildService";
|
||||
import { addFusionTreasures, addMiscItems, addShipDecorations, getInventory } from "@/src/services/inventoryService";
|
||||
import {
|
||||
Alliance,
|
||||
Guild,
|
||||
GuildMember,
|
||||
TGuildDatabaseDocument,
|
||||
TGuildMemberDatabaseDocument
|
||||
} from "@/src/models/guildModel";
|
||||
import {
|
||||
addGuildMemberMiscItemContribution,
|
||||
addGuildMemberShipDecoContribution,
|
||||
addVaultFusionTreasures,
|
||||
addVaultMiscItems,
|
||||
addVaultShipDecos,
|
||||
getGuildForRequestEx
|
||||
} from "@/src/services/guildService";
|
||||
import {
|
||||
addFusionTreasures,
|
||||
addMiscItems,
|
||||
addShipDecorations,
|
||||
getInventory,
|
||||
updateCurrency
|
||||
} from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { IFusionTreasure, IMiscItem, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const contributeToVaultController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId);
|
||||
const guild = await getGuildForRequestEx(req, inventory);
|
||||
const inventory = await getInventory(accountId, "GuildId RegularCredits MiscItems ShipDecorations FusionTreasures");
|
||||
const request = JSON.parse(String(req.body)) as IContributeToVaultRequest;
|
||||
|
||||
if (request.Alliance) {
|
||||
const guild = await getGuildForRequestEx(req, inventory);
|
||||
const alliance = (await Alliance.findById(guild.AllianceId!))!;
|
||||
alliance.VaultRegularCredits ??= 0;
|
||||
alliance.VaultRegularCredits += request.RegularCredits;
|
||||
if (request.FromVault) {
|
||||
guild.VaultRegularCredits! -= request.RegularCredits;
|
||||
await Promise.all([guild.save(), alliance.save()]);
|
||||
} else {
|
||||
updateCurrency(inventory, request.RegularCredits, false);
|
||||
await Promise.all([inventory.save(), alliance.save()]);
|
||||
}
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
let guild: TGuildDatabaseDocument;
|
||||
let guildMember: TGuildMemberDatabaseDocument | undefined;
|
||||
if (request.GuildVault) {
|
||||
guild = (await Guild.findById(request.GuildVault))!;
|
||||
} else {
|
||||
guild = await getGuildForRequestEx(req, inventory);
|
||||
guildMember = (await GuildMember.findOne(
|
||||
{ accountId, guildId: guild._id },
|
||||
"RegularCreditsContributed MiscItemsContributed ShipDecorationsContributed"
|
||||
))!;
|
||||
}
|
||||
|
||||
if (request.RegularCredits) {
|
||||
updateCurrency(inventory, request.RegularCredits, false);
|
||||
|
||||
guild.VaultRegularCredits ??= 0;
|
||||
guild.VaultRegularCredits += request.RegularCredits;
|
||||
|
||||
if (guildMember) {
|
||||
guildMember.RegularCreditsContributed ??= 0;
|
||||
guildMember.RegularCreditsContributed += request.RegularCredits;
|
||||
}
|
||||
}
|
||||
if (request.MiscItems.length) {
|
||||
guild.VaultMiscItems ??= [];
|
||||
addVaultMiscItems(guild, request.MiscItems);
|
||||
for (const item of request.MiscItems) {
|
||||
guild.VaultMiscItems.push(item);
|
||||
if (guildMember) {
|
||||
addGuildMemberMiscItemContribution(guildMember, item);
|
||||
}
|
||||
addMiscItems(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
|
||||
}
|
||||
}
|
||||
if (request.ShipDecorations.length) {
|
||||
guild.VaultShipDecorations ??= [];
|
||||
addVaultShipDecos(guild, request.ShipDecorations);
|
||||
for (const item of request.ShipDecorations) {
|
||||
guild.VaultShipDecorations.push(item);
|
||||
if (guildMember) {
|
||||
addGuildMemberShipDecoContribution(guildMember, item);
|
||||
}
|
||||
addShipDecorations(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
|
||||
}
|
||||
}
|
||||
if (request.FusionTreasures.length) {
|
||||
guild.VaultFusionTreasures ??= [];
|
||||
addVaultFusionTreasures(guild, request.FusionTreasures);
|
||||
for (const item of request.FusionTreasures) {
|
||||
guild.VaultFusionTreasures.push(item);
|
||||
addFusionTreasures(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
|
||||
}
|
||||
}
|
||||
|
||||
await guild.save();
|
||||
await inventory.save();
|
||||
const promises: Promise<unknown>[] = [guild.save(), inventory.save()];
|
||||
if (guildMember) {
|
||||
promises.push(guildMember.save());
|
||||
}
|
||||
await Promise.all(promises);
|
||||
|
||||
res.end();
|
||||
};
|
||||
|
||||
@ -46,4 +107,7 @@ interface IContributeToVaultRequest {
|
||||
MiscItems: IMiscItem[];
|
||||
ShipDecorations: ITypeCount[];
|
||||
FusionTreasures: IFusionTreasure[];
|
||||
Alliance?: boolean;
|
||||
FromVault?: boolean;
|
||||
GuildVault?: string;
|
||||
}
|
||||
|
50
src/controllers/api/createAllianceController.ts
Normal file
50
src/controllers/api/createAllianceController.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
|
||||
import { getAllianceClient } from "@/src/services/guildService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { GuildPermission } from "@/src/types/guildTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const createAllianceController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "GuildId");
|
||||
const guild = (await Guild.findById(inventory.GuildId!, "Name Tier AllianceId"))!;
|
||||
if (guild.AllianceId) {
|
||||
res.status(400).send("Guild is already in an alliance").end();
|
||||
return;
|
||||
}
|
||||
const guildMember = (await GuildMember.findOne({ guildId: guild._id, accountId }, "rank"))!;
|
||||
if (guildMember.rank > 1) {
|
||||
res.status(400).send("Invalid permission").end();
|
||||
return;
|
||||
}
|
||||
const data = getJSONfromString<ICreateAllianceRequest>(String(req.body));
|
||||
const alliance = new Alliance({ Name: data.allianceName });
|
||||
try {
|
||||
await alliance.save();
|
||||
} catch (e) {
|
||||
res.status(400).send("Alliance name already in use").end();
|
||||
return;
|
||||
}
|
||||
guild.AllianceId = alliance._id;
|
||||
await Promise.all([
|
||||
guild.save(),
|
||||
AllianceMember.insertOne({
|
||||
allianceId: alliance._id,
|
||||
guildId: guild._id,
|
||||
Pending: false,
|
||||
Permissions:
|
||||
GuildPermission.Ruler |
|
||||
GuildPermission.Promoter |
|
||||
GuildPermission.Recruiter |
|
||||
GuildPermission.Treasurer |
|
||||
GuildPermission.ChatModerator
|
||||
})
|
||||
]);
|
||||
res.json(await getAllianceClient(alliance, guild));
|
||||
};
|
||||
|
||||
interface ICreateAllianceRequest {
|
||||
allianceName: string;
|
||||
}
|
@ -2,16 +2,17 @@ import { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { Guild, GuildMember } from "@/src/models/guildModel";
|
||||
import {
|
||||
createUniqueClanName,
|
||||
getGuildClient,
|
||||
updateInventoryForConfirmedGuildJoin
|
||||
} from "@/src/services/guildService";
|
||||
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 payload = getJSONfromString<ICreateGuildRequest>(String(req.body));
|
||||
|
||||
// Remove pending applications for this account
|
||||
await GuildMember.deleteMany({ accountId, status: 1 });
|
||||
|
||||
// Create guild on database
|
||||
const guild = new Guild({
|
||||
Name: await createUniqueClanName(payload.guildName)
|
||||
@ -26,9 +27,16 @@ export const createGuildController: RequestHandler = async (req, res) => {
|
||||
rank: 0
|
||||
});
|
||||
|
||||
await updateInventoryForConfirmedGuildJoin(accountId, guild._id);
|
||||
const inventory = await getInventory(accountId, "GuildId LevelKeys Recipes");
|
||||
inventory.GuildId = guild._id;
|
||||
const inventoryChanges: IInventoryChanges = {};
|
||||
giveClanKey(inventory, inventoryChanges);
|
||||
await inventory.save();
|
||||
|
||||
res.json(await getGuildClient(guild, accountId));
|
||||
res.json({
|
||||
...(await getGuildClient(guild, accountId)),
|
||||
InventoryChanges: inventoryChanges
|
||||
});
|
||||
};
|
||||
|
||||
interface ICreateGuildRequest {
|
||||
|
28
src/controllers/api/crewMembersController.ts
Normal file
28
src/controllers/api/crewMembersController.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
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");
|
||||
const data = getJSONfromString<ICrewMembersRequest>(String(req.body));
|
||||
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;
|
||||
}
|
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;
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { Guild } from "@/src/models/guildModel";
|
||||
import { getAccountForRequest } from "@/src/services/loginService";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const customObstacleCourseLeaderboardController: RequestHandler = async (req, res) => {
|
||||
const data = getJSONfromString<ICustomObstacleCourseLeaderboardRequest>(String(req.body));
|
||||
const guild = (await Guild.findById(data.g, "DojoComponents"))!;
|
||||
const component = guild.DojoComponents.id(data.c)!;
|
||||
if (req.query.act == "f") {
|
||||
res.json({
|
||||
results: component.Leaderboard ?? []
|
||||
});
|
||||
} else if (req.query.act == "p") {
|
||||
const account = await getAccountForRequest(req);
|
||||
component.Leaderboard ??= [];
|
||||
const entry = component.Leaderboard.find(x => x.n == account.DisplayName);
|
||||
if (entry) {
|
||||
entry.s = data.s!;
|
||||
} else {
|
||||
component.Leaderboard.push({
|
||||
s: data.s!,
|
||||
n: account.DisplayName,
|
||||
r: 0
|
||||
});
|
||||
}
|
||||
component.Leaderboard.sort((a, b) => a.s - b.s); // In this case, the score is the time in milliseconds, so smaller is better.
|
||||
if (component.Leaderboard.length > 10) {
|
||||
component.Leaderboard.shift();
|
||||
}
|
||||
let r = 0;
|
||||
for (const entry of component.Leaderboard) {
|
||||
entry.r = ++r;
|
||||
}
|
||||
await guild.save();
|
||||
res.status(200).end();
|
||||
} else {
|
||||
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
||||
throw new Error(`unknown customObstacleCourseLeaderboard act: ${String(req.query.act)}`);
|
||||
}
|
||||
};
|
||||
|
||||
interface ICustomObstacleCourseLeaderboardRequest {
|
||||
g: string;
|
||||
c: string;
|
||||
s?: number; // act=p
|
||||
}
|
17
src/controllers/api/declineAllianceInviteController.ts
Normal file
17
src/controllers/api/declineAllianceInviteController.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { AllianceMember, GuildMember } from "@/src/models/guildModel";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const declineAllianceInviteController: RequestHandler = async (req, res) => {
|
||||
// Check requester is a warlord in their guild
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const guildMember = (await GuildMember.findOne({ accountId, status: 0 }))!;
|
||||
if (guildMember.rank > 1) {
|
||||
res.status(400).json({ Error: 104 });
|
||||
return;
|
||||
}
|
||||
|
||||
await AllianceMember.deleteOne({ allianceId: req.query.allianceId, guildId: guildMember.guildId });
|
||||
|
||||
res.end();
|
||||
};
|
67
src/controllers/api/divvyAllianceVaultController.ts
Normal file
67
src/controllers/api/divvyAllianceVaultController.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
|
||||
import { getAccountForRequest } from "@/src/services/loginService";
|
||||
import { GuildPermission } from "@/src/types/guildTypes";
|
||||
import { parallelForeach } from "@/src/utils/async-utils";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const divvyAllianceVaultController: RequestHandler = async (req, res) => {
|
||||
// Afaict, there's no way to put anything other than credits in the alliance vault (anymore?), so just no-op if this is not a request to divvy credits.
|
||||
if (req.query.credits == "1") {
|
||||
// Check requester is a warlord in their guild
|
||||
const account = await getAccountForRequest(req);
|
||||
const guildMember = (await GuildMember.findOne({ accountId: account._id, status: 0 }))!;
|
||||
if (guildMember.rank > 1) {
|
||||
res.status(400).end();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check guild has treasurer permissions in the alliance
|
||||
const allianceMember = (await AllianceMember.findOne({
|
||||
allianceId: req.query.allianceId,
|
||||
guildId: guildMember.guildId
|
||||
}))!;
|
||||
if (!(allianceMember.Permissions & GuildPermission.Treasurer)) {
|
||||
res.status(400).end();
|
||||
return;
|
||||
}
|
||||
|
||||
const allianceMembers = await AllianceMember.find({ allianceId: req.query.allianceId });
|
||||
const memberCounts: Record<string, number> = {};
|
||||
let totalMembers = 0;
|
||||
await parallelForeach(allianceMembers, async allianceMember => {
|
||||
const memberCount = await GuildMember.countDocuments({
|
||||
guildId: allianceMember.guildId
|
||||
});
|
||||
memberCounts[allianceMember.guildId.toString()] = memberCount;
|
||||
totalMembers += memberCount;
|
||||
});
|
||||
logger.debug(`alliance has ${totalMembers} members between all its clans`);
|
||||
|
||||
const alliance = (await Alliance.findById(allianceMember.allianceId, "VaultRegularCredits"))!;
|
||||
if (alliance.VaultRegularCredits) {
|
||||
let creditsHandedOutInTotal = 0;
|
||||
await parallelForeach(allianceMembers, async allianceMember => {
|
||||
const memberCount = memberCounts[allianceMember.guildId.toString()];
|
||||
const cutPercentage = memberCount / totalMembers;
|
||||
const creditsToHandOut = Math.trunc(alliance.VaultRegularCredits! * cutPercentage);
|
||||
logger.debug(
|
||||
`${allianceMember.guildId.toString()} has ${memberCount} member(s) = ${Math.trunc(cutPercentage * 100)}% of alliance; giving ${creditsToHandOut} credit(s)`
|
||||
);
|
||||
if (creditsToHandOut != 0) {
|
||||
await Guild.updateOne(
|
||||
{ _id: allianceMember.guildId },
|
||||
{ $inc: { VaultRegularCredits: creditsToHandOut } }
|
||||
);
|
||||
creditsHandedOutInTotal += creditsToHandOut;
|
||||
}
|
||||
});
|
||||
alliance.VaultRegularCredits -= creditsHandedOutInTotal;
|
||||
logger.debug(
|
||||
`handed out ${creditsHandedOutInTotal} credits; alliance vault now has ${alliance.VaultRegularCredits} credit(s)`
|
||||
);
|
||||
}
|
||||
await alliance.save();
|
||||
}
|
||||
res.end();
|
||||
};
|
@ -1,3 +1,4 @@
|
||||
import { GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
|
||||
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, scaleRequiredCount } from "@/src/services/guildService";
|
||||
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
@ -35,10 +36,10 @@ export const dojoComponentRushController: RequestHandler = async (req, res) => {
|
||||
if (request.DecoId) {
|
||||
const deco = component.Decos!.find(x => x._id.equals(request.DecoId))!;
|
||||
const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == deco.Type)!;
|
||||
processContribution(deco, meta, platinumDonated);
|
||||
processContribution(guild, deco, meta, platinumDonated);
|
||||
} else {
|
||||
const meta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!;
|
||||
processContribution(component, meta, platinumDonated);
|
||||
processContribution(guild, component, meta, platinumDonated);
|
||||
|
||||
const entry = guild.RoomChanges?.find(x => x.componentId.equals(component._id));
|
||||
if (entry) {
|
||||
@ -46,16 +47,25 @@ export const dojoComponentRushController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
}
|
||||
|
||||
await guild.save();
|
||||
await inventory.save();
|
||||
const guildMember = (await GuildMember.findOne({ accountId, guildId: guild._id }, "PremiumCreditsContributed"))!;
|
||||
guildMember.PremiumCreditsContributed ??= 0;
|
||||
guildMember.PremiumCreditsContributed += request.Amount;
|
||||
|
||||
await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
|
||||
|
||||
res.json({
|
||||
...(await getDojoClient(guild, 0, component._id)),
|
||||
InventoryChanges: inventoryChanges
|
||||
});
|
||||
};
|
||||
|
||||
const processContribution = (component: IDojoContributable, meta: IDojoBuild, platinumDonated: number): void => {
|
||||
const fullPlatinumCost = scaleRequiredCount(meta.skipTimePrice);
|
||||
const processContribution = (
|
||||
guild: TGuildDatabaseDocument,
|
||||
component: IDojoContributable,
|
||||
meta: IDojoBuild,
|
||||
platinumDonated: number
|
||||
): void => {
|
||||
const fullPlatinumCost = scaleRequiredCount(guild.Tier, meta.skipTimePrice);
|
||||
const fullDurationSeconds = meta.time;
|
||||
const secondsPerPlatinum = fullDurationSeconds / fullPlatinumCost;
|
||||
component.CompletionTime = new Date(
|
||||
|
@ -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)!;
|
||||
|
71
src/controllers/api/entratiLabConquestModeController.ts
Normal file
71
src/controllers/api/entratiLabConquestModeController.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const entratiLabConquestModeController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(
|
||||
accountId,
|
||||
"EntratiVaultCountResetDate EntratiVaultCountLastPeriod EntratiLabConquestUnlocked EchoesHexConquestUnlocked EchoesHexConquestActiveFrameVariants EchoesHexConquestActiveStickers EntratiLabConquestActiveFrameVariants EntratiLabConquestCacheScoreMission EchoesHexConquestCacheScoreMission"
|
||||
);
|
||||
const body = getJSONfromString<IEntratiLabConquestModeRequest>(String(req.body));
|
||||
if (!inventory.EntratiVaultCountResetDate || Date.now() >= inventory.EntratiVaultCountResetDate.getTime()) {
|
||||
const EPOCH = 1734307200 * 1000; // Mondays, amirite?
|
||||
const day = Math.trunc((Date.now() - EPOCH) / 86400000);
|
||||
const week = Math.trunc(day / 7);
|
||||
const weekStart = EPOCH + week * 604800000;
|
||||
const weekEnd = weekStart + 604800000;
|
||||
inventory.EntratiVaultCountLastPeriod = 0;
|
||||
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 = [];
|
||||
}
|
||||
}
|
||||
if (body.BuyMode) {
|
||||
inventory.EntratiVaultCountLastPeriod! += 2;
|
||||
if (body.IsEchoesDeepArchemedea) {
|
||||
inventory.EchoesHexConquestUnlocked = 1;
|
||||
} else {
|
||||
inventory.EntratiLabConquestUnlocked = 1;
|
||||
}
|
||||
}
|
||||
if (body.IsEchoesDeepArchemedea) {
|
||||
if (inventory.EchoesHexConquestUnlocked) {
|
||||
inventory.EchoesHexConquestActiveFrameVariants = body.EchoesHexConquestActiveFrameVariants!;
|
||||
inventory.EchoesHexConquestActiveStickers = body.EchoesHexConquestActiveStickers!;
|
||||
}
|
||||
} else {
|
||||
if (inventory.EntratiLabConquestUnlocked) {
|
||||
inventory.EntratiLabConquestActiveFrameVariants = body.EntratiLabConquestActiveFrameVariants!;
|
||||
}
|
||||
}
|
||||
await inventory.save();
|
||||
res.json({
|
||||
EntratiVaultCountResetDate: toMongoDate(inventory.EntratiVaultCountResetDate),
|
||||
EntratiVaultCountLastPeriod: inventory.EntratiVaultCountLastPeriod,
|
||||
EntratiLabConquestUnlocked: inventory.EntratiLabConquestUnlocked,
|
||||
EntratiLabConquestCacheScoreMission: inventory.EntratiLabConquestCacheScoreMission,
|
||||
EchoesHexConquestUnlocked: inventory.EchoesHexConquestUnlocked,
|
||||
EchoesHexConquestCacheScoreMission: inventory.EchoesHexConquestCacheScoreMission
|
||||
});
|
||||
};
|
||||
|
||||
interface IEntratiLabConquestModeRequest {
|
||||
BuyMode?: number;
|
||||
IsEchoesDeepArchemedea?: number;
|
||||
EntratiLabConquestUnlocked?: number;
|
||||
EntratiLabConquestActiveFrameVariants?: string[];
|
||||
EchoesHexConquestUnlocked?: number;
|
||||
EchoesHexConquestActiveFrameVariants?: string[];
|
||||
EchoesHexConquestActiveStickers?: string[];
|
||||
}
|
@ -17,7 +17,7 @@ export const evolveWeaponController: RequestHandler = async (req, res) => {
|
||||
recipe.ingredients.map(x => ({ ItemType: x.ItemType, ItemCount: x.ItemCount * -1 }))
|
||||
);
|
||||
|
||||
const item = inventory[payload.Category].find(item => item._id.toString() == (req.query.ItemId as string))!;
|
||||
const item = inventory[payload.Category].id(req.query.ItemId as string)!;
|
||||
item.Features ??= 0;
|
||||
item.Features |= EquipmentFeatures.INCARNON_GENESIS;
|
||||
|
||||
@ -39,7 +39,7 @@ export const evolveWeaponController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
]);
|
||||
|
||||
const item = inventory[payload.Category].find(item => item._id.toString() == (req.query.ItemId as string))!;
|
||||
const item = inventory[payload.Category].id(req.query.ItemId as string)!;
|
||||
item.Features! &= ~EquipmentFeatures.INCARNON_GENESIS;
|
||||
} else {
|
||||
throw new Error(`unexpected evolve weapon action: ${payload.Action}`);
|
||||
|
@ -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
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -18,8 +18,8 @@ export const focusController: RequestHandler = async (req, res) => {
|
||||
case FocusOperation.InstallLens: {
|
||||
const request = JSON.parse(String(req.body)) as ILensInstallRequest;
|
||||
const inventory = await getInventory(accountId);
|
||||
for (const item of inventory[request.Category]) {
|
||||
if (item._id.toString() == request.WeaponId) {
|
||||
const item = inventory[request.Category].id(request.WeaponId);
|
||||
if (item) {
|
||||
item.FocusLens = request.LensType;
|
||||
addMiscItems(inventory, [
|
||||
{
|
||||
@ -27,8 +27,6 @@ export const focusController: RequestHandler = async (req, res) => {
|
||||
ItemCount: -1
|
||||
} satisfies IMiscItem
|
||||
]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
await inventory.save();
|
||||
res.json({
|
||||
@ -106,13 +104,14 @@ export const focusController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
case FocusOperation.SentTrainingAmplifier: {
|
||||
const request = JSON.parse(String(req.body)) as ISentTrainingAmplifierRequest;
|
||||
const parts: string[] = [
|
||||
const inventory = await getInventory(accountId);
|
||||
const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, {
|
||||
ModularParts: [
|
||||
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingGrip",
|
||||
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingChassis",
|
||||
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingBarrel"
|
||||
];
|
||||
const inventory = await getInventory(accountId);
|
||||
const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, parts);
|
||||
]
|
||||
});
|
||||
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[][]>;
|
||||
}
|
@ -10,8 +10,7 @@ import { IGenericUpdate } from "@/src/types/genericUpdate";
|
||||
const genericUpdateController: RequestHandler = async (request, response) => {
|
||||
const accountId = await getAccountIdForRequest(request);
|
||||
const update = getJSONfromString<IGenericUpdate>(String(request.body));
|
||||
await updateGeneric(update, accountId);
|
||||
response.json(update);
|
||||
response.json(await updateGeneric(update, accountId));
|
||||
};
|
||||
|
||||
export { genericUpdateController };
|
||||
|
@ -1,7 +1,25 @@
|
||||
import { Alliance, Guild } from "@/src/models/guildModel";
|
||||
import { getAllianceClient } from "@/src/services/guildService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
const getAllianceController: RequestHandler = (_req, res) => {
|
||||
res.sendStatus(200);
|
||||
export const getAllianceController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "GuildId");
|
||||
if (inventory.GuildId) {
|
||||
const guild = (await Guild.findById(inventory.GuildId, "Name Tier AllianceId"))!;
|
||||
if (guild.AllianceId) {
|
||||
const alliance = (await Alliance.findById(guild.AllianceId))!;
|
||||
res.json(await getAllianceClient(alliance, guild));
|
||||
return;
|
||||
}
|
||||
}
|
||||
res.end();
|
||||
};
|
||||
|
||||
export { getAllianceController };
|
||||
/*interface IGetAllianceRequest {
|
||||
memberCount: number;
|
||||
clanLeaderName: string;
|
||||
clanLeaderId: string;
|
||||
}*/
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Request, 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",
|
||||
|
19
src/controllers/api/getGuildContributionsController.ts
Normal file
19
src/controllers/api/getGuildContributionsController.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { GuildMember } from "@/src/models/guildModel";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { IGuildMemberClient } from "@/src/types/guildTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const getGuildContributionsController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const guildId = (await getInventory(accountId, "GuildId")).GuildId;
|
||||
const guildMember = (await GuildMember.findOne({ guildId, accountId: req.query.buddyId }))!;
|
||||
res.json({
|
||||
_id: { $oid: req.query.buddyId as string },
|
||||
RegularCreditsContributed: guildMember.RegularCreditsContributed,
|
||||
PremiumCreditsContributed: guildMember.PremiumCreditsContributed,
|
||||
MiscItemsContributed: guildMember.MiscItemsContributed,
|
||||
ConsumablesContributed: [], // ???
|
||||
ShipDecorationsContributed: guildMember.ShipDecorationsContributed
|
||||
} satisfies Partial<IGuildMemberClient>);
|
||||
};
|
@ -5,11 +5,11 @@ import { logger } from "@/src/utils/logger";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { createUniqueClanName, getGuildClient } from "@/src/services/guildService";
|
||||
|
||||
const getGuildController: RequestHandler = async (req, res) => {
|
||||
export const getGuildController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "GuildId");
|
||||
if (inventory.GuildId) {
|
||||
const guild = await Guild.findOne({ _id: inventory.GuildId });
|
||||
const guild = await Guild.findById(inventory.GuildId);
|
||||
if (guild) {
|
||||
// Handle guilds created before we added discriminators
|
||||
if (guild.Name.indexOf("#") == -1) {
|
||||
@ -28,7 +28,5 @@ const getGuildController: RequestHandler = async (req, res) => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
res.sendStatus(200);
|
||||
res.end();
|
||||
};
|
||||
|
||||
export { getGuildController };
|
||||
|
@ -6,7 +6,7 @@ import { getDojoClient } from "@/src/services/guildService";
|
||||
export const getGuildDojoController: RequestHandler = async (req, res) => {
|
||||
const guildId = req.query.guildId as string;
|
||||
|
||||
const guild = await Guild.findOne({ _id: guildId });
|
||||
const guild = await Guild.findById(guildId);
|
||||
if (!guild) {
|
||||
res.status(404).end();
|
||||
return;
|
||||
|
@ -9,7 +9,7 @@ export const getGuildLogController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "GuildId");
|
||||
if (inventory.GuildId) {
|
||||
const guild = await Guild.findOne({ _id: inventory.GuildId });
|
||||
const guild = await Guild.findById(inventory.GuildId);
|
||||
if (guild) {
|
||||
const log: Record<string, IGuildLogEntryClient[]> = {
|
||||
RoomChanges: [],
|
||||
|
@ -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/guildTypes";
|
||||
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,13 +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
|
||||
@ -18,9 +17,3 @@ export const getNewRewardSeedController: RequestHandler = async (req, res) => {
|
||||
);
|
||||
res.json({ rewardSeed: rewardSeed });
|
||||
};
|
||||
|
||||
export function generateRewardSeed(): number {
|
||||
const min = -Number.MAX_SAFE_INTEGER;
|
||||
const max = Number.MAX_SAFE_INTEGER;
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
@ -2,17 +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 { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
|
||||
import { getShip } from "@/src/services/shipService";
|
||||
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");
|
||||
|
||||
@ -26,7 +33,10 @@ export const getShipController: RequestHandler = async (req, res) => {
|
||||
Colors: personalRooms.ShipInteriorColors,
|
||||
ShipAttachments: ship.ShipAttachments,
|
||||
SkinFlavourItem: ship.SkinFlavourItem
|
||||
}
|
||||
},
|
||||
FavouriteLoadoutId: personalRooms.Ship.FavouriteLoadoutId
|
||||
? toOid(personalRooms.Ship.FavouriteLoadoutId)
|
||||
: undefined
|
||||
},
|
||||
Apartment: personalRooms.Apartment,
|
||||
TailorShop: personalRooms.TailorShop
|
||||
|
92
src/controllers/api/giftingController.ts
Normal file
92
src/controllers/api/giftingController.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { Account } from "@/src/models/loginModel";
|
||||
import { createMessage } from "@/src/services/inboxService";
|
||||
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||
import { IOid } from "@/src/types/commonTypes";
|
||||
import { IPurchaseParams } from "@/src/types/purchaseTypes";
|
||||
import { RequestHandler } from "express";
|
||||
import { ExportFlavour } from "warframe-public-export-plus";
|
||||
|
||||
export const giftingController: RequestHandler = async (req, res) => {
|
||||
const data = getJSONfromString<IGiftingRequest>(String(req.body));
|
||||
if (data.PurchaseParams.Source != 0 || !data.PurchaseParams.UsePremium) {
|
||||
throw new Error(`unexpected purchase params in gifting request: ${String(req.body)}`);
|
||||
}
|
||||
|
||||
const account = await Account.findOne(
|
||||
data.RecipientId ? { _id: data.RecipientId.$oid } : { DisplayName: data.Recipient }
|
||||
);
|
||||
if (!account) {
|
||||
res.status(400).send("9").end();
|
||||
return;
|
||||
}
|
||||
const inventory = await getInventory(account._id.toString(), "Suits Settings");
|
||||
|
||||
// Cannot gift items to players that have not completed the tutorial.
|
||||
if (inventory.Suits.length == 0) {
|
||||
res.status(400).send("14").end();
|
||||
return;
|
||||
}
|
||||
|
||||
// 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") {
|
||||
res.status(400).send("17").end();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Cannot gift items with mastery requirement to players who are too low level. (Code 2)
|
||||
// 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"
|
||||
);
|
||||
|
||||
if (senderInventory.GiftsRemaining == 0) {
|
||||
res.status(400).send("10").end();
|
||||
return;
|
||||
}
|
||||
senderInventory.GiftsRemaining -= 1;
|
||||
|
||||
updateCurrency(senderInventory, data.PurchaseParams.ExpectedPrice, true);
|
||||
await senderInventory.save();
|
||||
|
||||
const senderName = getSuffixedName(senderAccount);
|
||||
await createMessage(account._id, [
|
||||
{
|
||||
sndr: senderName,
|
||||
msg: data.Message || "/Lotus/Language/Menu/GiftReceivedBody_NoCustomMessage",
|
||||
arg: [
|
||||
{
|
||||
Key: "GIFTER_NAME",
|
||||
Tag: senderName
|
||||
},
|
||||
{
|
||||
Key: "GIFT_QUANTITY",
|
||||
Tag: data.PurchaseParams.Quantity
|
||||
}
|
||||
],
|
||||
sub: "/Lotus/Language/Menu/GiftReceivedSubject",
|
||||
icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
|
||||
gifts: [
|
||||
{
|
||||
GiftType: data.PurchaseParams.StoreItem
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
res.end();
|
||||
};
|
||||
|
||||
interface IGiftingRequest {
|
||||
PurchaseParams: IPurchaseParams;
|
||||
Message?: string;
|
||||
Recipient?: string;
|
||||
RecipientId?: IOid;
|
||||
buildLabel: string;
|
||||
}
|
@ -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;
|
||||
if (data.Recipe != "webui") {
|
||||
weapon.ItemName = data.ItemName;
|
||||
weapon.XP = 0;
|
||||
}
|
||||
if (data.Category != "OperatorAmps" && data.PolarizeSlot && data.PolarizeValue) {
|
||||
weapon.Polarity = [
|
||||
{
|
||||
@ -56,6 +47,9 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
|
||||
const inventoryChanges: IInventoryChanges = {};
|
||||
inventoryChanges[data.Category] = [weapon.toJSON<IEquipmentClient>()];
|
||||
|
||||
const affiliationMods = [];
|
||||
|
||||
if (data.Recipe != "webui") {
|
||||
const recipe = ExportRecipes[data.Recipe];
|
||||
inventoryChanges.MiscItems = recipe.secretIngredients!.map(ingredient => ({
|
||||
ItemType: ingredient.ItemType,
|
||||
@ -63,7 +57,6 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
|
||||
}));
|
||||
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;
|
||||
@ -72,6 +65,7 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
|
||||
Standing: recipe.syndicateStandingChange.value
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await inventory.save();
|
||||
res.json({
|
||||
|
@ -2,8 +2,8 @@ import { RequestHandler } from "express";
|
||||
import { parseString } from "@/src/helpers/general";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { IGroup } from "@/src/types/loginTypes";
|
||||
import { giveKeyChainItem } from "@/src/services/questService";
|
||||
import { IKeyChainRequest } from "@/src/types/requestTypes";
|
||||
|
||||
export const giveKeyChainTriggeredItemsController: RequestHandler = async (req, res) => {
|
||||
const accountId = parseString(req.query.accountId);
|
||||
@ -15,9 +15,3 @@ export const giveKeyChainTriggeredItemsController: RequestHandler = async (req,
|
||||
|
||||
res.send(inventoryChanges);
|
||||
};
|
||||
|
||||
export interface IKeyChainRequest {
|
||||
KeyChain: string;
|
||||
ChainStage: number;
|
||||
Groups?: IGroup[];
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { IKeyChainRequest } from "@/src/controllers/api/giveKeyChainTriggeredItemsController";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { giveKeyChainMessage } from "@/src/services/questService";
|
||||
import { IKeyChainRequest } from "@/src/types/requestTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const giveKeyChainTriggeredMessageController: RequestHandler = async (req, res) => {
|
||||
|
@ -16,15 +16,15 @@ 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
|
||||
};
|
||||
|
||||
export interface IQuestKeyRewardRequest {
|
||||
interface IQuestKeyRewardRequest {
|
||||
reward: IQuestKeyReward;
|
||||
}
|
||||
|
||||
export interface IQuestKeyReward {
|
||||
interface IQuestKeyReward {
|
||||
RewardType: string;
|
||||
CouponType: string;
|
||||
Icon: string;
|
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[];
|
||||
}
|
@ -1,20 +1,8 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { InventoryDocumentProps } from "@/src/models/inventoryModels/inventoryModel";
|
||||
import {
|
||||
addEquipment,
|
||||
addItem,
|
||||
addPowerSuit,
|
||||
combineInventoryChanges,
|
||||
getInventory,
|
||||
updateSlots
|
||||
} from "@/src/services/inventoryService";
|
||||
import { addStartingGear, getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { IInventoryClient, IInventoryDatabase, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { TPartialStartingGear } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { RequestHandler } from "express";
|
||||
import { HydratedDocument } from "mongoose";
|
||||
|
||||
type TPartialStartingGear = Pick<IInventoryClient, "LongGuns" | "Suits" | "Pistols" | "Melee">;
|
||||
|
||||
export const giveStartingGearController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
@ -26,72 +14,3 @@ export const giveStartingGearController: RequestHandler = async (req, res) => {
|
||||
|
||||
res.send(inventoryChanges);
|
||||
};
|
||||
|
||||
//TODO: RawUpgrades might need to return a LastAdded
|
||||
const awakeningRewards = [
|
||||
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem1",
|
||||
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem2",
|
||||
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem3",
|
||||
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem4",
|
||||
"/Lotus/Types/Restoratives/LisetAutoHack",
|
||||
"/Lotus/Upgrades/Mods/Warframe/AvatarShieldMaxMod"
|
||||
];
|
||||
|
||||
export const addStartingGear = async (
|
||||
inventory: HydratedDocument<IInventoryDatabase, InventoryDocumentProps>,
|
||||
startingGear: TPartialStartingGear | undefined = undefined
|
||||
): Promise<IInventoryChanges> => {
|
||||
const { LongGuns, Pistols, Suits, Melee } = startingGear || {
|
||||
LongGuns: [{ ItemType: "/Lotus/Weapons/Tenno/Rifle/Rifle" }],
|
||||
Pistols: [{ ItemType: "/Lotus/Weapons/Tenno/Pistol/Pistol" }],
|
||||
Suits: [{ ItemType: "/Lotus/Powersuits/Excalibur/Excalibur" }],
|
||||
Melee: [{ ItemType: "/Lotus/Weapons/Tenno/Melee/LongSword/LongSword" }]
|
||||
};
|
||||
|
||||
//TODO: properly merge weapon bin changes it is currently static here
|
||||
const inventoryChanges: IInventoryChanges = {};
|
||||
addEquipment(inventory, "LongGuns", LongGuns[0].ItemType, undefined, inventoryChanges);
|
||||
addEquipment(inventory, "Pistols", Pistols[0].ItemType, undefined, inventoryChanges);
|
||||
addEquipment(inventory, "Melee", Melee[0].ItemType, undefined, inventoryChanges);
|
||||
addPowerSuit(inventory, Suits[0].ItemType, inventoryChanges);
|
||||
addEquipment(
|
||||
inventory,
|
||||
"DataKnives",
|
||||
"/Lotus/Weapons/Tenno/HackingDevices/TnHackingDevice/TnHackingDeviceWeapon",
|
||||
undefined,
|
||||
inventoryChanges,
|
||||
{ XP: 450_000 }
|
||||
);
|
||||
addEquipment(
|
||||
inventory,
|
||||
"Scoops",
|
||||
"/Lotus/Weapons/Tenno/Speedball/SpeedballWeaponTest",
|
||||
undefined,
|
||||
inventoryChanges
|
||||
);
|
||||
|
||||
updateSlots(inventory, InventorySlot.SUITS, 0, 1);
|
||||
updateSlots(inventory, InventorySlot.WEAPONS, 0, 3);
|
||||
inventoryChanges.SuitBin = { count: 1, platinum: 0, Slots: -1 };
|
||||
inventoryChanges.WeaponBin = { count: 3, platinum: 0, Slots: -3 };
|
||||
|
||||
await addItem(inventory, "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain");
|
||||
inventory.ActiveQuest = "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain";
|
||||
|
||||
inventory.PremiumCredits = 50;
|
||||
inventory.PremiumCreditsFree = 50;
|
||||
inventoryChanges.PremiumCredits = 50;
|
||||
inventoryChanges.PremiumCreditsFree = 50;
|
||||
inventory.RegularCredits = 3000;
|
||||
inventoryChanges.RegularCredits = 3000;
|
||||
|
||||
for (const item of awakeningRewards) {
|
||||
const inventoryDelta = await addItem(inventory, item);
|
||||
combineInventoryChanges(inventoryChanges, inventoryDelta.InventoryChanges);
|
||||
}
|
||||
|
||||
inventory.PlayedParkourTutorial = true;
|
||||
inventory.ReceivedStartingGear = true;
|
||||
|
||||
return inventoryChanges;
|
||||
};
|
||||
|
@ -1,37 +1,46 @@
|
||||
import { RequestHandler } from "express";
|
||||
import {
|
||||
addGuildMemberMiscItemContribution,
|
||||
getGuildForRequestEx,
|
||||
getGuildVault,
|
||||
hasAccessToDojo,
|
||||
hasGuildPermission,
|
||||
processFundedGuildTechProject,
|
||||
processGuildTechProjectContributionsUpdate,
|
||||
removePigmentsFromGuildMembers,
|
||||
scaleRequiredCount
|
||||
scaleRequiredCount,
|
||||
setGuildTechLogState
|
||||
} from "@/src/services/guildService";
|
||||
import { ExportDojoRecipes, IDojoResearch } from "warframe-public-export-plus";
|
||||
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, ITechProjectDatabase } from "@/src/types/guildTypes";
|
||||
import { TGuildDatabaseDocument } from "@/src/models/guildModel";
|
||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||
import { GuildPermission, ITechProjectClient } from "@/src/types/guildTypes";
|
||||
import { GuildMember } from "@/src/models/guildModel";
|
||||
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 = {
|
||||
@ -43,7 +52,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
||||
if (project.CompletionDate) {
|
||||
techProject.CompletionDate = toMongoDate(project.CompletionDate);
|
||||
if (Date.now() >= project.CompletionDate.getTime()) {
|
||||
needSave ||= setTechLogState(guild, project.ItemType, 4, project.CompletionDate);
|
||||
needSave ||= setGuildTechLogState(guild, project.ItemType, 4, project.CompletionDate);
|
||||
}
|
||||
}
|
||||
techProjects.push(techProject);
|
||||
@ -54,6 +63,8 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
res.json({ TechProjects: techProjects });
|
||||
} else if (data.Action == "Start") {
|
||||
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;
|
||||
@ -65,17 +76,17 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
||||
guild.TechProjects[
|
||||
guild.TechProjects.push({
|
||||
ItemType: data.RecipeType,
|
||||
ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(recipe.price),
|
||||
ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, recipe.price),
|
||||
ReqItems: recipe.ingredients.map(x => ({
|
||||
ItemType: x.ItemType,
|
||||
ItemCount: config.noDojoResearchCosts ? 0 : scaleRequiredCount(x.ItemCount)
|
||||
ItemCount: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, x.ItemCount)
|
||||
})),
|
||||
State: 0
|
||||
}) - 1
|
||||
];
|
||||
setTechLogState(guild, techProject.ItemType, 5);
|
||||
setGuildTechLogState(guild, techProject.ItemType, 5);
|
||||
if (config.noDojoResearchCosts) {
|
||||
processFundedProject(guild, techProject, recipe);
|
||||
processFundedGuildTechProject(guild, techProject, recipe);
|
||||
} else {
|
||||
if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
|
||||
guild.ActiveDojoColorResearch = data.RecipeType;
|
||||
@ -84,29 +95,109 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
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`
|
||||
);
|
||||
}
|
||||
}
|
||||
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)!;
|
||||
|
||||
techProject.ReqCredits -= data.RegularCredits;
|
||||
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, data.RegularCredits, false);
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
addMiscItems(inventory, miscItemChanges);
|
||||
inventoryChanges.MiscItems = miscItemChanges;
|
||||
|
||||
techProject.HasContributions = true;
|
||||
|
||||
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 contributions = data;
|
||||
const techProject = guild.TechProjects!.find(x => x.ItemType == contributions.RecipeType)!;
|
||||
|
||||
if (contributions.VaultCredits) {
|
||||
if (contributions.VaultCredits > techProject.ReqCredits) {
|
||||
contributions.VaultCredits = techProject.ReqCredits;
|
||||
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 -= contributions.VaultCredits;
|
||||
guild.VaultRegularCredits! -= contributions.VaultCredits;
|
||||
techProject.ReqCredits -= data.VaultCredits;
|
||||
guild.VaultRegularCredits! -= data.VaultCredits;
|
||||
}
|
||||
|
||||
if (contributions.RegularCredits > techProject.ReqCredits) {
|
||||
contributions.RegularCredits = techProject.ReqCredits;
|
||||
if (data.RegularCredits > techProject.ReqCredits) {
|
||||
data.RegularCredits = techProject.ReqCredits;
|
||||
}
|
||||
techProject.ReqCredits -= contributions.RegularCredits;
|
||||
techProject.ReqCredits -= data.RegularCredits;
|
||||
|
||||
if (contributions.VaultMiscItems.length) {
|
||||
for (const miscItem of contributions.VaultMiscItems) {
|
||||
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) {
|
||||
@ -121,7 +212,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
|
||||
const miscItemChanges = [];
|
||||
for (const miscItem of contributions.MiscItems) {
|
||||
for (const miscItem of data.MiscItems) {
|
||||
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
|
||||
if (reqItem) {
|
||||
if (miscItem.ItemCount > reqItem.ItemCount) {
|
||||
@ -132,34 +223,34 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
||||
ItemType: miscItem.ItemType,
|
||||
ItemCount: miscItem.ItemCount * -1
|
||||
});
|
||||
|
||||
addGuildMemberMiscItemContribution(guildMember, miscItem);
|
||||
}
|
||||
}
|
||||
addMiscItems(inventory, miscItemChanges);
|
||||
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, contributions.RegularCredits, false);
|
||||
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, data.RegularCredits, false);
|
||||
inventoryChanges.MiscItems = miscItemChanges;
|
||||
|
||||
if (techProject.ReqCredits == 0 && !techProject.ReqItems.find(x => x.ItemCount > 0)) {
|
||||
// This research is now fully funded.
|
||||
const recipe = ExportDojoRecipes.research[data.RecipeType];
|
||||
processFundedProject(guild, techProject, recipe);
|
||||
if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
|
||||
guild.ActiveDojoColorResearch = "";
|
||||
await removePigmentsFromGuildMembers(guild._id);
|
||||
}
|
||||
}
|
||||
// Check if research is fully funded now.
|
||||
await processGuildTechProjectContributionsUpdate(guild, techProject);
|
||||
|
||||
await guild.save();
|
||||
await inventory.save();
|
||||
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))) {
|
||||
const purchase = data as IGuildTechBuyRequest;
|
||||
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;
|
||||
}
|
||||
const purchase = data as IGuildTechBuyRequest;
|
||||
const quantity = parseInt(data.Action.split(",")[1]);
|
||||
const recipeChanges = [
|
||||
{
|
||||
@ -181,7 +272,15 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
||||
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;
|
||||
@ -193,11 +292,12 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
||||
ItemCount: x.ItemCount * -1
|
||||
}));
|
||||
addMiscItems(inventory, inventoryChanges.MiscItems);
|
||||
combineInventoryChanges(inventoryChanges, (await addItem(inventory, recipe.resultType)).InventoryChanges);
|
||||
combineInventoryChanges(inventoryChanges, await addItem(inventory, recipe.resultType));
|
||||
await inventory.save();
|
||||
// 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;
|
||||
@ -209,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;
|
||||
@ -218,46 +319,67 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
||||
guild.ActiveDojoColorResearch = data.RecipeType;
|
||||
await guild.save();
|
||||
res.end();
|
||||
} else {
|
||||
throw new Error(`unknown guildTech action: ${data.Action}`);
|
||||
}
|
||||
};
|
||||
} 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 processFundedProject = (
|
||||
guild: TGuildDatabaseDocument,
|
||||
techProject: ITechProjectDatabase,
|
||||
recipe: IDojoResearch
|
||||
): void => {
|
||||
techProject.State = 1;
|
||||
techProject.CompletionDate = new Date(Date.now() + (config.noDojoResearchTime ? 0 : recipe.time) * 1000);
|
||||
if (recipe.guildXpValue) {
|
||||
guild.XP += recipe.guildXpValue;
|
||||
}
|
||||
setTechLogState(guild, techProject.ItemType, config.noDojoResearchTime ? 4 : 3, techProject.CompletionDate);
|
||||
};
|
||||
|
||||
const setTechLogState = (
|
||||
guild: TGuildDatabaseDocument,
|
||||
type: string,
|
||||
state: number,
|
||||
dateTime: Date | undefined = undefined
|
||||
): boolean => {
|
||||
guild.TechChanges ??= [];
|
||||
const entry = guild.TechChanges.find(x => x.details == type);
|
||||
if (entry) {
|
||||
if (entry.entryType == state) {
|
||||
return false;
|
||||
}
|
||||
entry.dateTime = dateTime;
|
||||
entry.entryType = state;
|
||||
} else {
|
||||
guild.TechChanges.push({
|
||||
dateTime: dateTime,
|
||||
entryType: state,
|
||||
details: type
|
||||
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
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
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(`unhandled guildTech request`);
|
||||
}
|
||||
};
|
||||
|
||||
type TGuildTechRequest =
|
||||
@ -266,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;
|
||||
};
|
||||
|
45
src/controllers/api/hubBlessingController.ts
Normal file
45
src/controllers/api/hubBlessingController.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { addBooster, getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getRandomInt } from "@/src/services/rngService";
|
||||
import { RequestHandler } from "express";
|
||||
import { ExportBoosters } from "warframe-public-export-plus";
|
||||
|
||||
export const hubBlessingController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const data = getJSONfromString<IHubBlessingRequest>(String(req.body));
|
||||
const boosterType = ExportBoosters[data.booster].typeName;
|
||||
if (req.query.mode == "send") {
|
||||
const inventory = await getInventory(accountId, "BlessingCooldown Boosters");
|
||||
inventory.BlessingCooldown = new Date(Date.now() + 86400000);
|
||||
addBooster(boosterType, 3 * 3600, inventory);
|
||||
await inventory.save();
|
||||
|
||||
let token = "";
|
||||
for (let i = 0; i != 32; ++i) {
|
||||
token += getRandomInt(0, 15).toString(16);
|
||||
}
|
||||
|
||||
res.json({
|
||||
BlessingCooldown: inventory.BlessingCooldown,
|
||||
SendTime: Math.trunc(Date.now() / 1000).toString(),
|
||||
Token: token
|
||||
});
|
||||
} else {
|
||||
const inventory = await getInventory(accountId, "Boosters");
|
||||
addBooster(boosterType, 3 * 3600, inventory);
|
||||
await inventory.save();
|
||||
|
||||
res.json({
|
||||
BoosterType: data.booster,
|
||||
Sender: data.senderId
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
interface IHubBlessingRequest {
|
||||
booster: string;
|
||||
senderId?: string; // mode=request
|
||||
sendTime?: string; // mode=request
|
||||
token?: string; // mode=request
|
||||
}
|
@ -1,21 +1,25 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { Inbox } from "@/src/models/inboxModel";
|
||||
import {
|
||||
createMessage,
|
||||
createNewEventMessages,
|
||||
deleteAllMessagesRead,
|
||||
deleteMessageRead,
|
||||
getAllMessagesSorted,
|
||||
getMessage
|
||||
} from "@/src/services/inboxService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { addItems, getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountForRequest, getAccountFromSuffixedName, getSuffixedName } from "@/src/services/loginService";
|
||||
import { addItems, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { ExportGear } from "warframe-public-export-plus";
|
||||
import { ExportFlavour, ExportGear } from "warframe-public-export-plus";
|
||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||
import { fromStoreItem, isStoreItem } from "@/src/services/itemDataService";
|
||||
|
||||
export const inboxController: RequestHandler = async (req, res) => {
|
||||
const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query;
|
||||
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const account = await getAccountForRequest(req);
|
||||
const accountId = account._id.toString();
|
||||
|
||||
if (deleteId) {
|
||||
if (deleteId === "DeleteAllRead") {
|
||||
@ -29,12 +33,12 @@ export const inboxController: RequestHandler = async (req, res) => {
|
||||
} else if (messageId) {
|
||||
const message = await getMessage(messageId as string);
|
||||
message.r = true;
|
||||
const attachmentItems = message.att;
|
||||
const attachmentCountedItems = message.countedAtt;
|
||||
|
||||
if (!attachmentItems && !attachmentCountedItems) {
|
||||
await message.save();
|
||||
|
||||
const attachmentItems = message.attVisualOnly ? undefined : message.att;
|
||||
const attachmentCountedItems = message.attVisualOnly ? undefined : message.countedAtt;
|
||||
|
||||
if (!attachmentItems && !attachmentCountedItems && !message.gifts) {
|
||||
res.status(200).end();
|
||||
return;
|
||||
}
|
||||
@ -45,7 +49,7 @@ export const inboxController: RequestHandler = async (req, res) => {
|
||||
await addItems(
|
||||
inventory,
|
||||
attachmentItems.map(attItem => ({
|
||||
ItemType: attItem,
|
||||
ItemType: isStoreItem(attItem) ? fromStoreItem(attItem) : attItem,
|
||||
ItemCount: attItem in ExportGear ? (ExportGear[attItem].purchaseQuantity ?? 1) : 1
|
||||
})),
|
||||
inventoryChanges
|
||||
@ -54,9 +58,43 @@ export const inboxController: RequestHandler = async (req, res) => {
|
||||
if (attachmentCountedItems) {
|
||||
await addItems(inventory, attachmentCountedItems, inventoryChanges);
|
||||
}
|
||||
if (message.gifts) {
|
||||
const sender = await getAccountFromSuffixedName(message.sndr);
|
||||
const recipientName = getSuffixedName(account);
|
||||
const giftQuantity = message.arg!.find(x => x.Key == "GIFT_QUANTITY")!.Tag as number;
|
||||
for (const gift of message.gifts) {
|
||||
combineInventoryChanges(
|
||||
inventoryChanges,
|
||||
(await handleStoreItemAcquisition(gift.GiftType, inventory, giftQuantity)).InventoryChanges
|
||||
);
|
||||
if (sender) {
|
||||
await createMessage(sender._id, [
|
||||
{
|
||||
sndr: recipientName,
|
||||
msg: "/Lotus/Language/Menu/GiftReceivedConfirmationBody",
|
||||
arg: [
|
||||
{
|
||||
Key: "RECIPIENT_NAME",
|
||||
Tag: recipientName
|
||||
},
|
||||
{
|
||||
Key: "GIFT_TYPE",
|
||||
Tag: gift.GiftType
|
||||
},
|
||||
{
|
||||
Key: "GIFT_QUANTITY",
|
||||
Tag: giftQuantity
|
||||
}
|
||||
],
|
||||
sub: "/Lotus/Language/Menu/GiftReceivedConfirmationSubject",
|
||||
icon: ExportFlavour[inventory.ActiveAvatarImageType].icon,
|
||||
highPriority: true
|
||||
}
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
await inventory.save();
|
||||
await message.save();
|
||||
|
||||
res.json({ InventoryChanges: inventoryChanges });
|
||||
} else if (latestClientMessageId) {
|
||||
await createNewEventMessages(req);
|
||||
|
@ -6,20 +6,21 @@ import { IOid } from "@/src/types/commonTypes";
|
||||
import {
|
||||
IConsumedSuit,
|
||||
IHelminthFoodRecord,
|
||||
IInfestedFoundryClient,
|
||||
IInfestedFoundryDatabase,
|
||||
IInventoryClient,
|
||||
IMiscItem,
|
||||
InventorySlot,
|
||||
ITypeCount
|
||||
InventorySlot
|
||||
} from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { ExportMisc, ExportRecipes } from "warframe-public-export-plus";
|
||||
import { ExportMisc } from "warframe-public-export-plus";
|
||||
import { getRecipe } from "@/src/services/itemDataService";
|
||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { colorToShard } from "@/src/helpers/shardHelper";
|
||||
import { config } from "@/src/services/configService";
|
||||
import {
|
||||
addInfestedFoundryXP,
|
||||
applyCheatsToInfestedFoundry,
|
||||
handleSubsumeCompletion
|
||||
} from "@/src/services/infestedFoundryService";
|
||||
|
||||
export const infestedFoundryController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
@ -28,7 +29,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
||||
// shard installation
|
||||
const request = getJSONfromString<IShardInstallRequest>(String(req.body));
|
||||
const inventory = await getInventory(accountId);
|
||||
const suit = inventory.Suits.find(suit => suit._id.toString() == request.SuitId.$oid)!;
|
||||
const suit = inventory.Suits.id(request.SuitId.$oid)!;
|
||||
if (!suit.ArchonCrystalUpgrades || suit.ArchonCrystalUpgrades.length != 5) {
|
||||
suit.ArchonCrystalUpgrades = [{}, {}, {}, {}, {}];
|
||||
}
|
||||
@ -56,7 +57,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
||||
// shard removal
|
||||
const request = getJSONfromString<IShardUninstallRequest>(String(req.body));
|
||||
const inventory = await getInventory(accountId);
|
||||
const suit = inventory.Suits.find(suit => suit._id.toString() == request.SuitId.$oid)!;
|
||||
const suit = inventory.Suits.id(request.SuitId.$oid)!;
|
||||
|
||||
const miscItemChanges: IMiscItem[] = [];
|
||||
if (suit.ArchonCrystalUpgrades![request.Slot].Color) {
|
||||
@ -355,6 +356,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
|
||||
default:
|
||||
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
||||
throw new Error(`unhandled infestedFoundry mode: ${String(req.query.mode)}`);
|
||||
}
|
||||
};
|
||||
@ -382,116 +384,11 @@ interface IHelminthFeedRequest {
|
||||
}[];
|
||||
}
|
||||
|
||||
export const addInfestedFoundryXP = (infestedFoundry: IInfestedFoundryDatabase, delta: number): ITypeCount[] => {
|
||||
const recipeChanges: ITypeCount[] = [];
|
||||
infestedFoundry.XP ??= 0;
|
||||
const prevXP = infestedFoundry.XP;
|
||||
infestedFoundry.XP += delta;
|
||||
if (prevXP < 2250_00 && infestedFoundry.XP >= 2250_00) {
|
||||
infestedFoundry.Slots ??= 0;
|
||||
infestedFoundry.Slots += 3;
|
||||
}
|
||||
if (prevXP < 5625_00 && infestedFoundry.XP >= 5625_00) {
|
||||
recipeChanges.push({
|
||||
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthShieldsBlueprint",
|
||||
ItemCount: 1
|
||||
});
|
||||
}
|
||||
if (prevXP < 10125_00 && infestedFoundry.XP >= 10125_00) {
|
||||
recipeChanges.push({ ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthHackBlueprint", ItemCount: 1 });
|
||||
}
|
||||
if (prevXP < 15750_00 && infestedFoundry.XP >= 15750_00) {
|
||||
infestedFoundry.Slots ??= 0;
|
||||
infestedFoundry.Slots += 10;
|
||||
}
|
||||
if (prevXP < 22500_00 && infestedFoundry.XP >= 22500_00) {
|
||||
recipeChanges.push({
|
||||
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthAmmoEfficiencyBlueprint",
|
||||
ItemCount: 1
|
||||
});
|
||||
}
|
||||
if (prevXP < 30375_00 && infestedFoundry.XP >= 30375_00) {
|
||||
recipeChanges.push({ ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthStunBlueprint", ItemCount: 1 });
|
||||
}
|
||||
if (prevXP < 39375_00 && infestedFoundry.XP >= 39375_00) {
|
||||
infestedFoundry.Slots ??= 0;
|
||||
infestedFoundry.Slots += 20;
|
||||
}
|
||||
if (prevXP < 60750_00 && infestedFoundry.XP >= 60750_00) {
|
||||
recipeChanges.push({ ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthStatusBlueprint", ItemCount: 1 });
|
||||
}
|
||||
if (prevXP < 73125_00 && infestedFoundry.XP >= 73125_00) {
|
||||
infestedFoundry.Slots = 1;
|
||||
}
|
||||
if (prevXP < 86625_00 && infestedFoundry.XP >= 86625_00) {
|
||||
recipeChanges.push({
|
||||
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthShieldArmorBlueprint",
|
||||
ItemCount: 1
|
||||
});
|
||||
}
|
||||
if (prevXP < 101250_00 && infestedFoundry.XP >= 101250_00) {
|
||||
recipeChanges.push({
|
||||
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthProcBlockBlueprint",
|
||||
ItemCount: 1
|
||||
});
|
||||
}
|
||||
if (prevXP < 117000_00 && infestedFoundry.XP >= 117000_00) {
|
||||
recipeChanges.push({
|
||||
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthEnergyShareBlueprint",
|
||||
ItemCount: 1
|
||||
});
|
||||
}
|
||||
if (prevXP < 133875_00 && infestedFoundry.XP >= 133875_00) {
|
||||
recipeChanges.push({
|
||||
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthMaxStatusBlueprint",
|
||||
ItemCount: 1
|
||||
});
|
||||
}
|
||||
if (prevXP < 151875_00 && infestedFoundry.XP >= 151875_00) {
|
||||
recipeChanges.push({
|
||||
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthTreasureBlueprint",
|
||||
ItemCount: 1
|
||||
});
|
||||
}
|
||||
return recipeChanges;
|
||||
};
|
||||
|
||||
interface IHelminthSubsumeRequest {
|
||||
SuitId: IOid;
|
||||
Recipe: string;
|
||||
}
|
||||
|
||||
export const handleSubsumeCompletion = (inventory: TInventoryDatabaseDocument): ITypeCount[] => {
|
||||
const [recipeType] = Object.entries(ExportRecipes).find(
|
||||
([_recipeType, recipe]) =>
|
||||
recipe.secretIngredientAction == "SIA_WARFRAME_ABILITY" &&
|
||||
recipe.secretIngredients![0].ItemType == inventory.InfestedFoundry!.LastConsumedSuit!.ItemType
|
||||
)!;
|
||||
inventory.InfestedFoundry!.LastConsumedSuit = undefined;
|
||||
inventory.InfestedFoundry!.AbilityOverrideUnlockCooldown = undefined;
|
||||
const recipeChanges: ITypeCount[] = [
|
||||
{
|
||||
ItemType: recipeType,
|
||||
ItemCount: 1
|
||||
}
|
||||
];
|
||||
addRecipes(inventory, recipeChanges);
|
||||
return recipeChanges;
|
||||
};
|
||||
|
||||
export const applyCheatsToInfestedFoundry = (infestedFoundry: IInfestedFoundryClient): void => {
|
||||
if (config.infiniteHelminthMaterials) {
|
||||
infestedFoundry.Resources = [
|
||||
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthCalx", Count: 1000 },
|
||||
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthBiotics", Count: 1000 },
|
||||
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthSynthetics", Count: 1000 },
|
||||
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthPheromones", Count: 1000 },
|
||||
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthBile", Count: 1000 },
|
||||
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthOxides", Count: 1000 }
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
interface IHelminthOfferingsUpdate {
|
||||
OfferingsIndex: number;
|
||||
SuitTypes: string[];
|
||||
|
@ -13,9 +13,17 @@ import {
|
||||
ExportResources,
|
||||
ExportVirtuals
|
||||
} from "warframe-public-export-plus";
|
||||
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "./infestedFoundryController";
|
||||
import { addMiscItems, allDailyAffiliationKeys, createLibraryDailyTask } from "@/src/services/inventoryService";
|
||||
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "@/src/services/infestedFoundryService";
|
||||
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";
|
||||
|
||||
export const inventoryController: RequestHandler = async (request, response) => {
|
||||
const accountId = await getAccountIdForRequest(request);
|
||||
@ -33,6 +41,8 @@ export const inventoryController: RequestHandler = async (request, response) =>
|
||||
inventory[key] = 16000 + inventory.PlayerLevel * 500;
|
||||
}
|
||||
inventory.DailyFocus = 250000 + inventory.PlayerLevel * 5000;
|
||||
inventory.GiftsRemaining = Math.max(8, inventory.PlayerLevel);
|
||||
inventory.TradesRemaining = inventory.PlayerLevel;
|
||||
|
||||
inventory.LibraryAvailableDailyTaskInfo = createLibraryDailyTask();
|
||||
|
||||
@ -50,9 +60,11 @@ export const inventoryController: RequestHandler = async (request, response) =>
|
||||
if (numArgonCrystals == 0) {
|
||||
break;
|
||||
}
|
||||
const numStableArgonCrystals =
|
||||
const numStableArgonCrystals = Math.min(
|
||||
numArgonCrystals,
|
||||
inventory.FoundToday?.find(x => x.ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal")
|
||||
?.ItemCount ?? 0;
|
||||
?.ItemCount ?? 0
|
||||
);
|
||||
const numDecayingArgonCrystals = numArgonCrystals - numStableArgonCrystals;
|
||||
const numDecayingArgonCrystalsToRemove = Math.ceil(numDecayingArgonCrystals / 2);
|
||||
logger.debug(`ticking argon crystals for day ${i + 1} of ${daysPassed}`, {
|
||||
@ -74,8 +86,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 (
|
||||
@ -84,9 +98,20 @@ export const inventoryController: RequestHandler = async (request, response) =>
|
||||
new Date() >= inventory.InfestedFoundry.AbilityOverrideUnlockCooldown
|
||||
) {
|
||||
handleSubsumeCompletion(inventory);
|
||||
await inventory.save();
|
||||
//await inventory.save();
|
||||
}
|
||||
|
||||
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`);
|
||||
inventory.DuviriInfo.Seed = generateRewardSeed();
|
||||
}
|
||||
}
|
||||
inventory.LastInventorySync = new Types.ObjectId();
|
||||
await inventory.save();
|
||||
|
||||
response.json(await getInventoryResponse(inventory, "xpBasedLevelCapDisabled" in request.query));
|
||||
};
|
||||
|
||||
@ -144,7 +169,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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -197,7 +222,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
|
||||
@ -252,12 +278,16 @@ 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;
|
||||
@ -265,7 +295,7 @@ export const getInventoryResponse = async (
|
||||
return inventoryResponse;
|
||||
};
|
||||
|
||||
export const addString = (arr: string[], str: string): void => {
|
||||
const addString = (arr: string[], str: string): void => {
|
||||
if (!arr.find(x => x == str)) {
|
||||
arr.push(str);
|
||||
}
|
||||
@ -295,13 +325,3 @@ const resourceGetParent = (resourceName: string): string | undefined => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
return ExportVirtuals[resourceName]?.parentName;
|
||||
};
|
||||
|
||||
// This is FNV1a-32 except operating under modulus 2^31 because JavaScript is stinky and likes producing negative integers out of nowhere.
|
||||
const catBreadHash = (name: string): number => {
|
||||
let hash = 2166136261;
|
||||
for (let i = 0; i != name.length; ++i) {
|
||||
hash = (hash ^ name.charCodeAt(i)) & 0x7fffffff;
|
||||
hash = (hash * 16777619) & 0x7fffffff;
|
||||
}
|
||||
return hash;
|
||||
};
|
||||
|
@ -21,10 +21,14 @@ 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;
|
||||
let name = nameFromEmail || loginRequest.email.substring(1) || "SpaceNinja";
|
||||
if (await isNameTaken(name)) {
|
||||
let suffix = 0;
|
||||
do {
|
||||
@ -37,13 +41,12 @@ export const loginController: RequestHandler = async (request, response) => {
|
||||
password: loginRequest.password,
|
||||
DisplayName: name,
|
||||
CountryCode: loginRequest.lang.toUpperCase(),
|
||||
ClientType: loginRequest.ClientType,
|
||||
ClientType: loginRequest.ClientType == "webui-register" ? "webui" : loginRequest.ClientType,
|
||||
CrossPlatformAllowed: true,
|
||||
ForceLogoutVersion: 0,
|
||||
ConsentNeeded: false,
|
||||
TrackedSettings: [],
|
||||
Nonce: nonce,
|
||||
LatestEventMessageDate: new Date(0)
|
||||
Nonce: nonce
|
||||
});
|
||||
logger.debug("created new account");
|
||||
response.json(createLoginResponse(myAddress, newAccount, buildLabel));
|
||||
@ -55,8 +58,17 @@ export const loginController: RequestHandler = async (request, response) => {
|
||||
}
|
||||
}
|
||||
|
||||
//email not found or incorrect password
|
||||
if (!account || !isCorrectPassword(loginRequest.password, account.password)) {
|
||||
if (!account) {
|
||||
response.status(400).json({ error: "unknown user" });
|
||||
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;
|
||||
}
|
||||
|
@ -1,8 +1,55 @@
|
||||
import { RequestHandler } from "express";
|
||||
import loginRewards from "@/static/fixed_responses/loginRewards.json";
|
||||
import { getAccountForRequest } from "@/src/services/loginService";
|
||||
import {
|
||||
claimLoginReward,
|
||||
getRandomLoginRewards,
|
||||
ILoginRewardsReponse,
|
||||
isLoginRewardAChoice,
|
||||
setAccountGotLoginRewardToday
|
||||
} from "@/src/services/loginRewardService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
|
||||
const loginRewardsController: RequestHandler = (_req, res) => {
|
||||
res.json(loginRewards);
|
||||
export const loginRewardsController: RequestHandler = async (req, res) => {
|
||||
const account = await getAccountForRequest(req);
|
||||
const today = Math.trunc(Date.now() / 86400000) * 86400;
|
||||
const isMilestoneDay = account.LoginDays == 5 || account.LoginDays % 50 == 0;
|
||||
const nextMilestoneDay = account.LoginDays < 5 ? 5 : (Math.trunc(account.LoginDays / 50) + 1) * 50;
|
||||
|
||||
if (today == account.LastLoginRewardDate) {
|
||||
res.json({
|
||||
DailyTributeInfo: {
|
||||
IsMilestoneDay: isMilestoneDay,
|
||||
IsChooseRewardSet: isLoginRewardAChoice(account),
|
||||
LoginDays: account.LoginDays,
|
||||
NextMilestoneReward: "",
|
||||
NextMilestoneDay: nextMilestoneDay
|
||||
}
|
||||
} satisfies ILoginRewardsReponse);
|
||||
return;
|
||||
}
|
||||
|
||||
const inventory = await getInventory(account._id.toString());
|
||||
const randomRewards = getRandomLoginRewards(account, inventory);
|
||||
const response: ILoginRewardsReponse = {
|
||||
DailyTributeInfo: {
|
||||
Rewards: randomRewards,
|
||||
IsMilestoneDay: isMilestoneDay,
|
||||
IsChooseRewardSet: randomRewards.length != 1,
|
||||
LoginDays: account.LoginDays,
|
||||
NextMilestoneReward: "",
|
||||
NextMilestoneDay: nextMilestoneDay,
|
||||
HasChosenReward: false
|
||||
},
|
||||
LastLoginRewardDate: today
|
||||
};
|
||||
if (!isMilestoneDay && randomRewards.length == 1) {
|
||||
response.DailyTributeInfo.HasChosenReward = true;
|
||||
response.DailyTributeInfo.ChosenReward = randomRewards[0];
|
||||
response.DailyTributeInfo.NewInventory = await claimLoginReward(inventory, randomRewards[0]);
|
||||
await inventory.save();
|
||||
|
||||
setAccountGotLoginRewardToday(account);
|
||||
await account.save();
|
||||
}
|
||||
res.json(response);
|
||||
};
|
||||
|
||||
export { loginRewardsController };
|
||||
|
65
src/controllers/api/loginRewardsSelectionController.ts
Normal file
65
src/controllers/api/loginRewardsSelectionController.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import {
|
||||
claimLoginReward,
|
||||
getRandomLoginRewards,
|
||||
setAccountGotLoginRewardToday
|
||||
} from "@/src/services/loginRewardService";
|
||||
import { getAccountForRequest } from "@/src/services/loginService";
|
||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const loginRewardsSelectionController: RequestHandler = async (req, res) => {
|
||||
const account = await getAccountForRequest(req);
|
||||
const inventory = await getInventory(account._id.toString());
|
||||
const body = JSON.parse(String(req.body)) as ILoginRewardsSelectionRequest;
|
||||
const isMilestoneDay = account.LoginDays == 5 || account.LoginDays % 50 == 0;
|
||||
if (body.IsMilestoneReward != isMilestoneDay) {
|
||||
logger.warn(`Client disagrees on login milestone (got ${body.IsMilestoneReward}, expected ${isMilestoneDay})`);
|
||||
}
|
||||
let chosenReward;
|
||||
let inventoryChanges: IInventoryChanges;
|
||||
if (body.IsMilestoneReward) {
|
||||
chosenReward = {
|
||||
RewardType: "RT_STORE_ITEM",
|
||||
StoreItemType: body.ChosenReward
|
||||
};
|
||||
inventoryChanges = (await handleStoreItemAcquisition(body.ChosenReward, inventory)).InventoryChanges;
|
||||
if (!evergreenRewards.find(x => x == body.ChosenReward)) {
|
||||
inventory.LoginMilestoneRewards.push(body.ChosenReward);
|
||||
}
|
||||
} else {
|
||||
const randomRewards = getRandomLoginRewards(account, inventory);
|
||||
chosenReward = randomRewards.find(x => x.StoreItemType == body.ChosenReward)!;
|
||||
inventoryChanges = await claimLoginReward(inventory, chosenReward);
|
||||
}
|
||||
await inventory.save();
|
||||
|
||||
setAccountGotLoginRewardToday(account);
|
||||
await account.save();
|
||||
|
||||
res.json({
|
||||
DailyTributeInfo: {
|
||||
NewInventory: inventoryChanges,
|
||||
ChosenReward: chosenReward
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
interface ILoginRewardsSelectionRequest {
|
||||
ChosenReward: string;
|
||||
IsMilestoneReward: boolean;
|
||||
}
|
||||
|
||||
const evergreenRewards = [
|
||||
"/Lotus/Types/StoreItems/Packages/EvergreenTripleForma",
|
||||
"/Lotus/Types/StoreItems/Packages/EvergreenTripleRifleRiven",
|
||||
"/Lotus/Types/StoreItems/Packages/EvergreenTripleMeleeRiven",
|
||||
"/Lotus/Types/StoreItems/Packages/EvergreenTripleSecondaryRiven",
|
||||
"/Lotus/Types/StoreItems/Packages/EvergreenWeaponSlots",
|
||||
"/Lotus/Types/StoreItems/Packages/EvergreenKuva",
|
||||
"/Lotus/Types/StoreItems/Packages/EvergreenBoosters",
|
||||
"/Lotus/Types/StoreItems/Packages/EvergreenEndo",
|
||||
"/Lotus/Types/StoreItems/Packages/EvergreenExilus"
|
||||
];
|
@ -1,19 +1,28 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { Account } from "@/src/models/loginModel";
|
||||
|
||||
const logoutController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const account = await Account.findOne({ _id: accountId });
|
||||
if (account) {
|
||||
account.Nonce = 0;
|
||||
await account.save();
|
||||
export const logoutController: RequestHandler = async (req, res) => {
|
||||
if (!req.query.accountId) {
|
||||
throw new Error("Request is missing accountId parameter");
|
||||
}
|
||||
const nonce: number = parseInt(req.query.nonce as string);
|
||||
if (!nonce) {
|
||||
throw new Error("Request is missing nonce parameter");
|
||||
}
|
||||
|
||||
await Account.updateOne(
|
||||
{
|
||||
_id: req.query.accountId,
|
||||
Nonce: nonce
|
||||
},
|
||||
{
|
||||
Nonce: 0
|
||||
}
|
||||
);
|
||||
|
||||
res.writeHead(200, {
|
||||
"Content-Type": "text/html",
|
||||
"Content-Length": 1
|
||||
});
|
||||
res.end("1");
|
||||
};
|
||||
|
||||
export { logoutController };
|
||||
|
27
src/controllers/api/maturePetController.ts
Normal file
27
src/controllers/api/maturePetController.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const maturePetController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "KubrowPets");
|
||||
const data = getJSONfromString<IMaturePetRequest>(String(req.body));
|
||||
const details = inventory.KubrowPets.id(data.petId)!.Details!;
|
||||
details.IsPuppy = data.revert;
|
||||
await inventory.save();
|
||||
res.json({
|
||||
petId: data.petId,
|
||||
updateCollar: true,
|
||||
armorSkins: ["", "", ""],
|
||||
furPatterns: data.revert
|
||||
? ["", "", ""]
|
||||
: [details.DominantTraits.FurPattern, details.DominantTraits.FurPattern, details.DominantTraits.FurPattern],
|
||||
unmature: data.revert
|
||||
});
|
||||
};
|
||||
|
||||
interface IMaturePetRequest {
|
||||
petId: string;
|
||||
revert: boolean;
|
||||
}
|
@ -3,9 +3,10 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getAccountIdForRequest } 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 ****
|
||||
@ -47,16 +48,22 @@ import { logger } from "@/src/utils/logger";
|
||||
- [ ] FpsSamples
|
||||
*/
|
||||
//move credit calc in here, return MissionRewards: [] if no reward info
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
export const missionInventoryUpdateController: RequestHandler = async (req, res): Promise<void> => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const missionReport = getJSONfromString<IMissionInventoryUpdateRequest>((req.body as string).toString());
|
||||
logger.debug("mission report:", missionReport);
|
||||
|
||||
const inventory = await getInventory(accountId);
|
||||
const firstCompletion = missionReport.SortieId
|
||||
? inventory.CompletedSorties.indexOf(missionReport.SortieId) == -1
|
||||
: false;
|
||||
const inventoryUpdates = await addMissionInventoryUpdates(inventory, missionReport);
|
||||
|
||||
if (missionReport.MissionStatus !== "GS_SUCCESS") {
|
||||
if (
|
||||
missionReport.MissionStatus !== "GS_SUCCESS" &&
|
||||
!(missionReport.RewardInfo?.jobId || missionReport.RewardInfo?.challengeMissionId)
|
||||
) {
|
||||
inventory.RewardSeed = generateRewardSeed();
|
||||
await inventory.save();
|
||||
const inventoryResponse = await getInventoryResponse(inventory, true);
|
||||
res.json({
|
||||
@ -66,8 +73,16 @@ 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);
|
||||
|
||||
inventory.RewardSeed = generateRewardSeed();
|
||||
await inventory.save();
|
||||
const inventoryResponse = await getInventoryResponse(inventory, true);
|
||||
|
||||
@ -78,8 +93,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);
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -8,15 +8,22 @@ import {
|
||||
addMiscItems,
|
||||
applyDefaultUpgrades,
|
||||
occupySlot,
|
||||
productCategoryToInventoryBin
|
||||
productCategoryToInventoryBin,
|
||||
combineInventoryChanges,
|
||||
addSpecialItem
|
||||
} from "@/src/services/inventoryService";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
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, 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) => {
|
||||
@ -28,34 +35,158 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
|
||||
const category = modularWeaponTypes[data.WeaponType];
|
||||
const inventory = await getInventory(accountId);
|
||||
|
||||
const defaultUpgrades = getDefaultUpgrades(data.Parts);
|
||||
const configs = applyDefaultUpgrades(inventory, defaultUpgrades);
|
||||
const inventoryChanges: IInventoryChanges = {
|
||||
...addEquipment(inventory, category, data.WeaponType, data.Parts, {}, { Configs: configs }),
|
||||
...occupySlot(inventory, productCategoryToInventoryBin(category)!, false)
|
||||
let defaultUpgrades: IDefaultUpgrade[] | undefined;
|
||||
const defaultOverwrites: Partial<IEquipmentDatabase> = {
|
||||
ModularParts: data.Parts
|
||||
};
|
||||
const inventoryChanges: IInventoryChanges = {};
|
||||
if (category == "KubrowPets") {
|
||||
const traits = {
|
||||
"/Lotus/Types/Friendly/Pets/CreaturePets/ArmoredInfestedCatbrowPetPowerSuit": {
|
||||
BaseColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorRareBase",
|
||||
SecondaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorRareSecondary",
|
||||
TertiaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorRareTertiary",
|
||||
AccentColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorRareAccent",
|
||||
EyeColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorRareEyes",
|
||||
FurPattern: "/Lotus/Types/Game/InfestedKavatPet/Patterns/InfestedCritterPatternDefault",
|
||||
Personality: data.WeaponType,
|
||||
BodyType: "/Lotus/Types/Game/CatbrowPet/BodyTypes/InfestedCatbrowPetRegularBodyType",
|
||||
Head: "/Lotus/Types/Game/InfestedKavatPet/Heads/InfestedCritterHeadC"
|
||||
},
|
||||
"/Lotus/Types/Friendly/Pets/CreaturePets/HornedInfestedCatbrowPetPowerSuit": {
|
||||
BaseColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorUncommonBase",
|
||||
SecondaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorUncommonSecondary",
|
||||
TertiaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorUncommonTertiary",
|
||||
AccentColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorUncommonAccent",
|
||||
EyeColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorUncommonEyes",
|
||||
FurPattern: "/Lotus/Types/Game/InfestedKavatPet/Patterns/InfestedCritterPatternDefault",
|
||||
Personality: data.WeaponType,
|
||||
BodyType: "/Lotus/Types/Game/CatbrowPet/BodyTypes/InfestedCatbrowPetRegularBodyType",
|
||||
Head: "/Lotus/Types/Game/InfestedKavatPet/Heads/InfestedCritterHeadB"
|
||||
},
|
||||
"/Lotus/Types/Friendly/Pets/CreaturePets/MedjayPredatorKubrowPetPowerSuit": {
|
||||
BaseColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorRareBase",
|
||||
SecondaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorRareSecondary",
|
||||
TertiaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorRareTertiary",
|
||||
AccentColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorRareAccent",
|
||||
EyeColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorRareEyes",
|
||||
FurPattern: "/Lotus/Types/Game/InfestedPredatorPet/Patterns/InfestedPredatorPatternDefault",
|
||||
Personality: data.WeaponType,
|
||||
BodyType: "/Lotus/Types/Game/KubrowPet/BodyTypes/InfestedKubrowPetBodyType",
|
||||
Head: "/Lotus/Types/Game/InfestedPredatorPet/Heads/InfestedPredatorHeadA"
|
||||
},
|
||||
"/Lotus/Types/Friendly/Pets/CreaturePets/PharaohPredatorKubrowPetPowerSuit": {
|
||||
BaseColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorUncommonBase",
|
||||
SecondaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorUncommonSecondary",
|
||||
TertiaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorUncommonTertiary",
|
||||
AccentColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorUncommonAccent",
|
||||
EyeColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorUncommonEyes",
|
||||
FurPattern: "/Lotus/Types/Game/InfestedPredatorPet/Patterns/InfestedPredatorPatternDefault",
|
||||
Personality: data.WeaponType,
|
||||
BodyType: "/Lotus/Types/Game/KubrowPet/BodyTypes/InfestedKubrowPetBodyType",
|
||||
Head: "/Lotus/Types/Game/InfestedPredatorPet/Heads/InfestedPredatorHeadB"
|
||||
},
|
||||
"/Lotus/Types/Friendly/Pets/CreaturePets/VizierPredatorKubrowPetPowerSuit": {
|
||||
BaseColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorCommonBase",
|
||||
SecondaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorCommonSecondary",
|
||||
TertiaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorCommonTertiary",
|
||||
AccentColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorCommonAccent",
|
||||
EyeColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorCommonEyes",
|
||||
FurPattern: "/Lotus/Types/Game/InfestedPredatorPet/Patterns/InfestedPredatorPatternDefault",
|
||||
Personality: data.WeaponType,
|
||||
BodyType: "/Lotus/Types/Game/KubrowPet/BodyTypes/InfestedKubrowPetBodyType",
|
||||
Head: "/Lotus/Types/Game/InfestedPredatorPet/Heads/InfestedPredatorHeadC"
|
||||
},
|
||||
"/Lotus/Types/Friendly/Pets/CreaturePets/VulpineInfestedCatbrowPetPowerSuit": {
|
||||
BaseColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorCommonBase",
|
||||
SecondaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorCommonSecondary",
|
||||
TertiaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorCommonTertiary",
|
||||
AccentColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorCommonAccent",
|
||||
EyeColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorCommonEyes",
|
||||
FurPattern: "/Lotus/Types/Game/InfestedKavatPet/Patterns/InfestedCritterPatternDefault",
|
||||
Personality: data.WeaponType,
|
||||
BodyType: "/Lotus/Types/Game/CatbrowPet/BodyTypes/InfestedCatbrowPetRegularBodyType",
|
||||
Head: "/Lotus/Types/Game/InfestedKavatPet/Heads/InfestedCritterHeadA"
|
||||
}
|
||||
}[data.WeaponType];
|
||||
|
||||
if (!traits) {
|
||||
throw new Error(`unknown KubrowPets type: ${data.WeaponType}`);
|
||||
}
|
||||
|
||||
defaultOverwrites.Details = {
|
||||
Name: "",
|
||||
IsPuppy: false,
|
||||
HasCollar: true,
|
||||
PrintsRemaining: 2,
|
||||
Status: Status.StatusStasis,
|
||||
HatchDate: new Date(Math.trunc(Date.now() / 86400000) * 86400000),
|
||||
IsMale: !!getRandomInt(0, 1),
|
||||
Size: getRandomInt(70, 100) / 100,
|
||||
DominantTraits: traits,
|
||||
RecessiveTraits: traits
|
||||
};
|
||||
|
||||
// Only save mutagen & antigen in the ModularParts.
|
||||
defaultOverwrites.ModularParts = [data.Parts[1], data.Parts[2]];
|
||||
|
||||
const meta = ExportSentinels[data.WeaponType];
|
||||
|
||||
for (const specialItem of meta.exalted!) {
|
||||
addSpecialItem(inventory, specialItem, inventoryChanges);
|
||||
}
|
||||
|
||||
defaultUpgrades = meta.defaultUpgrades;
|
||||
} else {
|
||||
defaultUpgrades = getDefaultUpgrades(data.Parts);
|
||||
}
|
||||
|
||||
if (category == "MoaPets") {
|
||||
const weapon = ExportSentinels[data.WeaponType].defaultWeapon;
|
||||
if (weapon) {
|
||||
const category = ExportWeapons[weapon].productCategory;
|
||||
addEquipment(inventory, category, weapon, undefined, inventoryChanges);
|
||||
combineInventoryChanges(
|
||||
inventoryChanges,
|
||||
occupySlot(inventory, productCategoryToInventoryBin(category)!, !!data.isWebUi)
|
||||
);
|
||||
}
|
||||
}
|
||||
defaultOverwrites.Configs = applyDefaultUpgrades(inventory, defaultUpgrades);
|
||||
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 = [];
|
||||
let currencyChanges = {};
|
||||
if (!data.isWebUi) {
|
||||
for (const part of data.Parts) {
|
||||
miscItemChanges.push({
|
||||
ItemType: part,
|
||||
ItemCount: -1
|
||||
});
|
||||
}
|
||||
const currencyChanges = updateCurrency(
|
||||
currencyChanges = updateCurrency(
|
||||
inventory,
|
||||
category == "Hoverboards" || category == "MoaPets" || category == "LongGuns" || category == "Pistols"
|
||||
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: {
|
||||
|
@ -21,8 +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) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (
|
||||
data.partType &&
|
||||
data.premiumPrice &&
|
||||
!data.excludeFromCodex // exclude pvp variants
|
||||
) {
|
||||
partTypeToParts[data.partType] ??= [];
|
||||
partTypeToParts[data.partType].push(uniqueName);
|
||||
}
|
||||
@ -42,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,
|
||||
{},
|
||||
{
|
||||
...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)
|
||||
};
|
||||
@ -144,14 +141,10 @@ const getModularWeaponSale = (
|
||||
getItemType: (parts: string[]) => string
|
||||
): IModularWeaponSaleInfo => {
|
||||
const rng = new CRng(day);
|
||||
const parts = partTypes.map(partType => rng.randomElement(partTypeToParts[partType]));
|
||||
const parts = partTypes.map(partType => rng.randomElement(partTypeToParts[partType])!);
|
||||
let partsCost = 0;
|
||||
for (const part of parts) {
|
||||
const meta = ExportWeapons[part];
|
||||
if (!meta.premiumPrice) {
|
||||
throw new Error(`no premium price for ${part}`);
|
||||
}
|
||||
partsCost += meta.premiumPrice;
|
||||
partsCost += ExportWeapons[part].premiumPrice!;
|
||||
}
|
||||
return {
|
||||
Name: name,
|
||||
|
@ -12,15 +12,17 @@ export const nameWeaponController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId);
|
||||
const body = getJSONfromString<INameWeaponRequest>(String(req.body));
|
||||
const item = inventory[req.query.Category as string as TEquipmentKey].find(
|
||||
item => item._id.toString() == (req.query.ItemId as string)
|
||||
)!;
|
||||
const item = inventory[req.query.Category as string as TEquipmentKey].id(req.query.ItemId as string)!;
|
||||
if (body.ItemName != "") {
|
||||
item.ItemName = body.ItemName;
|
||||
} else {
|
||||
item.ItemName = undefined;
|
||||
}
|
||||
const currencyChanges = updateCurrency(inventory, "webui" in req.query ? 0 : 15, true);
|
||||
const currencyChanges = updateCurrency(
|
||||
inventory,
|
||||
req.query.Category == "Horses" || "webui" in req.query ? 0 : 15,
|
||||
true
|
||||
);
|
||||
await inventory.save();
|
||||
res.json({
|
||||
InventoryChanges: currencyChanges
|
||||
|
314
src/controllers/api/nemesisController.ts
Normal file
314
src/controllers/api/nemesisController.ts
Normal file
@ -0,0 +1,314 @@
|
||||
import {
|
||||
consumeModCharge,
|
||||
encodeNemesisGuess,
|
||||
getInfNodes,
|
||||
getKnifeUpgrade,
|
||||
getNemesisPasscode,
|
||||
getNemesisPasscodeModTypes,
|
||||
getWeaponsForManifest,
|
||||
IKnifeResponse,
|
||||
showdownNodes
|
||||
} 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 { SRng } from "@/src/services/rngService";
|
||||
import { IMongoDate, IOid } from "@/src/types/commonTypes";
|
||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import {
|
||||
IInnateDamageFingerprint,
|
||||
IInventoryClient,
|
||||
INemesisClient,
|
||||
InventorySlot,
|
||||
IUpgradeClient,
|
||||
IWeaponSkinClient,
|
||||
LoadoutIndex,
|
||||
TEquipmentKey
|
||||
} 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);
|
||||
if ((req.query.mode as string) == "f") {
|
||||
const body = getJSONfromString<IValenceFusionRequest>(String(req.body));
|
||||
const inventory = await getInventory(accountId, 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;
|
||||
const sourceFingerprint = JSON.parse(sourceWeapon.UpgradeFingerprint!) as IInnateDamageFingerprint;
|
||||
|
||||
// Update destination damage type if desired
|
||||
if (body.UseSourceDmgType) {
|
||||
destFingerprint.buffs[0].Tag = sourceFingerprint.buffs[0].Tag;
|
||||
}
|
||||
|
||||
// Upgrade destination damage value
|
||||
const destDamage = 0.25 + (destFingerprint.buffs[0].Value / 0x3fffffff) * (0.6 - 0.25);
|
||||
const sourceDamage = 0.25 + (sourceFingerprint.buffs[0].Value / 0x3fffffff) * (0.6 - 0.25);
|
||||
let newDamage = Math.max(destDamage, sourceDamage) * 1.1;
|
||||
if (newDamage >= 0.5794998) {
|
||||
newDamage = 0.6;
|
||||
}
|
||||
destFingerprint.buffs[0].Value = Math.trunc(((newDamage - 0.25) / (0.6 - 0.25)) * 0x3fffffff);
|
||||
|
||||
// Commit fingerprint
|
||||
destWeapon.UpgradeFingerprint = JSON.stringify(destFingerprint);
|
||||
|
||||
// Remove source weapon
|
||||
inventory[body.Category].pull({ _id: body.SourceWeapon.$oid });
|
||||
freeUpSlot(inventory, InventorySlot.WEAPONS);
|
||||
|
||||
await inventory.save();
|
||||
res.json({
|
||||
InventoryChanges: {
|
||||
[body.Category]: [destWeapon.toJSON()],
|
||||
RemovedIdItems: [{ ItemId: body.SourceWeapon }]
|
||||
}
|
||||
});
|
||||
} else if ((req.query.mode as string) == "p") {
|
||||
const inventory = await getInventory(accountId, "Nemesis");
|
||||
const body = getJSONfromString<INemesisPrespawnCheckRequest>(String(req.body));
|
||||
const passcode = getNemesisPasscode(inventory.Nemesis!);
|
||||
let guessResult = 0;
|
||||
if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
|
||||
for (let i = 0; i != 3; ++i) {
|
||||
if (body.guess[i] == passcode[0]) {
|
||||
guessResult = 1 + i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i != 3; ++i) {
|
||||
if (body.guess[i] == passcode[i]) {
|
||||
++guessResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
res.json({ GuessResult: guessResult });
|
||||
} else if (req.query.mode == "r") {
|
||||
const inventory = await getInventory(
|
||||
accountId,
|
||||
"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("FC_INFESTATION", 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(inventory.Nemesis!.Faction, 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(accountId, "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 body = getJSONfromString<INemesisStartRequest>(String(req.body));
|
||||
body.target.fp = BigInt(body.target.fp);
|
||||
|
||||
let weaponIdx = -1;
|
||||
if (body.target.Faction != "FC_INFESTATION") {
|
||||
const weapons = getWeaponsForManifest(body.target.manifest);
|
||||
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)) {
|
||||
break;
|
||||
}
|
||||
weaponIdx = (weaponIdx + 1) % weapons.length;
|
||||
} while (weaponIdx != initialWeaponIdx);
|
||||
}
|
||||
|
||||
inventory.Nemesis = {
|
||||
fp: body.target.fp,
|
||||
manifest: body.target.manifest,
|
||||
KillingSuit: body.target.KillingSuit,
|
||||
killingDamageType: body.target.killingDamageType,
|
||||
ShoulderHelmet: body.target.ShoulderHelmet,
|
||||
WeaponIdx: weaponIdx,
|
||||
AgentIdx: body.target.AgentIdx,
|
||||
BirthNode: body.target.BirthNode,
|
||||
Faction: body.target.Faction,
|
||||
Rank: 0,
|
||||
k: false,
|
||||
Traded: false,
|
||||
d: new Date(),
|
||||
InfNodes: getInfNodes(body.target.Faction, 0),
|
||||
GuessHistory: [],
|
||||
Hints: [],
|
||||
HintProgress: 0,
|
||||
Weakened: body.target.Weakened,
|
||||
PrevOwners: 0,
|
||||
HenchmenKilled: 0,
|
||||
SecondInCommand: body.target.SecondInCommand,
|
||||
MissionCount: 0,
|
||||
LastEnc: 0
|
||||
};
|
||||
await inventory.save();
|
||||
|
||||
res.json({
|
||||
target: inventory.toJSON().Nemesis
|
||||
});
|
||||
} else if ((req.query.mode as string) == "w") {
|
||||
const inventory = await getInventory(
|
||||
accountId,
|
||||
"Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades"
|
||||
);
|
||||
//const body = getJSONfromString<INemesisWeakenRequest>(String(req.body));
|
||||
|
||||
inventory.Nemesis!.InfNodes = [
|
||||
{
|
||||
Node: showdownNodes[inventory.Nemesis!.Faction],
|
||||
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)}`);
|
||||
}
|
||||
};
|
||||
|
||||
interface IValenceFusionRequest {
|
||||
DestWeapon: IOid;
|
||||
SourceWeapon: IOid;
|
||||
Category: TEquipmentKey;
|
||||
UseSourceDmgType: boolean;
|
||||
}
|
||||
|
||||
interface INemesisStartRequest {
|
||||
target: {
|
||||
fp: number | bigint;
|
||||
manifest: string;
|
||||
KillingSuit: string;
|
||||
killingDamageType: number;
|
||||
ShoulderHelmet: string;
|
||||
DisallowedWeapons: string[];
|
||||
WeaponIdx: number;
|
||||
AgentIdx: number;
|
||||
BirthNode: string;
|
||||
Faction: string;
|
||||
Rank: number;
|
||||
k: boolean;
|
||||
Traded: boolean;
|
||||
d: IMongoDate;
|
||||
InfNodes: [];
|
||||
GuessHistory: [];
|
||||
Hints: [];
|
||||
HintProgress: number;
|
||||
Weakened: boolean;
|
||||
PrevOwners: number;
|
||||
HenchmenKilled: number;
|
||||
MissionCount?: number; // Added in 38.5.0
|
||||
LastEnc?: number; // Added in 38.5.0
|
||||
SecondInCommand: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface INemesisPrespawnCheckRequest {
|
||||
guess: number[]; // .length == 3
|
||||
potency?: number[];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// interface INemesisWeakenRequest {
|
||||
// target: INemesisClient;
|
||||
// knife: IKnife;
|
||||
// }
|
||||
|
||||
interface IKnife {
|
||||
Item: IEquipmentClient;
|
||||
Skins: IWeaponSkinClient[];
|
||||
ModSlot: number;
|
||||
CustSlot: number;
|
||||
AttachedUpgrades: IUpgradeClient[];
|
||||
HiddenWhenHolstered: boolean;
|
||||
}
|
@ -1,10 +1,19 @@
|
||||
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
|
||||
import {
|
||||
getDojoClient,
|
||||
getGuildForRequestEx,
|
||||
getVaultMiscItemCount,
|
||||
hasAccessToDojo,
|
||||
hasGuildPermission,
|
||||
processDojoBuildMaterialsGathered,
|
||||
scaleRequiredCount
|
||||
} from "@/src/services/guildService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { GuildPermission } from "@/src/types/guildTypes";
|
||||
import { RequestHandler } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
|
||||
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);
|
||||
@ -24,18 +33,77 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
|
||||
}
|
||||
|
||||
component.Decos ??= [];
|
||||
if (request.MoveId) {
|
||||
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[
|
||||
component.Decos.push({
|
||||
_id: new Types.ObjectId(),
|
||||
Type: request.Type,
|
||||
Pos: request.Pos,
|
||||
Rot: request.Rot,
|
||||
Name: request.Name
|
||||
});
|
||||
|
||||
Scale: request.Scale,
|
||||
Name: request.Name,
|
||||
Sockets: request.Sockets
|
||||
}) - 1
|
||||
];
|
||||
const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == request.Type);
|
||||
if (meta && meta.capacityCost) {
|
||||
if (meta) {
|
||||
if (meta.capacityCost) {
|
||||
component.DecoCapacity -= meta.capacityCost;
|
||||
}
|
||||
} else {
|
||||
const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)![0];
|
||||
if (deco.Sockets !== undefined) {
|
||||
guild.VaultFusionTreasures!.find(x => x.ItemType == itemType && x.Sockets == deco.Sockets)!.ItemCount -=
|
||||
1;
|
||||
} else {
|
||||
guild.VaultShipDecorations!.find(x => x.ItemType == itemType)!.ItemCount -= 1;
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await guild.save();
|
||||
res.json(await getDojoClient(guild, 0, component._id));
|
||||
@ -47,5 +115,10 @@ interface IPlaceDecoInComponentRequest {
|
||||
Type: string;
|
||||
Pos: number[];
|
||||
Rot: number[];
|
||||
Scale?: number;
|
||||
Name?: string;
|
||||
Sockets?: number;
|
||||
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();
|
||||
};
|
@ -6,7 +6,7 @@ import { RequestHandler } from "express";
|
||||
|
||||
export const playerSkillsController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId);
|
||||
const inventory = await getInventory(accountId, "PlayerSkills");
|
||||
const request = getJSONfromString<IPlayerSkillsRequest>(String(req.body));
|
||||
|
||||
const oldRank: number = inventory.PlayerSkills[request.Skill as keyof IPlayerSkills];
|
||||
|
75
src/controllers/api/postGuildAdvertisementController.ts
Normal file
75
src/controllers/api/postGuildAdvertisementController.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { GuildAd, GuildMember } from "@/src/models/guildModel";
|
||||
import {
|
||||
addGuildMemberMiscItemContribution,
|
||||
addVaultMiscItems,
|
||||
getGuildForRequestEx,
|
||||
getVaultMiscItemCount,
|
||||
hasGuildPermissionEx
|
||||
} from "@/src/services/guildService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getVendorManifestByTypeName } from "@/src/services/serversideVendorsService";
|
||||
import { GuildPermission } from "@/src/types/guildTypes";
|
||||
import { IPurchaseParams } from "@/src/types/purchaseTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const postGuildAdvertisementController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "GuildId MiscItems");
|
||||
const guild = await getGuildForRequestEx(req, inventory);
|
||||
const guildMember = (await GuildMember.findOne({ accountId, guildId: guild._id }))!;
|
||||
if (!hasGuildPermissionEx(guild, guildMember, GuildPermission.Advertiser)) {
|
||||
res.status(400).end();
|
||||
return;
|
||||
}
|
||||
const payload = getJSONfromString<IPostGuildAdvertisementRequest>(String(req.body));
|
||||
|
||||
// Handle resource cost
|
||||
const vendor = getVendorManifestByTypeName(
|
||||
"/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest"
|
||||
)!;
|
||||
const offer = vendor.VendorInfo.ItemManifest.find(x => x.StoreItem == payload.PurchaseParams.StoreItem)!;
|
||||
if (getVaultMiscItemCount(guild, offer.ItemPrices![0].ItemType) >= offer.ItemPrices![0].ItemCount) {
|
||||
addVaultMiscItems(guild, [
|
||||
{
|
||||
ItemType: offer.ItemPrices![0].ItemType,
|
||||
ItemCount: offer.ItemPrices![0].ItemCount * -1
|
||||
}
|
||||
]);
|
||||
} else {
|
||||
const miscItem = inventory.MiscItems.find(x => x.ItemType == offer.ItemPrices![0].ItemType);
|
||||
if (!miscItem || miscItem.ItemCount < offer.ItemPrices![0].ItemCount) {
|
||||
res.status(400).json("Insufficient funds");
|
||||
return;
|
||||
}
|
||||
miscItem.ItemCount -= offer.ItemPrices![0].ItemCount;
|
||||
addGuildMemberMiscItemContribution(guildMember, offer.ItemPrices![0]);
|
||||
await guildMember.save();
|
||||
await inventory.save();
|
||||
}
|
||||
|
||||
// Create or update ad
|
||||
await GuildAd.findOneAndUpdate(
|
||||
{ GuildId: guild._id },
|
||||
{
|
||||
Emblem: guild.Emblem,
|
||||
Expiry: new Date(Date.now() + 12 * 3600 * 1000),
|
||||
Features: payload.Features,
|
||||
GuildName: guild.Name,
|
||||
MemberCount: await GuildMember.countDocuments({ guildId: guild._id, status: 0 }),
|
||||
RecruitMsg: payload.RecruitMsg,
|
||||
Tier: guild.Tier
|
||||
},
|
||||
{ upsert: true }
|
||||
);
|
||||
|
||||
res.end();
|
||||
};
|
||||
|
||||
interface IPostGuildAdvertisementRequest {
|
||||
Features: number;
|
||||
RecruitMsg: string;
|
||||
Languages: string[];
|
||||
PurchaseParams: IPurchaseParams;
|
||||
}
|
@ -16,7 +16,7 @@ export const queueDojoComponentDestructionController: RequestHandler = async (re
|
||||
const componentId = req.query.componentId as string;
|
||||
|
||||
guild.DojoComponents.id(componentId)!.DestructionTime = new Date(
|
||||
Date.now() + (config.fastDojoRoomDestruction ? 5_000 : 2 * 3600_000)
|
||||
(Math.trunc(Date.now() / 1000) + (config.fastDojoRoomDestruction ? 5 : 2 * 3600)) * 1000
|
||||
);
|
||||
|
||||
await guild.save();
|
||||
|
34
src/controllers/api/redeemPromoCodeController.ts
Normal file
34
src/controllers/api/redeemPromoCodeController.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { RequestHandler } from "express";
|
||||
import glyphCodes from "@/static/fixed_responses/glyphsCodes.json";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { addItem, getInventory } from "@/src/services/inventoryService";
|
||||
|
||||
export const redeemPromoCodeController: RequestHandler = async (req, res) => {
|
||||
const body = getJSONfromString<IRedeemPromoCodeRequest>(String(req.body));
|
||||
if (!(body.codeId in glyphCodes)) {
|
||||
res.status(400).send("INVALID_CODE").end();
|
||||
return;
|
||||
}
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "FlavourItems");
|
||||
const acquiredGlyphs: string[] = [];
|
||||
for (const glyph of (glyphCodes as Record<string, string[]>)[body.codeId]) {
|
||||
if (!inventory.FlavourItems.find(x => x.ItemType == glyph)) {
|
||||
acquiredGlyphs.push(glyph);
|
||||
await addItem(inventory, glyph);
|
||||
}
|
||||
}
|
||||
if (acquiredGlyphs.length == 0) {
|
||||
res.status(400).send("USED_CODE").end();
|
||||
return;
|
||||
}
|
||||
await inventory.save();
|
||||
res.json({
|
||||
FlavourItems: acquiredGlyphs
|
||||
});
|
||||
};
|
||||
|
||||
interface IRedeemPromoCodeRequest {
|
||||
codeId: string;
|
||||
}
|
27
src/controllers/api/releasePetController.ts
Normal file
27
src/controllers/api/releasePetController.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const releasePetController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "RegularCredits KubrowPets");
|
||||
const payload = getJSONfromString<IReleasePetRequest>(String(req.body));
|
||||
|
||||
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 });
|
||||
|
||||
await inventory.save();
|
||||
res.json({ inventoryChanges }); // Not a mistake; it's "inventoryChanges" here.
|
||||
};
|
||||
|
||||
interface IReleasePetRequest {
|
||||
recipeName: "/Lotus/Types/Game/KubrowPet/ReleasePetRecipe" | "webui";
|
||||
petId: string;
|
||||
}
|
38
src/controllers/api/removeFromAllianceController.ts
Normal file
38
src/controllers/api/removeFromAllianceController.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
|
||||
import { deleteAlliance } from "@/src/services/guildService";
|
||||
import { getAccountForRequest } from "@/src/services/loginService";
|
||||
import { GuildPermission } from "@/src/types/guildTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const removeFromAllianceController: RequestHandler = async (req, res) => {
|
||||
// Check requester is a warlord in their guild
|
||||
const account = await getAccountForRequest(req);
|
||||
const guildMember = (await GuildMember.findOne({ accountId: account._id, status: 0 }))!;
|
||||
if (guildMember.rank > 1) {
|
||||
res.status(400).json({ Error: 104 });
|
||||
return;
|
||||
}
|
||||
|
||||
let allianceMember = (await AllianceMember.findOne({ guildId: guildMember.guildId }))!;
|
||||
if (!guildMember.guildId.equals(req.query.guildId as string)) {
|
||||
// Removing a guild that is not our own needs additional permissions
|
||||
if (!(allianceMember.Permissions & GuildPermission.Ruler)) {
|
||||
res.status(400).json({ Error: 104 });
|
||||
return;
|
||||
}
|
||||
|
||||
// Update allianceMember to point to the alliance to kick
|
||||
allianceMember = (await AllianceMember.findOne({ guildId: req.query.guildId }))!;
|
||||
}
|
||||
|
||||
if (allianceMember.Permissions & GuildPermission.Ruler) {
|
||||
await deleteAlliance(allianceMember.allianceId);
|
||||
} else {
|
||||
await Promise.all([
|
||||
await Guild.updateOne({ _id: allianceMember.guildId }, { $unset: { AllianceId: "" } }),
|
||||
await AllianceMember.deleteOne({ _id: allianceMember._id })
|
||||
]);
|
||||
}
|
||||
|
||||
res.end();
|
||||
};
|
@ -1,6 +1,8 @@
|
||||
import { GuildMember } from "@/src/models/guildModel";
|
||||
import { Inbox } from "@/src/models/inboxModel";
|
||||
import { Account } from "@/src/models/loginModel";
|
||||
import { getGuildForRequest, hasGuildPermission } from "@/src/services/guildService";
|
||||
import { deleteGuild, getGuildForRequest, hasGuildPermission, removeDojoKeyItems } from "@/src/services/guildService";
|
||||
import { createMessage } from "@/src/services/inboxService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||
import { GuildPermission } from "@/src/types/guildTypes";
|
||||
@ -17,32 +19,47 @@ export const removeFromGuildController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
|
||||
const guildMember = (await GuildMember.findOne({ accountId: payload.userId, guildId: guild._id }))!;
|
||||
if (guildMember.status == 0) {
|
||||
const inventory = await getInventory(payload.userId);
|
||||
inventory.GuildId = undefined;
|
||||
|
||||
// Remove clan key or blueprint from kicked member
|
||||
const itemIndex = inventory.LevelKeys.findIndex(x => x.ItemType == "/Lotus/Types/Keys/DojoKey");
|
||||
if (itemIndex != -1) {
|
||||
inventory.LevelKeys.splice(itemIndex, 1);
|
||||
if (guildMember.rank == 0) {
|
||||
await deleteGuild(guild._id);
|
||||
} else {
|
||||
const recipeIndex = inventory.Recipes.findIndex(x => x.ItemType == "/Lotus/Types/Keys/DojoKeyBlueprint");
|
||||
if (recipeIndex != -1) {
|
||||
inventory.Recipes.splice(itemIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (guildMember.status == 0) {
|
||||
const inventory = await getInventory(payload.userId, "GuildId LevelKeys Recipes");
|
||||
inventory.GuildId = undefined;
|
||||
removeDojoKeyItems(inventory);
|
||||
await inventory.save();
|
||||
|
||||
// TODO: Handle clan leader kicking themselves (guild should be deleted in this case, I think)
|
||||
} else if (guildMember.status == 1) {
|
||||
// TOVERIFY: Is this inbox message actually sent on live?
|
||||
await createMessage(guildMember.accountId, [
|
||||
{
|
||||
sndr: "/Lotus/Language/Bosses/Ordis",
|
||||
msg: "/Lotus/Language/Clan/RejectedFromClan",
|
||||
sub: "/Lotus/Language/Clan/RejectedFromClanHeader",
|
||||
arg: [
|
||||
{
|
||||
Key: "PLAYER_NAME",
|
||||
Tag: (await Account.findOne({ _id: guildMember.accountId }, "DisplayName"))!.DisplayName
|
||||
},
|
||||
{
|
||||
Key: "CLAN_NAME",
|
||||
Tag: guild.Name
|
||||
}
|
||||
]
|
||||
// TOVERIFY: If this message is sent on live, is it highPriority?
|
||||
}
|
||||
]);
|
||||
} else if (guildMember.status == 2) {
|
||||
// TODO: Maybe the inbox message for the sent invite should be deleted?
|
||||
// Delete the inbox message for the invite
|
||||
await Inbox.deleteOne({
|
||||
ownerId: guildMember.accountId,
|
||||
contextInfo: guild._id.toString(),
|
||||
acceptAction: "GUILD_INVITE"
|
||||
});
|
||||
}
|
||||
await GuildMember.deleteOne({ _id: guildMember._id });
|
||||
|
||||
guild.RosterActivity ??= [];
|
||||
if (isKick) {
|
||||
const kickee = (await Account.findOne({ _id: payload.userId }))!;
|
||||
const kickee = (await Account.findById(payload.userId))!;
|
||||
guild.RosterActivity.push({
|
||||
dateTime: new Date(),
|
||||
entryType: 12,
|
||||
@ -56,6 +73,7 @@ export const removeFromGuildController: RequestHandler = async (req, res) => {
|
||||
});
|
||||
}
|
||||
await guild.save();
|
||||
}
|
||||
|
||||
res.json({
|
||||
_id: payload.userId,
|
||||
|
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;
|
||||
}
|
33
src/controllers/api/retrievePetFromStasisController.ts
Normal file
33
src/controllers/api/retrievePetFromStasisController.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { Status } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const retrievePetFromStasisController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "KubrowPets");
|
||||
const data = getJSONfromString<IRetrievePetFromStasisRequest>(String(req.body));
|
||||
|
||||
let oldPetId: string | undefined;
|
||||
for (const pet of inventory.KubrowPets) {
|
||||
if (pet.Details!.Status == Status.StatusAvailable) {
|
||||
pet.Details!.Status = Status.StatusStasis;
|
||||
oldPetId = pet._id.toString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
inventory.KubrowPets.id(data.petId)!.Details!.Status = Status.StatusAvailable;
|
||||
|
||||
await inventory.save();
|
||||
res.json({
|
||||
petId: data.petId,
|
||||
oldPetId,
|
||||
status: Status.StatusAvailable
|
||||
});
|
||||
};
|
||||
|
||||
interface IRetrievePetFromStasisRequest {
|
||||
petId: string;
|
||||
}
|
@ -1,53 +1,41 @@
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||
import { config } from "@/src/services/configService";
|
||||
import { addEmailItem, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { ICompletedDialogue } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { ICompletedDialogue, IDialogueDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
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) {
|
||||
const inventory = await getInventory(accountId, "DialogueHistory");
|
||||
inventory.DialogueHistory ??= {};
|
||||
inventory.DialogueHistory.YearIteration = request.YearIteration;
|
||||
} else {
|
||||
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.QueuedDialogues.length != 0 || 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),
|
||||
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;
|
||||
dialogue.QueuedDialogues = request.QueuedDialogues;
|
||||
for (const bool of request.Booleans) {
|
||||
dialogue.Booleans.push(bool);
|
||||
if (bool == "LizzieShawzin") {
|
||||
await addEmailItem(
|
||||
inventory,
|
||||
"/Lotus/Types/Items/EmailItems/LizzieShawzinSkinEmailItem",
|
||||
inventoryChanges
|
||||
);
|
||||
}
|
||||
}
|
||||
for (const bool of request.ResetBooleans) {
|
||||
const index = dialogue.Booleans.findIndex(x => x == bool);
|
||||
@ -55,14 +43,38 @@ export const saveDialogueController: RequestHandler = async (req, res) => {
|
||||
dialogue.Booleans.splice(index, 1);
|
||||
}
|
||||
}
|
||||
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);
|
||||
const tomorrowAt0Utc = (Math.trunc(Date.now() / (86400 * 1000)) + 1) * 86400 * 1000;
|
||||
dialogue.AvailableDate = new Date(tomorrowAt0Utc);
|
||||
await inventory.save();
|
||||
res.json({
|
||||
InventoryChanges: [],
|
||||
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();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -77,9 +89,44 @@ interface SaveCompletedDialogueRequest {
|
||||
Rank: number;
|
||||
Chemistry: number;
|
||||
CompletionType: number;
|
||||
QueuedDialogues: string[]; // unsure
|
||||
QueuedDialogues: string[];
|
||||
Gift?: {
|
||||
Item: string;
|
||||
GainedChemistry: number;
|
||||
Cost: number;
|
||||
GiftedQuantity: number;
|
||||
};
|
||||
Booleans: string[];
|
||||
ResetBooleans: string[];
|
||||
Data: ICompletedDialogue;
|
||||
OtherDialogueInfos: string[]; // unsure
|
||||
Data?: ICompletedDialogue;
|
||||
OtherDialogueInfos: IOtherDialogueInfo[];
|
||||
}
|
||||
|
||||
interface IOtherDialogueInfo {
|
||||
Dialogue: string;
|
||||
Tag: string;
|
||||
Value: number;
|
||||
}
|
||||
|
||||
const getDialogue = (inventory: TInventoryDatabaseDocument, dialogueName: string): IDialogueDatabase => {
|
||||
let dialogue = inventory.DialogueHistory!.Dialogues!.find(x => x.DialogueName == 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: dialogueName
|
||||
}) - 1
|
||||
];
|
||||
}
|
||||
return dialogue;
|
||||
};
|
||||
|
@ -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" });
|
||||
}
|
||||
}
|
||||
};
|
21
src/controllers/api/saveLoadoutController.ts
Normal file
21
src/controllers/api/saveLoadoutController.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { ISaveLoadoutRequest } from "@/src/types/saveLoadoutTypes";
|
||||
import { handleInventoryItemConfigChange } from "@/src/services/saveLoadoutService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
|
||||
export const saveLoadoutController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
|
||||
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.end();
|
||||
};
|
@ -13,10 +13,10 @@ const saveSettingsController: RequestHandler = async (req, res): Promise<void> =
|
||||
|
||||
const settingResults = getJSONfromString<ISaveSettingsRequest>(String(req.body));
|
||||
|
||||
const inventory = await getInventory(accountId);
|
||||
inventory.Settings = Object.assign(inventory.Settings, settingResults.Settings);
|
||||
const inventory = await getInventory(accountId, "Settings");
|
||||
inventory.Settings = Object.assign(inventory.Settings ?? {}, settingResults.Settings);
|
||||
await inventory.save();
|
||||
res.json(inventory.Settings);
|
||||
res.json({ Settings: inventory.Settings });
|
||||
};
|
||||
|
||||
export { saveSettingsController };
|
||||
|
25
src/controllers/api/saveVaultAutoContributeController.ts
Normal file
25
src/controllers/api/saveVaultAutoContributeController.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { Guild } from "@/src/models/guildModel";
|
||||
import { hasGuildPermission } from "@/src/services/guildService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { GuildPermission } from "@/src/types/guildTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const saveVaultAutoContributeController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "GuildId");
|
||||
const guild = (await Guild.findById(inventory.GuildId!, "Ranks AutoContributeFromVault"))!;
|
||||
if (!(await hasGuildPermission(guild, accountId, GuildPermission.Treasurer))) {
|
||||
res.status(400).send("Invalid permission").end();
|
||||
return;
|
||||
}
|
||||
const data = getJSONfromString<ISetVaultAutoContributeRequest>(String(req.body));
|
||||
guild.AutoContributeFromVault = data.autoContributeFromVault;
|
||||
await guild.save();
|
||||
res.end();
|
||||
};
|
||||
|
||||
interface ISetVaultAutoContributeRequest {
|
||||
autoContributeFromVault: boolean;
|
||||
}
|
@ -6,20 +6,71 @@ 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 inventory = await getInventory(accountId);
|
||||
const requiredFields = new Set<keyof TInventoryDatabaseDocument>();
|
||||
if (payload.SellCurrency == "SC_RegularCredits") {
|
||||
requiredFields.add("RegularCredits");
|
||||
} else if (payload.SellCurrency == "SC_FusionPoints") {
|
||||
requiredFields.add("FusionPoints");
|
||||
} else {
|
||||
requiredFields.add("MiscItems");
|
||||
}
|
||||
for (const key of Object.keys(payload.Items)) {
|
||||
requiredFields.add(key as keyof TInventoryDatabaseDocument);
|
||||
}
|
||||
if (requiredFields.has("Upgrades")) {
|
||||
requiredFields.add("RawUpgrades");
|
||||
}
|
||||
if (payload.Items.Suits) {
|
||||
requiredFields.add(InventorySlot.SUITS);
|
||||
}
|
||||
if (payload.Items.LongGuns || payload.Items.Pistols || payload.Items.Melee) {
|
||||
requiredFields.add(InventorySlot.WEAPONS);
|
||||
}
|
||||
if (payload.Items.SpaceSuits) {
|
||||
requiredFields.add(InventorySlot.SPACESUITS);
|
||||
}
|
||||
if (payload.Items.SpaceGuns || payload.Items.SpaceMelee) {
|
||||
requiredFields.add(InventorySlot.SPACEWEAPONS);
|
||||
}
|
||||
if (payload.Items.Sentinels || payload.Items.SentinelWeapons || payload.Items.MoaPets) {
|
||||
requiredFields.add(InventorySlot.SENTINELS);
|
||||
}
|
||||
if (payload.Items.OperatorAmps) {
|
||||
requiredFields.add(InventorySlot.AMPS);
|
||||
}
|
||||
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, [
|
||||
{
|
||||
@ -34,10 +85,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 => {
|
||||
@ -93,6 +148,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 });
|
||||
@ -110,6 +171,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) {
|
||||
@ -156,7 +267,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 {
|
||||
@ -174,9 +287,12 @@ interface ISellRequest {
|
||||
SpaceMelee?: ISellItem[];
|
||||
Sentinels?: ISellItem[];
|
||||
SentinelWeapons?: ISellItem[];
|
||||
MoaPets?: ISellItem[];
|
||||
OperatorAmps?: ISellItem[];
|
||||
Hoverboards?: ISellItem[];
|
||||
Drones?: ISellItem[];
|
||||
CrewShipWeapons?: ISellItem[];
|
||||
CrewShipWeaponSkins?: ISellItem[];
|
||||
};
|
||||
SellPrice: number;
|
||||
SellCurrency:
|
||||
@ -193,3 +309,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[];
|
||||
}
|
38
src/controllers/api/setAllianceGuildPermissionsController.ts
Normal file
38
src/controllers/api/setAllianceGuildPermissionsController.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { AllianceMember, GuildMember } from "@/src/models/guildModel";
|
||||
import { getAccountForRequest } from "@/src/services/loginService";
|
||||
import { GuildPermission } from "@/src/types/guildTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const setAllianceGuildPermissionsController: RequestHandler = async (req, res) => {
|
||||
// Check requester is a warlord in their guild
|
||||
const account = await getAccountForRequest(req);
|
||||
const guildMember = (await GuildMember.findOne({ accountId: account._id, status: 0 }))!;
|
||||
if (guildMember.rank > 1) {
|
||||
res.status(400).end();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check guild is the creator of the alliance and don't allow changing of own permissions. (Technically changing permissions requires the Promoter permission, but both are exclusive to the creator guild.)
|
||||
const allianceMember = (await AllianceMember.findOne({
|
||||
guildId: guildMember.guildId,
|
||||
Pending: false
|
||||
}))!;
|
||||
if (
|
||||
!(allianceMember.Permissions & GuildPermission.Ruler) ||
|
||||
allianceMember.guildId.equals(req.query.guildId as string)
|
||||
) {
|
||||
res.status(400).end();
|
||||
return;
|
||||
}
|
||||
|
||||
const targetAllianceMember = (await AllianceMember.findOne({
|
||||
allianceId: allianceMember.allianceId,
|
||||
guildId: req.query.guildId
|
||||
}))!;
|
||||
targetAllianceMember.Permissions =
|
||||
parseInt(req.query.perms as string) &
|
||||
(GuildPermission.Recruiter | GuildPermission.Treasurer | GuildPermission.ChatModerator);
|
||||
await targetAllianceMember.save();
|
||||
|
||||
res.end();
|
||||
};
|
34
src/controllers/api/setDojoComponentColorsController.ts
Normal file
34
src/controllers/api/setDojoComponentColorsController.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { GuildPermission } from "@/src/types/guildTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const setDojoComponentColorsController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "GuildId LevelKeys");
|
||||
const guild = await getGuildForRequestEx(req, inventory);
|
||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Decorator))) {
|
||||
res.json({ DojoRequestStatus: -1 });
|
||||
return;
|
||||
}
|
||||
const data = getJSONfromString<ISetDojoComponentColorsRequest>(String(req.body));
|
||||
const component = guild.DojoComponents.id(data.ComponentId)!;
|
||||
//const deco = component.Decos!.find(x => x._id.equals(data.DecoId))!;
|
||||
//deco.Pending = true;
|
||||
//component.PaintBot = new Types.ObjectId(data.DecoId);
|
||||
if ("lights" in req.query) {
|
||||
component.PendingLights = data.Colours;
|
||||
} else {
|
||||
component.PendingColors = data.Colours;
|
||||
}
|
||||
await guild.save();
|
||||
res.json(await getDojoClient(guild, 0, component._id));
|
||||
};
|
||||
|
||||
interface ISetDojoComponentColorsRequest {
|
||||
ComponentId: string;
|
||||
DecoId: string;
|
||||
Colours: number[];
|
||||
}
|
25
src/controllers/api/setDojoComponentSettingsController.ts
Normal file
25
src/controllers/api/setDojoComponentSettingsController.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { GuildPermission } from "@/src/types/guildTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const setDojoComponentSettingsController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "GuildId LevelKeys");
|
||||
const guild = await getGuildForRequestEx(req, inventory);
|
||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Decorator))) {
|
||||
res.json({ DojoRequestStatus: -1 });
|
||||
return;
|
||||
}
|
||||
const component = guild.DojoComponents.id(req.query.componentId)!;
|
||||
const data = getJSONfromString<ISetDojoComponentSettingsRequest>(String(req.body));
|
||||
component.Settings = data.Settings;
|
||||
await guild.save();
|
||||
res.json(await getDojoClient(guild, 0, component._id));
|
||||
};
|
||||
|
||||
interface ISetDojoComponentSettingsRequest {
|
||||
Settings: string;
|
||||
}
|
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