forked from OpenWF/SpaceNinjaServer
Compare commits
272 Commits
Author | SHA1 | Date | |
---|---|---|---|
545b949202 | |||
0c9b27a29b | |||
cfa750b6f7 | |||
049baa4313 | |||
e267ca8f55 | |||
1a2d8ab19a | |||
8c19aec340 | |||
d1c860c693 | |||
69f9d5ebc5 | |||
d66f1c58d8 | |||
5234cf213e | |||
7c7d2b9061 | |||
3d46d05a6c | |||
00cea6788e | |||
58bdb2d2ec | |||
c4c622d82b | |||
44a129ab0b | |||
5a7caa5ba9 | |||
a9c5e30994 | |||
f0547cb9e6 | |||
ef3d3b92c7 | |||
a9359bd989 | |||
3d21813a79 | |||
7cad831702 | |||
0f2b6c68cd | |||
4fcac6dc37 | |||
d2cae012a7 | |||
b36d524953 | |||
abb5b8880f | |||
4895b4630b | |||
690b872b5e | |||
d77fe60cd8 | |||
3cae42c7d6 | |||
bbccee0637 | |||
31e24c27ad | |||
4acd87aae6 | |||
d8ff601be7 | |||
d79e7c0274 | |||
4f1f9592b0 | |||
764cdd1ab8 | |||
0ba641a2ac | |||
eb7b51852b | |||
a3be376489 | |||
d94cd38120 | |||
8c22555904 | |||
c9edef39f8 | |||
b42182c85f | |||
86f86d0476 | |||
0fdf8b2c75 | |||
285b1bbf60 | |||
731ce6c215 | |||
39630c5af7 | |||
d5be202835 | |||
3a6e4ac2e1 | |||
e234af098d | |||
4a434cea2b | |||
36f2828d37 | |||
ca3cfb5299 | |||
f242d9f873 | |||
9a034b1c8a | |||
122950034e | |||
636d3100f3 | |||
444c92f0c6 | |||
653798b987 | |||
7a88f6f486 | |||
82b203e00b | |||
271f5bd47a | |||
f61d15b496 | |||
cfd50e7402 | |||
2421a16b2c | |||
cee622d5e9 | |||
84f081312b | |||
7ca7147b78 | |||
558af66965 | |||
d7b9fb1ab5 | |||
bf12f90c88 | |||
6dd9b42f40 | |||
3bcd5827f9 | |||
d16d763977 | |||
ff8ec8dbed | |||
6cdd103c3d | |||
b6f79c1e5c | |||
2bb3e2afdd | |||
6a60537cd0 | |||
2fa6dcc7ed | |||
93ef9a5348 | |||
5d5d0ee560 | |||
f84cc54c97 | |||
4cb0f8b167 | |||
eadc9c4ecb | |||
f41377bb81 | |||
95136e6059 | |||
3c64f17e34 | |||
3619bdfdb5 | |||
97064826b2 | |||
c6c7a2966b | |||
ce46fa14ac | |||
3186ffe164 | |||
e686a2d028 | |||
88d4ba6340 | |||
05382beaaf | |||
e136c0494e | |||
ad7b5fc052 | |||
61a8d01f64 | |||
d78ca91d6c | |||
4ca4990f89 | |||
bf40155dd4 | |||
2e9d3c33b6 | |||
7c8e8fe049 | |||
0c4065619d | |||
dabca46e88 | |||
7819d87bbe | |||
b8b8b6a6c6 | |||
9af0e06b70 | |||
f8d0c9e0cb | |||
16e80acb53 | |||
6691d4e402 | |||
3dcd2663d3 | |||
ffeffe2796 | |||
a9f1368cb7 | |||
cccf6f04a5 | |||
1ead581780 | |||
145d21e30e | |||
6c2055a246 | |||
01e490768c | |||
2e8fe799d7 | |||
53976378bb | |||
4e832d3b2c | |||
8c1147998d | |||
3e99e069be | |||
9731004de6 | |||
b98a88b700 | |||
6023f1c113 | |||
c6dd8bfb81 | |||
3053112428 | |||
f448d03880 | |||
d00fbed46f | |||
c283d61399 | |||
12d09531b3 | |||
b67ddf6df2 | |||
8b27fcf459 | |||
071ef528ea | |||
71d1b6094c | |||
fcc11206cc | |||
3c019e41b9 | |||
54a73ad5d7 | |||
62eeb313ec | |||
62d4b9f6cb | |||
1d813a1b1b | |||
4823406229 | |||
bdc41de8bb | |||
60236a1154 | |||
1979b20f8c | |||
c736310ff3 | |||
2b555a6456 | |||
870c964854 | |||
4535b193e0 | |||
943574bf3a | |||
f9a4d48b4d | |||
135b1e54fe | |||
b7c47b91ff | |||
9def5c265e | |||
65387ccdea | |||
4118528603 | |||
8ffbb308c5 | |||
2e649cabf6 | |||
5c5296d565 | |||
8f5f2fc206 | |||
0997f9567f | |||
be02435661 | |||
20c4092dfe | |||
01492f4f16 | |||
d739945a1d | |||
d43e39d7b5 | |||
b0499a62aa | |||
8f02bd1509 | |||
32f4c5105a | |||
28da982c80 | |||
ae1850d6cd | |||
b90bdd2783 | |||
90f2b90398 | |||
84916bf64e | |||
d41e4f7f56 | |||
082ae536f7 | |||
ba6cd47432 | |||
92d34fd69e | |||
09b9683fa1 | |||
a47eccdec8 | |||
ffd410537e | |||
79eab71aaf | |||
21164554a3 | |||
45c0da6ed8 | |||
727f6837ba | |||
77a3b64f49 | |||
ce59086f7d | |||
9b0989f1df | |||
b01376f703 | |||
870ff2dd2c | |||
1ac71a9b28 | |||
a622787500 | |||
52c8802d57 | |||
daf721f7cd | |||
f724073d93 | |||
8fb676c906 | |||
2ce5cc4562 | |||
099f12a197 | |||
bfe2e93c76 | |||
8b97bb4b0a | |||
85a45a04ea | |||
2a40449604 | |||
382f8c55ce | |||
77513190e4 | |||
5e8ce934c9 | |||
6de81c2b41 | |||
4c5ac4f03a | |||
d6f4c1a035 | |||
c58c70c4ce | |||
3e1e19d6c5 | |||
2521733e55 | |||
d5297d3547 | |||
5bc39aac8a | |||
b201508fa1 | |||
5f9ae2aef6 | |||
b451c73598 | |||
9d4bce852e | |||
c83e732b88 | |||
3d13ec311e | |||
31043b55de | |||
ab32728c47 | |||
3fc2dccf81 | |||
1084932afb | |||
f9b3fecc10 | |||
7a51fab5d3 | |||
0e255067a8 | |||
8d788d38a5 | |||
bdd5ade2eb | |||
3ff7e4264c | |||
29b54b52dd | |||
dab8c6c8ba | |||
bdb4c8fd7c | |||
3bcac1459b | |||
b987e01811 | |||
d831732513 | |||
42c63ecbe8 | |||
0319031e13 | |||
1f3bb88910 | |||
6171b36479 | |||
c56507e12d | |||
e545ecf767 | |||
58549c1488 | |||
bb606f3a95 | |||
4b12fe12cb | |||
4f28688837 | |||
203b3e20d9 | |||
005690daa4 | |||
5fefd189af | |||
f6cbc02c47 | |||
da6d75c748 | |||
460deed3ed | |||
4e57bcd1ae | |||
e2fe406017 | |||
c53dc2fd02 | |||
cfa3586f64 | |||
238af294fe | |||
1914fd8f10 | |||
ec4af075b5 | |||
355a70d366 | |||
cad82cf7de | |||
ff3a9b382c | |||
18fbd51efb | |||
f7b4b4f089 | |||
83743831c9 |
18
.eslintrc
18
.eslintrc
@ -11,17 +11,17 @@
|
|||||||
"node": true
|
"node": true
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"@typescript-eslint/explicit-function-return-type": "warn",
|
"@typescript-eslint/explicit-function-return-type": "error",
|
||||||
"@typescript-eslint/restrict-template-expressions": "warn",
|
"@typescript-eslint/restrict-template-expressions": "error",
|
||||||
"@typescript-eslint/restrict-plus-operands": "warn",
|
"@typescript-eslint/restrict-plus-operands": "error",
|
||||||
"@typescript-eslint/no-unsafe-member-access": "warn",
|
"@typescript-eslint/no-unsafe-member-access": "error",
|
||||||
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "caughtErrors": "none" }],
|
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "caughtErrors": "none" }],
|
||||||
"@typescript-eslint/no-unsafe-argument": "error",
|
"@typescript-eslint/no-unsafe-argument": "error",
|
||||||
"@typescript-eslint/no-unsafe-call": "warn",
|
"@typescript-eslint/no-unsafe-call": "error",
|
||||||
"@typescript-eslint/no-unsafe-assignment": "warn",
|
"@typescript-eslint/no-unsafe-assignment": "error",
|
||||||
"@typescript-eslint/no-explicit-any": "warn",
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
"no-loss-of-precision": "warn",
|
"no-loss-of-precision": "error",
|
||||||
"@typescript-eslint/no-unnecessary-condition": "warn",
|
"@typescript-eslint/no-unnecessary-condition": "error",
|
||||||
"@typescript-eslint/no-base-to-string": "off",
|
"@typescript-eslint/no-base-to-string": "off",
|
||||||
"no-case-declarations": "error",
|
"no-case-declarations": "error",
|
||||||
"prettier/prettier": "error",
|
"prettier/prettier": "error",
|
||||||
|
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
@ -1,6 +1,7 @@
|
|||||||
name: Build
|
name: Build
|
||||||
on:
|
on:
|
||||||
push: {}
|
push:
|
||||||
|
branches: ["main"]
|
||||||
pull_request: {}
|
pull_request: {}
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@ -10,6 +11,8 @@ jobs:
|
|||||||
uses: actions/checkout@v4.1.2
|
uses: actions/checkout@v4.1.2
|
||||||
- name: Setup Node.js environment
|
- name: Setup Node.js environment
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.2
|
||||||
|
with:
|
||||||
|
node-version: ">=20.6.0"
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: cp config.json.example config.json
|
- run: cp config.json.example config.json
|
||||||
- run: npm run verify
|
- run: npm run verify
|
||||||
|
25
.github/workflows/docker.yml
vendored
25
.github/workflows/docker.yml
vendored
@ -4,9 +4,9 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
jobs:
|
jobs:
|
||||||
docker:
|
docker-amd64:
|
||||||
if: github.repository == 'OpenWF/SpaceNinjaServer'
|
if: github.repository == 'OpenWF/SpaceNinjaServer'
|
||||||
runs-on: ubuntu-latest
|
runs-on: amd64
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Docker buildx
|
- name: Set up Docker buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
@ -18,8 +18,27 @@ jobs:
|
|||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
openwf/spaceninjaserver:latest
|
openwf/spaceninjaserver:latest
|
||||||
openwf/spaceninjaserver:${{ github.sha }}
|
openwf/spaceninjaserver:${{ github.sha }}
|
||||||
|
docker-arm64:
|
||||||
|
if: github.repository == 'OpenWF/SpaceNinjaServer'
|
||||||
|
runs-on: arm64
|
||||||
|
steps:
|
||||||
|
- name: Set up Docker buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Log in to container registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: openwf
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
platforms: linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
openwf/spaceninjaserver:latest-arm64
|
||||||
|
openwf/spaceninjaserver:${{ github.sha }}-arm64
|
||||||
|
3
.vscode/launch.json
vendored
3
.vscode/launch.json
vendored
@ -8,8 +8,7 @@
|
|||||||
"type": "node",
|
"type": "node",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Debug and Watch",
|
"name": "Debug and Watch",
|
||||||
"runtimeArgs": ["-r", "tsconfig-paths/register", "-r", "ts-node/register", "--watch-path", "src"],
|
"args": ["${workspaceFolder}/scripts/dev.js"],
|
||||||
"args": ["${workspaceFolder}/src/index.ts"],
|
|
||||||
"console": "integratedTerminal"
|
"console": "integratedTerminal"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
17
AGENTS.md
Normal file
17
AGENTS.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
## In General
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
Use `npm i` or `npm ci` to install all dependencies.
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
Use `npm run verify` to verify that your changes pass TypeScript's checks.
|
||||||
|
|
||||||
|
### Formatting
|
||||||
|
|
||||||
|
Use `npm run prettier` to ensure your formatting matches the expected format. Failing to do so will cause CI failure.
|
||||||
|
|
||||||
|
## WebUI Specific
|
||||||
|
|
||||||
|
The translation system is designed around additions being made to `static/webui/translations/en.js`. They are copied over for translation via `npm run update-translations`. DO NOT produce non-English strings; we want them to be translated by humans who can understand the full context.
|
48
Dockerfile
48
Dockerfile
@ -1,49 +1,11 @@
|
|||||||
FROM node:18-alpine3.19
|
FROM node:24-alpine3.21
|
||||||
|
|
||||||
ENV APP_MONGODB_URL=mongodb://mongodb:27017/openWF
|
RUN apk add --no-cache bash jq
|
||||||
ENV APP_MY_ADDRESS=localhost
|
|
||||||
ENV APP_HTTP_PORT=80
|
|
||||||
ENV APP_HTTPS_PORT=443
|
|
||||||
ENV APP_AUTO_CREATE_ACCOUNT=true
|
|
||||||
ENV APP_SKIP_TUTORIAL=false
|
|
||||||
ENV APP_SKIP_ALL_DIALOGUE=false
|
|
||||||
ENV APP_UNLOCK_ALL_SCANS=false
|
|
||||||
ENV APP_UNLOCK_ALL_MISSIONS=false
|
|
||||||
ENV APP_INFINITE_CREDITS=false
|
|
||||||
ENV APP_INFINITE_PLATINUM=false
|
|
||||||
ENV APP_INFINITE_ENDO=false
|
|
||||||
ENV APP_INFINITE_REGAL_AYA=false
|
|
||||||
ENV APP_INFINITE_HELMINTH_MATERIALS=false
|
|
||||||
ENV APP_DONT_SUBTRACT_CONSUMABLES=false
|
|
||||||
ENV APP_UNLOCK_ALL_SHIP_FEATURES=false
|
|
||||||
ENV APP_UNLOCK_ALL_SHIP_DECORATIONS=false
|
|
||||||
ENV APP_UNLOCK_ALL_FLAVOUR_ITEMS=false
|
|
||||||
ENV APP_UNLOCK_ALL_SKINS=false
|
|
||||||
ENV APP_UNLOCK_ALL_CAPTURA_SCENES=false
|
|
||||||
ENV APP_UNIVERSAL_POLARITY_EVERYWHERE=false
|
|
||||||
ENV APP_UNLOCK_DOUBLE_CAPACITY_POTATOES_EVERYWHERE=false
|
|
||||||
ENV APP_UNLOCK_EXILUS_EVERYWHERE=false
|
|
||||||
ENV APP_UNLOCK_ARCANES_EVERYWHERE=false
|
|
||||||
ENV APP_NO_DAILY_FOCUS_LIMIT=false
|
|
||||||
ENV APP_NO_ARGON_CRYSTAL_DECAY=false
|
|
||||||
ENV APP_NO_MASTERY_RANK_UP_COOLDOWN=false
|
|
||||||
ENV APP_NO_VENDOR_PURCHASE_LIMITS=true
|
|
||||||
ENV APP_NO_DEATH_MARKS=false
|
|
||||||
ENV APP_NO_KIM_COOLDOWNS=false
|
|
||||||
ENV APP_INSTANT_RESOURCE_EXTRACTOR_DRONES=false
|
|
||||||
ENV APP_NO_RESOURCE_EXTRACTOR_DRONES_DAMAGE=false
|
|
||||||
ENV APP_SKIP_CLAN_KEY_CRAFTING=false
|
|
||||||
ENV APP_NO_DOJO_ROOM_BUILD_STAGE=false
|
|
||||||
ENV APP_NO_DECO_BUILD_STAGE=false
|
|
||||||
ENV APP_FAST_DOJO_ROOM_DESTRUCTION=false
|
|
||||||
ENV APP_NO_DOJO_RESEARCH_COSTS=false
|
|
||||||
ENV APP_NO_DOJO_RESEARCH_TIME=false
|
|
||||||
ENV APP_FAST_CLAN_ASCENSION=false
|
|
||||||
ENV APP_SPOOF_MASTERY_RANK=-1
|
|
||||||
|
|
||||||
RUN apk add --no-cache bash sed wget jq
|
|
||||||
|
|
||||||
COPY . /app
|
COPY . /app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN npm i --omit=dev
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
||||||
|
23
README.md
23
README.md
@ -14,4 +14,25 @@ SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [confi
|
|||||||
|
|
||||||
- `logger.level` can be `fatal`, `error`, `warn`, `info`, `http`, `debug`, or `trace`.
|
- `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 ]`.
|
- `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.
|
- `worldState.eidolonOverride` can be set to `day` or `night` to lock the time to day/fass and night/vome on Plains of Eidolon/Cambion Drift.
|
||||||
|
- `worldState.vallisOverride` can be set to `warm` or `cold` to lock the temperature on Orb Vallis.
|
||||||
|
- `worldState.duviriOverride` can be set to `joy`, `anger`, `envy`, `sorrow`, or `fear` to lock the Duviri spiral.
|
||||||
|
- `worldState.nightwaveOverride` will lock the nightwave season, assuming the client is new enough for it. Valid values:
|
||||||
|
- `RadioLegionIntermission13Syndicate` for Nora's Mix Vol. 9
|
||||||
|
- `RadioLegionIntermission12Syndicate` for Nora's Mix Vol. 8
|
||||||
|
- `RadioLegionIntermission11Syndicate` for Nora's Mix Vol. 7
|
||||||
|
- `RadioLegionIntermission10Syndicate` for Nora's Mix Vol. 6
|
||||||
|
- `RadioLegionIntermission9Syndicate` for Nora's Mix Vol. 5
|
||||||
|
- `RadioLegionIntermission8Syndicate` for Nora's Mix Vol. 4
|
||||||
|
- `RadioLegionIntermission7Syndicate` for Nora's Mix Vol. 3
|
||||||
|
- `RadioLegionIntermission6Syndicate` for Nora's Mix Vol. 2
|
||||||
|
- `RadioLegionIntermission5Syndicate` for Nora's Mix Vol. 1
|
||||||
|
- `RadioLegionIntermission4Syndicate` for Nora's Choice
|
||||||
|
- `RadioLegionIntermission3Syndicate` for Intermission III
|
||||||
|
- `RadioLegion3Syndicate` for Glassmaker
|
||||||
|
- `RadioLegionIntermission2Syndicate` for Intermission II
|
||||||
|
- `RadioLegion2Syndicate` for The Emissary
|
||||||
|
- `RadioLegionIntermissionSyndicate` for Intermission I
|
||||||
|
- `RadioLegionSyndicate` for The Wolf of Saturn Six
|
||||||
|
- `allTheFissures` can be set to `normal` or `hard` to enable all fissures either in normal or steel path, respectively.
|
||||||
|
- `worldState.circuitGameModes` can be set to an array of game modes which will override the otherwise-random pattern in The Circuit. Valid element values are `Survival`, `VoidFlood`, `Excavation`, `Defense`, `Exterminate`, `Assassination`, and `Alchemy`.
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
echo Updating SpaceNinjaServer...
|
echo Updating SpaceNinjaServer...
|
||||||
git fetch --prune
|
git fetch --prune
|
||||||
git stash
|
git stash
|
||||||
git reset --hard origin/main
|
git checkout -f origin/main
|
||||||
|
|
||||||
if exist static\data\0\ (
|
if exist static\data\0\ (
|
||||||
echo Updating stripped assets...
|
echo Updating stripped assets...
|
||||||
|
23
UPDATE AND START SERVER.sh
Executable file
23
UPDATE AND START SERVER.sh
Executable file
@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "Updating SpaceNinjaServer..."
|
||||||
|
git fetch --prune
|
||||||
|
git stash
|
||||||
|
git checkout -f origin/main
|
||||||
|
|
||||||
|
if [ -d "static/data/0/" ]; then
|
||||||
|
echo "Updating stripped assets..."
|
||||||
|
cd static/data/0/
|
||||||
|
git pull
|
||||||
|
cd ../../../
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Updating dependencies..."
|
||||||
|
npm i --omit=dev
|
||||||
|
|
||||||
|
npm run build
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
npm run start
|
||||||
|
echo "SpaceNinjaServer seems to have crashed."
|
||||||
|
fi
|
||||||
|
|
@ -13,12 +13,17 @@
|
|||||||
"skipTutorial": false,
|
"skipTutorial": false,
|
||||||
"skipAllDialogue": false,
|
"skipAllDialogue": false,
|
||||||
"unlockAllScans": false,
|
"unlockAllScans": false,
|
||||||
"unlockAllMissions": false,
|
|
||||||
"infiniteCredits": false,
|
"infiniteCredits": false,
|
||||||
"infinitePlatinum": false,
|
"infinitePlatinum": false,
|
||||||
"infiniteEndo": false,
|
"infiniteEndo": false,
|
||||||
"infiniteRegalAya": false,
|
"infiniteRegalAya": false,
|
||||||
"infiniteHelminthMaterials": false,
|
"infiniteHelminthMaterials": false,
|
||||||
|
"claimingBlueprintRefundsIngredients": false,
|
||||||
|
"dontSubtractPurchaseCreditCost": false,
|
||||||
|
"dontSubtractPurchasePlatinumCost": false,
|
||||||
|
"dontSubtractPurchaseItemCost": false,
|
||||||
|
"dontSubtractPurchaseStandingCost": false,
|
||||||
|
"dontSubtractVoidTraces": false,
|
||||||
"dontSubtractConsumables": false,
|
"dontSubtractConsumables": false,
|
||||||
"unlockAllShipFeatures": false,
|
"unlockAllShipFeatures": false,
|
||||||
"unlockAllShipDecorations": false,
|
"unlockAllShipDecorations": false,
|
||||||
@ -33,9 +38,15 @@
|
|||||||
"noDailyFocusLimit": false,
|
"noDailyFocusLimit": false,
|
||||||
"noArgonCrystalDecay": false,
|
"noArgonCrystalDecay": false,
|
||||||
"noMasteryRankUpCooldown": false,
|
"noMasteryRankUpCooldown": false,
|
||||||
"noVendorPurchaseLimits": true,
|
"noVendorPurchaseLimits": false,
|
||||||
"noDeathMarks": false,
|
"noDeathMarks": false,
|
||||||
"noKimCooldowns": false,
|
"noKimCooldowns": false,
|
||||||
|
"fullyStockedVendors": false,
|
||||||
|
"baroAlwaysAvailable": false,
|
||||||
|
"baroFullyStocked": false,
|
||||||
|
"syndicateMissionsRepeatable": false,
|
||||||
|
"unlockAllProfitTakerStages": false,
|
||||||
|
"instantFinishRivenChallenge": false,
|
||||||
"instantResourceExtractorDrones": false,
|
"instantResourceExtractorDrones": false,
|
||||||
"noResourceExtractorDronesDamage": false,
|
"noResourceExtractorDronesDamage": false,
|
||||||
"skipClanKeyCrafting": false,
|
"skipClanKeyCrafting": false,
|
||||||
@ -45,12 +56,30 @@
|
|||||||
"noDojoResearchCosts": false,
|
"noDojoResearchCosts": false,
|
||||||
"noDojoResearchTime": false,
|
"noDojoResearchTime": false,
|
||||||
"fastClanAscension": false,
|
"fastClanAscension": false,
|
||||||
|
"missionsCanGiveAllRelics": false,
|
||||||
|
"unlockAllSimarisResearchEntries": false,
|
||||||
|
"disableDailyTribute": false,
|
||||||
"spoofMasteryRank": -1,
|
"spoofMasteryRank": -1,
|
||||||
|
"nightwaveStandingMultiplier": 1,
|
||||||
|
"unfaithfulBugFixes": {
|
||||||
|
"ignore1999LastRegionPlayed": false,
|
||||||
|
"fixXtraCheeseTimer": false
|
||||||
|
},
|
||||||
"worldState": {
|
"worldState": {
|
||||||
"creditBoost": false,
|
"creditBoost": false,
|
||||||
"affinityBoost": false,
|
"affinityBoost": false,
|
||||||
"resourceBoost": false,
|
"resourceBoost": false,
|
||||||
"starDays": true,
|
"starDays": true,
|
||||||
"lockTime": 0
|
"galleonOfGhouls": 0,
|
||||||
|
"eidolonOverride": "",
|
||||||
|
"vallisOverride": "",
|
||||||
|
"duviriOverride": "",
|
||||||
|
"nightwaveOverride": "",
|
||||||
|
"allTheFissures": "",
|
||||||
|
"circuitGameModes": null,
|
||||||
|
"darvoStockMultiplier": 1
|
||||||
|
},
|
||||||
|
"dev": {
|
||||||
|
"keepVendorsExpired": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,58 +1,20 @@
|
|||||||
services:
|
services:
|
||||||
spaceninjaserver:
|
spaceninjaserver:
|
||||||
# build: .
|
# The image to use. If you have an ARM CPU, replace 'latest' with 'latest-arm64'.
|
||||||
image: openwf/spaceninjaserver:latest
|
image: openwf/spaceninjaserver:latest
|
||||||
environment:
|
|
||||||
APP_MONGODB_URL: mongodb://openwfagent:spaceninjaserver@mongodb:27017/
|
|
||||||
|
|
||||||
# Following environment variables are set to default image values.
|
|
||||||
# Uncomment to edit.
|
|
||||||
|
|
||||||
# APP_MY_ADDRESS: localhost
|
|
||||||
# APP_HTTP_PORT: 80
|
|
||||||
# APP_HTTPS_PORT: 443
|
|
||||||
# APP_AUTO_CREATE_ACCOUNT: true
|
|
||||||
# APP_SKIP_TUTORIAL: false
|
|
||||||
# APP_SKIP_ALL_DIALOGUE: false
|
|
||||||
# APP_UNLOCK_ALL_SCANS: false
|
|
||||||
# APP_UNLOCK_ALL_MISSIONS: false
|
|
||||||
# APP_INFINITE_CREDITS: false
|
|
||||||
# APP_INFINITE_PLATINUM: false
|
|
||||||
# APP_INFINITE_ENDO: false
|
|
||||||
# APP_INFINITE_REGAL_AYA: false
|
|
||||||
# APP_INFINITE_HELMINTH_MATERIALS: false
|
|
||||||
# APP_DONT_SUBTRACT_CONSUMABLES: false
|
|
||||||
# APP_UNLOCK_ALL_SHIP_FEATURES: false
|
|
||||||
# APP_UNLOCK_ALL_SHIP_DECORATIONS: false
|
|
||||||
# APP_UNLOCK_ALL_FLAVOUR_ITEMS: false
|
|
||||||
# APP_UNLOCK_ALL_SKINS: false
|
|
||||||
# APP_UNLOCK_ALL_CAPTURA_SCENES: false
|
|
||||||
# APP_UNIVERSAL_POLARITY_EVERYWHERE: false
|
|
||||||
# APP_UNLOCK_DOUBLE_CAPACITY_POTATOES_EVERYWHERE: false
|
|
||||||
# APP_UNLOCK_EXILUS_EVERYWHERE: false
|
|
||||||
# APP_UNLOCK_ARCANES_EVERYWHERE: false
|
|
||||||
# APP_NO_DAILY_FOCUS_LIMIT: false
|
|
||||||
# APP_NO_ARGON_CRYSTAL_DECAY: false
|
|
||||||
# APP_NO_MASTERY_RANK_UP_COOLDOWN: false
|
|
||||||
# APP_NO_VENDOR_PURCHASE_LIMITS: true
|
|
||||||
# APP_NO_DEATH_MARKS: false
|
|
||||||
# APP_NO_KIM_COOLDOWNS: false
|
|
||||||
# APP_INSTANT_RESOURCE_EXTRACTOR_DRONES: false
|
|
||||||
# APP_NO_RESOURCE_EXTRACTOR_DRONES_DAMAGE: false
|
|
||||||
# APP_SKIP_CLAN_KEY_CRAFTING: false
|
|
||||||
# APP_NO_DOJO_ROOM_BUILD_STAGE: false
|
|
||||||
# APP_NO_DECO_BUILD_STAGE: false
|
|
||||||
# APP_FAST_DOJO_ROOM_DESTRUCTION: false
|
|
||||||
# APP_NO_DOJO_RESEARCH_COSTS: false
|
|
||||||
# APP_NO_DOJO_RESEARCH_TIME: false
|
|
||||||
# APP_FAST_CLAN_ASCENSION: false
|
|
||||||
# APP_SPOOF_MASTERY_RANK: -1
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./docker-data/static:/app/static/data
|
- ./docker-data/conf:/app/conf
|
||||||
|
- ./docker-data/static-data:/app/static/data
|
||||||
- ./docker-data/logs:/app/logs
|
- ./docker-data/logs:/app/logs
|
||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 80:80
|
||||||
- 443:443
|
- 443:443
|
||||||
|
|
||||||
|
# Normally, the image is fetched from Docker Hub, but you can use the local Dockerfile by removing "image" above and adding this:
|
||||||
|
#build: .
|
||||||
|
# Works best when using `docker-compose up --force-recreate --build`.
|
||||||
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- mongodb
|
- mongodb
|
||||||
mongodb:
|
mongodb:
|
||||||
@ -62,3 +24,4 @@ services:
|
|||||||
MONGO_INITDB_ROOT_PASSWORD: spaceninjaserver
|
MONGO_INITDB_ROOT_PASSWORD: spaceninjaserver
|
||||||
volumes:
|
volumes:
|
||||||
- ./docker-data/database:/data/db
|
- ./docker-data/database:/data/db
|
||||||
|
command: mongod --quiet --logpath /dev/null
|
||||||
|
@ -1,24 +1,8 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Set up the configuration file using environment variables.
|
if [ ! -f conf/config.json ]; then
|
||||||
echo '{
|
jq --arg value "mongodb://openwfagent:spaceninjaserver@mongodb:27017/" '.mongodbUrl = $value' /app/config.json.example > /app/conf/config.json
|
||||||
"logger": {
|
fi
|
||||||
"files": true,
|
|
||||||
"level": "trace",
|
|
||||||
"__valid_levels": "fatal, error, warn, info, http, debug, trace"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
' > config.json
|
|
||||||
|
|
||||||
for config in $(env | grep "APP_")
|
exec npm run start -- --configPath conf/config.json
|
||||||
do
|
|
||||||
var=$(echo "${config}" | tr '[:upper:]' '[:lower:]' | sed 's/app_//g' | sed -E 's/_([a-z])/\U\1/g' | sed 's/=.*//g')
|
|
||||||
val=$(echo "${config}" | sed 's/.*=//g')
|
|
||||||
jq --arg variable "$var" --arg value "$val" '.[$variable] += try [$value|fromjson][] catch $value' config.json > config.tmp
|
|
||||||
mv config.tmp config.json
|
|
||||||
done
|
|
||||||
|
|
||||||
npm i --omit=dev
|
|
||||||
npm run build
|
|
||||||
exec npm run start
|
|
||||||
|
1308
package-lock.json
generated
1308
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
31
package.json
31
package.json
@ -5,9 +5,16 @@
|
|||||||
"main": "index.ts",
|
"main": "index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node --enable-source-maps --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": "tsgo --sourceMap && ncp static/webui build/static/webui",
|
||||||
"build": "tsc --incremental --sourceMap && ncp static/webui build/static/webui",
|
"build:tsc": "tsc --incremental --sourceMap && ncp static/webui build/static/webui",
|
||||||
|
"build:dev": "tsgo --sourceMap",
|
||||||
|
"build:dev:tsc": "tsc --incremental --sourceMap",
|
||||||
|
"build-and-start": "npm run build && npm run start",
|
||||||
|
"build-and-start:bun": "npm run verify && npm run bun-run",
|
||||||
|
"dev": "node scripts/dev.js",
|
||||||
|
"dev:bun": "bun scripts/dev.js",
|
||||||
"verify": "tsgo --noEmit",
|
"verify": "tsgo --noEmit",
|
||||||
|
"bun-run": "bun src/index.ts",
|
||||||
"lint": "eslint --ext .ts .",
|
"lint": "eslint --ext .ts .",
|
||||||
"lint:ci": "eslint --ext .ts --rule \"prettier/prettier: off\" .",
|
"lint:ci": "eslint --ext .ts --rule \"prettier/prettier: off\" .",
|
||||||
"lint:fix": "eslint --fix --ext .ts .",
|
"lint:fix": "eslint --fix --ext .ts .",
|
||||||
@ -18,30 +25,30 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/express": "^5",
|
"@types/express": "^5",
|
||||||
"@types/morgan": "^1.9.9",
|
"@types/morgan": "^1.9.9",
|
||||||
|
"@types/websocket": "^1.0.10",
|
||||||
|
"@types/ws": "^8.18.1",
|
||||||
|
"@typescript/native-preview": "^7.0.0-dev.20250625.1",
|
||||||
|
"chokidar": "^4.0.3",
|
||||||
"crc-32": "^1.2.2",
|
"crc-32": "^1.2.2",
|
||||||
"express": "^5",
|
"express": "^5",
|
||||||
"json-with-bigint": "^3.2.2",
|
"json-with-bigint": "^3.4.4",
|
||||||
"mongoose": "^8.11.0",
|
"mongoose": "^8.11.0",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"ncp": "^2.0.0",
|
"ncp": "^2.0.0",
|
||||||
"typescript": "^5.5",
|
"typescript": "^5.5",
|
||||||
"warframe-public-export-plus": "^0.5.59",
|
"undici": "^7.10.0",
|
||||||
|
"warframe-public-export-plus": "^0.5.74",
|
||||||
"warframe-riven-info": "^0.1.2",
|
"warframe-riven-info": "^0.1.2",
|
||||||
"winston": "^3.17.0",
|
"winston": "^3.17.0",
|
||||||
"winston-daily-rotate-file": "^5.0.0"
|
"winston-daily-rotate-file": "^5.0.0",
|
||||||
|
"ws": "^8.18.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rxliuli/tsgo": "^2025.3.31",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^8.28.0",
|
"@typescript-eslint/eslint-plugin": "^8.28.0",
|
||||||
"@typescript-eslint/parser": "^8.28.0",
|
"@typescript-eslint/parser": "^8.28.0",
|
||||||
"eslint": "^8",
|
"eslint": "^8",
|
||||||
"eslint-plugin-prettier": "^5.2.5",
|
"eslint-plugin-prettier": "^5.2.5",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"ts-node-dev": "^2.0.0",
|
"tree-kill": "^1.2.2"
|
||||||
"tsconfig-paths": "^4.2.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18.15.0",
|
|
||||||
"npm": ">=9.5.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
58
scripts/dev.js
Normal file
58
scripts/dev.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
const { spawn } = require("child_process");
|
||||||
|
const chokidar = require("chokidar");
|
||||||
|
const kill = require("tree-kill");
|
||||||
|
|
||||||
|
let secret = "";
|
||||||
|
for (let i = 0; i != 10; ++i) {
|
||||||
|
secret += String.fromCharCode(Math.floor(Math.random() * 26) + 0x41);
|
||||||
|
}
|
||||||
|
|
||||||
|
const args = [...process.argv].splice(2);
|
||||||
|
args.push("--dev");
|
||||||
|
args.push("--secret");
|
||||||
|
args.push(secret);
|
||||||
|
|
||||||
|
let buildproc, runproc;
|
||||||
|
const spawnopts = { stdio: "inherit", shell: true };
|
||||||
|
function run(changedFile) {
|
||||||
|
if (changedFile) {
|
||||||
|
console.log(`Change to ${changedFile} detected`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buildproc) {
|
||||||
|
kill(buildproc.pid);
|
||||||
|
buildproc = undefined;
|
||||||
|
}
|
||||||
|
if (runproc) {
|
||||||
|
kill(runproc.pid);
|
||||||
|
runproc = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const thisbuildproc = spawn("npm", ["run", process.versions.bun ? "verify" : "build:dev"], spawnopts);
|
||||||
|
const thisbuildstart = Date.now();
|
||||||
|
buildproc = thisbuildproc;
|
||||||
|
buildproc.on("exit", code => {
|
||||||
|
if (buildproc !== thisbuildproc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
buildproc = undefined;
|
||||||
|
if (code === 0) {
|
||||||
|
console.log(`${process.versions.bun ? "Verified" : "Built"} in ${Date.now() - thisbuildstart} ms`);
|
||||||
|
runproc = spawn("npm", ["run", process.versions.bun ? "bun-run" : "start", "--", ...args], spawnopts);
|
||||||
|
runproc.on("exit", () => {
|
||||||
|
runproc = undefined;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
||||||
|
chokidar.watch("src").on("change", run);
|
||||||
|
chokidar.watch("static/fixed_responses").on("change", run);
|
||||||
|
|
||||||
|
chokidar.watch("static/webui").on("change", async () => {
|
||||||
|
try {
|
||||||
|
await fetch("http://localhost/custom/webuiFileChangeDetected?secret=" + secret);
|
||||||
|
} catch (e) {}
|
||||||
|
});
|
@ -1,6 +1,7 @@
|
|||||||
// Based on https://onlyg.it/OpenWF/Translations/src/branch/main/update.php
|
// Based on https://onlyg.it/OpenWF/Translations/src/branch/main/update.php
|
||||||
// Converted via ChatGPT-4o
|
// Converted via ChatGPT-4o
|
||||||
|
|
||||||
|
/* eslint-disable */
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
|
||||||
function extractStrings(content) {
|
function extractStrings(content) {
|
||||||
|
@ -21,6 +21,13 @@ app.use((req, _res, next) => {
|
|||||||
if (req.headers["content-encoding"] == "ezip" || req.headers["content-encoding"] == "e") {
|
if (req.headers["content-encoding"] == "ezip" || req.headers["content-encoding"] == "e") {
|
||||||
req.headers["content-encoding"] = undefined;
|
req.headers["content-encoding"] = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// U18 uses application/x-www-form-urlencoded even tho the data is JSON which Express doesn't like.
|
||||||
|
// U17 sets no Content-Type at all, which Express also doesn't like.
|
||||||
|
if (!req.headers["content-type"] || req.headers["content-type"] == "application/x-www-form-urlencoded") {
|
||||||
|
req.headers["content-type"] = "application/octet-stream";
|
||||||
|
}
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "@/src/helpers/rivenHelper";
|
import {
|
||||||
|
createVeiledRivenFingerprint,
|
||||||
|
createUnveiledRivenFingerprint,
|
||||||
|
rivenRawToRealWeighted
|
||||||
|
} from "@/src/helpers/rivenHelper";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { addMods, getInventory } from "@/src/services/inventoryService";
|
import { addMods, getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getRandomElement } from "@/src/services/rngService";
|
import { getRandomElement } from "@/src/services/rngService";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { ExportUpgrades } from "warframe-public-export-plus";
|
import { ExportUpgrades } from "warframe-public-export-plus";
|
||||||
|
import { config } from "@/src/services/configService";
|
||||||
|
|
||||||
export const activateRandomModController: RequestHandler = async (req, res) => {
|
export const activateRandomModController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
@ -18,7 +23,9 @@ export const activateRandomModController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType])!;
|
const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType])!;
|
||||||
const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
|
const fingerprint = config.instantFinishRivenChallenge
|
||||||
|
? createUnveiledRivenFingerprint(ExportUpgrades[rivenType])
|
||||||
|
: createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
|
||||||
const upgradeIndex =
|
const upgradeIndex =
|
||||||
inventory.Upgrades.push({
|
inventory.Upgrades.push({
|
||||||
ItemType: rivenType,
|
ItemType: rivenType,
|
||||||
|
60
src/controllers/api/addFriendController.ts
Normal file
60
src/controllers/api/addFriendController.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { Friendship } from "@/src/models/friendModel";
|
||||||
|
import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "@/src/services/friendService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { IFriendInfo } from "@/src/types/friendTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const addFriendController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const payload = getJSONfromString<IAddFriendRequest>(String(req.body));
|
||||||
|
const promises: Promise<void>[] = [];
|
||||||
|
const newFriends: IFriendInfo[] = [];
|
||||||
|
if (payload.friend == "all") {
|
||||||
|
const [internalFriendships, externalFriendships] = await Promise.all([
|
||||||
|
Friendship.find({ owner: accountId }, "friend"),
|
||||||
|
Friendship.find({ friend: accountId }, "owner")
|
||||||
|
]);
|
||||||
|
for (const externalFriendship of externalFriendships) {
|
||||||
|
if (!internalFriendships.find(x => x.friend.equals(externalFriendship.owner))) {
|
||||||
|
promises.push(
|
||||||
|
Friendship.insertOne({
|
||||||
|
owner: accountId,
|
||||||
|
friend: externalFriendship.owner,
|
||||||
|
Note: externalFriendship.Note // TOVERIFY: Should the note be copied when accepting a friend request?
|
||||||
|
}) as unknown as Promise<void>
|
||||||
|
);
|
||||||
|
newFriends.push({
|
||||||
|
_id: toOid(externalFriendship.owner)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const externalFriendship = await Friendship.findOne({ owner: payload.friend, friend: accountId }, "Note");
|
||||||
|
if (externalFriendship) {
|
||||||
|
promises.push(
|
||||||
|
Friendship.insertOne({
|
||||||
|
owner: accountId,
|
||||||
|
friend: payload.friend,
|
||||||
|
Note: externalFriendship.Note
|
||||||
|
}) as unknown as Promise<void>
|
||||||
|
);
|
||||||
|
newFriends.push({
|
||||||
|
_id: { $oid: payload.friend }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const newFriend of newFriends) {
|
||||||
|
promises.push(addAccountDataToFriendInfo(newFriend));
|
||||||
|
promises.push(addInventoryDataToFriendInfo(newFriend));
|
||||||
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
|
res.json({
|
||||||
|
Friends: newFriends
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IAddFriendRequest {
|
||||||
|
friend: string; // oid or "all" in which case all=1 is also a query parameter
|
||||||
|
}
|
@ -2,7 +2,7 @@ import { toOid } from "@/src/helpers/inventoryHelpers";
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { Account, Ignore } from "@/src/models/loginModel";
|
import { Account, Ignore } from "@/src/models/loginModel";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { IFriendInfo } from "@/src/types/guildTypes";
|
import { IFriendInfo } from "@/src/types/friendTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
export const addIgnoredUserController: RequestHandler = async (req, res) => {
|
export const addIgnoredUserController: RequestHandler = async (req, res) => {
|
||||||
|
52
src/controllers/api/addPendingFriendController.ts
Normal file
52
src/controllers/api/addPendingFriendController.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { Friendship } from "@/src/models/friendModel";
|
||||||
|
import { Account } from "@/src/models/loginModel";
|
||||||
|
import { addInventoryDataToFriendInfo, areFriendsOfFriends } from "@/src/services/friendService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { IFriendInfo } from "@/src/types/friendTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const addPendingFriendController: RequestHandler = async (req, res) => {
|
||||||
|
const payload = getJSONfromString<IAddPendingFriendRequest>(String(req.body));
|
||||||
|
|
||||||
|
const account = await Account.findOne({ DisplayName: payload.friend });
|
||||||
|
if (!account) {
|
||||||
|
res.status(400).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(account._id.toString(), "Settings");
|
||||||
|
if (
|
||||||
|
inventory.Settings?.FriendInvRestriction == "GIFT_MODE_NONE" ||
|
||||||
|
(inventory.Settings?.FriendInvRestriction == "GIFT_MODE_FRIENDS" &&
|
||||||
|
!(await areFriendsOfFriends(account._id, accountId)))
|
||||||
|
) {
|
||||||
|
res.status(400).send("Friend Invite Restriction");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Friendship.insertOne({
|
||||||
|
owner: accountId,
|
||||||
|
friend: account._id,
|
||||||
|
Note: payload.message
|
||||||
|
});
|
||||||
|
|
||||||
|
const friendInfo: IFriendInfo = {
|
||||||
|
_id: toOid(account._id),
|
||||||
|
DisplayName: account.DisplayName,
|
||||||
|
LastLogin: toMongoDate(account.LastLogin),
|
||||||
|
Note: payload.message
|
||||||
|
};
|
||||||
|
await addInventoryDataToFriendInfo(friendInfo);
|
||||||
|
res.json({
|
||||||
|
Friend: friendInfo
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IAddPendingFriendRequest {
|
||||||
|
friend: string;
|
||||||
|
message: string;
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import { getJSONfromString, regexEscape } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString, regexEscape } from "@/src/helpers/stringHelpers";
|
||||||
import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
|
import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
import { createMessage } from "@/src/services/inboxService";
|
import { createMessage } from "@/src/services/inboxService";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getEffectiveAvatarImageType, getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||||
import { GuildPermission } from "@/src/types/guildTypes";
|
import { GuildPermission } from "@/src/types/guildTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
@ -75,7 +75,7 @@ export const addToAllianceController: RequestHandler = async (req, res) => {
|
|||||||
const invitedClanOwnerMember = (await GuildMember.findOne({ guildId: guilds[0]._id, rank: 0 }))!;
|
const invitedClanOwnerMember = (await GuildMember.findOne({ guildId: guilds[0]._id, rank: 0 }))!;
|
||||||
const senderInventory = await getInventory(account._id.toString(), "ActiveAvatarImageType");
|
const senderInventory = await getInventory(account._id.toString(), "ActiveAvatarImageType");
|
||||||
const senderGuild = (await Guild.findById(allianceMember.guildId, "Name"))!;
|
const senderGuild = (await Guild.findById(allianceMember.guildId, "Name"))!;
|
||||||
const alliance = (await Alliance.findById(req.query.allianceId, "Name"))!;
|
const alliance = (await Alliance.findById(req.query.allianceId as string, "Name"))!;
|
||||||
await createMessage(invitedClanOwnerMember.accountId, [
|
await createMessage(invitedClanOwnerMember.accountId, [
|
||||||
{
|
{
|
||||||
sndr: getSuffixedName(account),
|
sndr: getSuffixedName(account),
|
||||||
@ -95,7 +95,7 @@ export const addToAllianceController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
sub: "/Lotus/Language/Menu/Mailbox_AllianceInvite_Title",
|
sub: "/Lotus/Language/Menu/Mailbox_AllianceInvite_Title",
|
||||||
icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
|
icon: ExportFlavour[getEffectiveAvatarImageType(senderInventory)].icon,
|
||||||
contextInfo: alliance._id.toString(),
|
contextInfo: alliance._id.toString(),
|
||||||
highPriority: true,
|
highPriority: true,
|
||||||
acceptAction: "ALLIANCE_INVITE",
|
acceptAction: "ALLIANCE_INVITE",
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||||
import { Guild, GuildMember } from "@/src/models/guildModel";
|
import { Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
import { Account } from "@/src/models/loginModel";
|
import { Account } from "@/src/models/loginModel";
|
||||||
import { fillInInventoryDataForGuildMember, hasGuildPermission } from "@/src/services/guildService";
|
import { addInventoryDataToFriendInfo, areFriends } from "@/src/services/friendService";
|
||||||
|
import { hasGuildPermission } from "@/src/services/guildService";
|
||||||
import { createMessage } from "@/src/services/inboxService";
|
import { createMessage } from "@/src/services/inboxService";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getEffectiveAvatarImageType, getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountForRequest, getAccountIdForRequest, getSuffixedName } from "@/src/services/loginService";
|
import { getAccountForRequest, getAccountIdForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||||
import { IOid } from "@/src/types/commonTypes";
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
import { GuildPermission, IGuildMemberClient } from "@/src/types/guildTypes";
|
import { GuildPermission, IGuildMemberClient } from "@/src/types/guildTypes";
|
||||||
@ -22,15 +24,18 @@ export const addToGuildController: RequestHandler = async (req, res) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const senderAccount = await getAccountForRequest(req);
|
||||||
const inventory = await getInventory(account._id.toString(), "Settings");
|
const inventory = await getInventory(account._id.toString(), "Settings");
|
||||||
// TODO: Also consider GIFT_MODE_FRIENDS once friends are implemented
|
if (
|
||||||
if (inventory.Settings?.GuildInvRestriction == "GIFT_MODE_NONE") {
|
inventory.Settings?.GuildInvRestriction == "GIFT_MODE_NONE" ||
|
||||||
|
(inventory.Settings?.GuildInvRestriction == "GIFT_MODE_FRIENDS" &&
|
||||||
|
!(await areFriends(account._id, senderAccount._id)))
|
||||||
|
) {
|
||||||
res.status(400).json("Invite restricted");
|
res.status(400).json("Invite restricted");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const guild = (await Guild.findById(payload.GuildId.$oid, "Name Ranks"))!;
|
const guild = (await Guild.findById(payload.GuildId.$oid, "Name Ranks"))!;
|
||||||
const senderAccount = await getAccountForRequest(req);
|
|
||||||
if (!(await hasGuildPermission(guild, senderAccount._id.toString(), GuildPermission.Recruiter))) {
|
if (!(await hasGuildPermission(guild, senderAccount._id.toString(), GuildPermission.Recruiter))) {
|
||||||
res.status(400).json("Invalid permission");
|
res.status(400).json("Invalid permission");
|
||||||
}
|
}
|
||||||
@ -59,7 +64,7 @@ export const addToGuildController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
sub: "/Lotus/Language/Menu/Mailbox_ClanInvite_Title",
|
sub: "/Lotus/Language/Menu/Mailbox_ClanInvite_Title",
|
||||||
icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
|
icon: ExportFlavour[getEffectiveAvatarImageType(senderInventory)].icon,
|
||||||
contextInfo: payload.GuildId.$oid,
|
contextInfo: payload.GuildId.$oid,
|
||||||
highPriority: true,
|
highPriority: true,
|
||||||
acceptAction: "GUILD_INVITE",
|
acceptAction: "GUILD_INVITE",
|
||||||
@ -71,10 +76,11 @@ export const addToGuildController: RequestHandler = async (req, res) => {
|
|||||||
const member: IGuildMemberClient = {
|
const member: IGuildMemberClient = {
|
||||||
_id: { $oid: account._id.toString() },
|
_id: { $oid: account._id.toString() },
|
||||||
DisplayName: account.DisplayName,
|
DisplayName: account.DisplayName,
|
||||||
|
LastLogin: toMongoDate(account.LastLogin),
|
||||||
Rank: 7,
|
Rank: 7,
|
||||||
Status: 2
|
Status: 2
|
||||||
};
|
};
|
||||||
await fillInInventoryDataForGuildMember(member);
|
await addInventoryDataToFriendInfo(member);
|
||||||
res.json({ NewMember: member });
|
res.json({ NewMember: member });
|
||||||
} else if ("RequestMsg" in payload) {
|
} else if ("RequestMsg" in payload) {
|
||||||
// Player applying to join a clan
|
// Player applying to join a clan
|
||||||
|
27
src/controllers/api/adoptPetController.ts
Normal file
27
src/controllers/api/adoptPetController.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 adoptPetController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "KubrowPets");
|
||||||
|
const data = getJSONfromString<IAdoptPetRequest>(String(req.body));
|
||||||
|
const details = inventory.KubrowPets.id(data.petId)!.Details!;
|
||||||
|
details.Name = data.name;
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
petId: data.petId,
|
||||||
|
newName: data.name
|
||||||
|
} satisfies IAdoptPetResponse);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IAdoptPetRequest {
|
||||||
|
petId: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IAdoptPetResponse {
|
||||||
|
petId: string;
|
||||||
|
newName: string;
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
import { fromOid, toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "@/src/helpers/rivenHelper";
|
import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "@/src/helpers/rivenHelper";
|
||||||
import { addMiscItems, addMods, getInventory } from "@/src/services/inventoryService";
|
import { addMiscItems, addMods, getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getRandomElement, getRandomWeightedReward, getRandomWeightedRewardUc } from "@/src/services/rngService";
|
import { getRandomElement, getRandomWeightedReward, getRandomWeightedRewardUc } from "@/src/services/rngService";
|
||||||
import { IOid } from "@/src/types/commonTypes";
|
import { IUpgradeFromClient } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { ExportBoosterPacks, ExportUpgrades, TRarity } from "warframe-public-export-plus";
|
import { ExportBoosterPacks, ExportUpgrades, TRarity } from "warframe-public-export-plus";
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
payload.Consumed.forEach(upgrade => {
|
payload.Consumed.forEach(upgrade => {
|
||||||
inventory.Upgrades.pull({ _id: upgrade.ItemId.$oid });
|
inventory.Upgrades.pull({ _id: fromOid(upgrade.ItemId) });
|
||||||
});
|
});
|
||||||
|
|
||||||
const rawRivenType = getRandomRawRivenType();
|
const rawRivenType = getRandomRawRivenType();
|
||||||
@ -57,8 +57,8 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
|
|||||||
payload.Consumed.forEach(upgrade => {
|
payload.Consumed.forEach(upgrade => {
|
||||||
const meta = ExportUpgrades[upgrade.ItemType];
|
const meta = ExportUpgrades[upgrade.ItemType];
|
||||||
counts[meta.rarity] += upgrade.ItemCount;
|
counts[meta.rarity] += upgrade.ItemCount;
|
||||||
if (upgrade.ItemId.$oid != "000000000000000000000000") {
|
if (fromOid(upgrade.ItemId) != "000000000000000000000000") {
|
||||||
inventory.Upgrades.pull({ _id: upgrade.ItemId.$oid });
|
inventory.Upgrades.pull({ _id: fromOid(upgrade.ItemId) });
|
||||||
} else {
|
} else {
|
||||||
addMods(inventory, [
|
addMods(inventory, [
|
||||||
{
|
{
|
||||||
@ -128,24 +128,14 @@ const getRandomRawRivenType = (): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface IArtifactTransmutationRequest {
|
interface IArtifactTransmutationRequest {
|
||||||
Upgrade: IAgnosticUpgradeClient;
|
Upgrade: IUpgradeFromClient;
|
||||||
LevelDiff: number;
|
LevelDiff: number;
|
||||||
Consumed: IAgnosticUpgradeClient[];
|
Consumed: IUpgradeFromClient[];
|
||||||
Cost: number;
|
Cost: number;
|
||||||
FusionPointCost: number;
|
FusionPointCost: number;
|
||||||
RivenTransmute?: boolean;
|
RivenTransmute?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IAgnosticUpgradeClient {
|
|
||||||
ItemType: string;
|
|
||||||
ItemId: IOid;
|
|
||||||
FromSKU: boolean;
|
|
||||||
UpgradeFingerprint: string;
|
|
||||||
PendingRerollFingerprint: string;
|
|
||||||
ItemCount: number;
|
|
||||||
LastAdded: IOid;
|
|
||||||
}
|
|
||||||
|
|
||||||
const specialModSets: string[][] = [
|
const specialModSets: string[][] = [
|
||||||
[
|
[
|
||||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod",
|
"/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod",
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
|
import { getAccountForRequest } from "@/src/services/loginService";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
const checkDailyMissionBonusController: RequestHandler = (_req, res) => {
|
export const checkDailyMissionBonusController: RequestHandler = async (req, res) => {
|
||||||
const data = Buffer.from([
|
const account = await getAccountForRequest(req);
|
||||||
0x44, 0x61, 0x69, 0x6c, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x6f, 0x6e, 0x75, 0x73, 0x3a,
|
const today = Math.trunc(Date.now() / 86400000) * 86400;
|
||||||
0x31, 0x2d, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x50, 0x56, 0x50, 0x57, 0x69, 0x6e, 0x42, 0x6f, 0x6e, 0x75, 0x73,
|
if (account.DailyFirstWinDate != today) {
|
||||||
0x3a, 0x31, 0x0a
|
res.send("DailyMissionBonus:1-DailyPVPWinBonus:1\n");
|
||||||
]);
|
} else {
|
||||||
res.writeHead(200, {
|
res.send("DailyMissionBonus:0-DailyPVPWinBonus:1\n");
|
||||||
"Content-Type": "text/html",
|
}
|
||||||
"Content-Length": data.length
|
|
||||||
});
|
|
||||||
res.end(data);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export { checkDailyMissionBonusController };
|
|
||||||
|
@ -4,21 +4,25 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { getRecipe } from "@/src/services/itemDataService";
|
import { getRecipe } from "@/src/services/itemDataService";
|
||||||
import { IOid } from "@/src/types/commonTypes";
|
import { IOid, IOidWithLegacySupport } from "@/src/types/commonTypes";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountForRequest } from "@/src/services/loginService";
|
||||||
import {
|
import {
|
||||||
getInventory,
|
getInventory,
|
||||||
updateCurrency,
|
updateCurrency,
|
||||||
addItem,
|
addItem,
|
||||||
addRecipes,
|
addRecipes,
|
||||||
occupySlot,
|
occupySlot,
|
||||||
combineInventoryChanges
|
combineInventoryChanges,
|
||||||
|
addKubrowPetPrint
|
||||||
} from "@/src/services/inventoryService";
|
} from "@/src/services/inventoryService";
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { InventorySlot, IPendingRecipeDatabase, Status } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
import { toOid2 } from "@/src/helpers/inventoryHelpers";
|
||||||
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
|
import { IRecipe } from "warframe-public-export-plus";
|
||||||
|
import { config } from "@/src/services/configService";
|
||||||
|
|
||||||
interface IClaimCompletedRecipeRequest {
|
interface IClaimCompletedRecipeRequest {
|
||||||
RecipeIds: IOid[];
|
RecipeIds: IOid[];
|
||||||
@ -26,10 +30,8 @@ interface IClaimCompletedRecipeRequest {
|
|||||||
|
|
||||||
export const claimCompletedRecipeController: RequestHandler = async (req, res) => {
|
export const claimCompletedRecipeController: RequestHandler = async (req, res) => {
|
||||||
const claimCompletedRecipeRequest = getJSONfromString<IClaimCompletedRecipeRequest>(String(req.body));
|
const claimCompletedRecipeRequest = getJSONfromString<IClaimCompletedRecipeRequest>(String(req.body));
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const account = await getAccountForRequest(req);
|
||||||
if (!accountId) throw new Error("no account id");
|
const inventory = await getInventory(account._id.toString());
|
||||||
|
|
||||||
const inventory = await getInventory(accountId);
|
|
||||||
const pendingRecipe = inventory.PendingRecipes.id(claimCompletedRecipeRequest.RecipeIds[0].$oid);
|
const pendingRecipe = inventory.PendingRecipes.id(claimCompletedRecipeRequest.RecipeIds[0].$oid);
|
||||||
if (!pendingRecipe) {
|
if (!pendingRecipe) {
|
||||||
throw new Error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$oid}`);
|
throw new Error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$oid}`);
|
||||||
@ -48,40 +50,14 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (req.query.cancel) {
|
if (req.query.cancel) {
|
||||||
const inventoryChanges: IInventoryChanges = {
|
const inventoryChanges: IInventoryChanges = {};
|
||||||
...updateCurrency(inventory, recipe.buildPrice * -1, false)
|
await refundRecipeIngredients(inventory, inventoryChanges, recipe, pendingRecipe);
|
||||||
};
|
|
||||||
|
|
||||||
const equipmentIngredients = new Set();
|
|
||||||
for (const category of ["LongGuns", "Pistols", "Melee"] as const) {
|
|
||||||
if (pendingRecipe[category]) {
|
|
||||||
pendingRecipe[category].forEach(item => {
|
|
||||||
const index = inventory[category].push(item) - 1;
|
|
||||||
inventoryChanges[category] ??= [];
|
|
||||||
inventoryChanges[category].push(inventory[category][index].toJSON<IEquipmentClient>());
|
|
||||||
equipmentIngredients.add(item.ItemType);
|
|
||||||
|
|
||||||
occupySlot(inventory, InventorySlot.WEAPONS, false);
|
|
||||||
inventoryChanges.WeaponBin ??= { Slots: 0 };
|
|
||||||
inventoryChanges.WeaponBin.Slots -= 1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const ingredient of recipe.ingredients) {
|
|
||||||
if (!equipmentIngredients.has(ingredient.ItemType)) {
|
|
||||||
combineInventoryChanges(
|
|
||||||
inventoryChanges,
|
|
||||||
await addItem(inventory, ingredient.ItemType, ingredient.ItemCount)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json(inventoryChanges); // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
|
res.json(inventoryChanges); // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
|
||||||
} else {
|
} else {
|
||||||
logger.debug("Claiming Recipe", { recipe, pendingRecipe });
|
logger.debug("Claiming Recipe", { recipe, pendingRecipe });
|
||||||
|
|
||||||
let BrandedSuits: undefined | IOid[];
|
let BrandedSuits: undefined | IOidWithLegacySupport[];
|
||||||
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
|
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
|
||||||
inventory.PendingSpectreLoadouts ??= [];
|
inventory.PendingSpectreLoadouts ??= [];
|
||||||
inventory.SpectreLoadouts ??= [];
|
inventory.SpectreLoadouts ??= [];
|
||||||
@ -106,7 +82,7 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
|
|||||||
inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)),
|
inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)),
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
BrandedSuits = [toOid(pendingRecipe.SuitToUnbrand!)];
|
BrandedSuits = [toOid2(pendingRecipe.SuitToUnbrand!, account.BuildLabel)];
|
||||||
}
|
}
|
||||||
|
|
||||||
let InventoryChanges: IInventoryChanges = {};
|
let InventoryChanges: IInventoryChanges = {};
|
||||||
@ -130,7 +106,24 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
|
|||||||
...updateCurrency(inventory, cost, true)
|
...updateCurrency(inventory, cost, true)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (recipe.secretIngredientAction != "SIA_UNBRAND") {
|
|
||||||
|
if (recipe.secretIngredientAction == "SIA_CREATE_KUBROW") {
|
||||||
|
const pet = inventory.KubrowPets.id(pendingRecipe.KubrowPet!)!;
|
||||||
|
if (pet.Details!.HatchDate!.getTime() > Date.now()) {
|
||||||
|
pet.Details!.HatchDate = new Date();
|
||||||
|
}
|
||||||
|
let canSetActive = true;
|
||||||
|
for (const pet of inventory.KubrowPets) {
|
||||||
|
if (pet.Details!.Status == Status.StatusAvailable) {
|
||||||
|
canSetActive = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pet.Details!.Status = canSetActive ? Status.StatusAvailable : Status.StatusStasis;
|
||||||
|
} else if (recipe.secretIngredientAction == "SIA_DISTILL_PRINT") {
|
||||||
|
const pet = inventory.KubrowPets.id(pendingRecipe.KubrowPet!)!;
|
||||||
|
addKubrowPetPrint(inventory, pet, InventoryChanges);
|
||||||
|
} else if (recipe.secretIngredientAction != "SIA_UNBRAND") {
|
||||||
InventoryChanges = {
|
InventoryChanges = {
|
||||||
...InventoryChanges,
|
...InventoryChanges,
|
||||||
...(await addItem(
|
...(await addItem(
|
||||||
@ -143,7 +136,46 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
|
|||||||
))
|
))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
config.claimingBlueprintRefundsIngredients &&
|
||||||
|
recipe.secretIngredientAction != "SIA_CREATE_KUBROW" // Can't refund the egg
|
||||||
|
) {
|
||||||
|
await refundRecipeIngredients(inventory, InventoryChanges, recipe, pendingRecipe);
|
||||||
|
}
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({ InventoryChanges, BrandedSuits });
|
res.json({ InventoryChanges, BrandedSuits });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const refundRecipeIngredients = async (
|
||||||
|
inventory: TInventoryDatabaseDocument,
|
||||||
|
inventoryChanges: IInventoryChanges,
|
||||||
|
recipe: IRecipe,
|
||||||
|
pendingRecipe: IPendingRecipeDatabase
|
||||||
|
): Promise<void> => {
|
||||||
|
updateCurrency(inventory, recipe.buildPrice * -1, false, inventoryChanges);
|
||||||
|
|
||||||
|
const equipmentIngredients = new Set();
|
||||||
|
for (const category of ["LongGuns", "Pistols", "Melee"] as const) {
|
||||||
|
if (pendingRecipe[category]) {
|
||||||
|
pendingRecipe[category].forEach(item => {
|
||||||
|
const index = inventory[category].push(item) - 1;
|
||||||
|
inventoryChanges[category] ??= [];
|
||||||
|
inventoryChanges[category].push(inventory[category][index].toJSON<IEquipmentClient>());
|
||||||
|
equipmentIngredients.add(item.ItemType);
|
||||||
|
|
||||||
|
occupySlot(inventory, InventorySlot.WEAPONS, false);
|
||||||
|
inventoryChanges.WeaponBin ??= { Slots: 0 };
|
||||||
|
inventoryChanges.WeaponBin.Slots -= 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const ingredient of recipe.ingredients) {
|
||||||
|
if (!equipmentIngredients.has(ingredient.ItemType)) {
|
||||||
|
combineInventoryChanges(
|
||||||
|
inventoryChanges,
|
||||||
|
await addItem(inventory, ingredient.ItemType, ingredient.ItemCount)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { ExportChallenges } from "warframe-public-export-plus";
|
||||||
|
|
||||||
|
export const claimJunctionChallengeRewardController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
const data = getJSONfromString<IClaimJunctionChallengeRewardRequest>(String(req.body));
|
||||||
|
const challengeProgress = inventory.ChallengeProgress.find(x => x.Name == data.Challenge)!;
|
||||||
|
if (challengeProgress.ReceivedJunctionReward) {
|
||||||
|
throw new Error(`attempt to double-claim junction reward`);
|
||||||
|
}
|
||||||
|
challengeProgress.ReceivedJunctionReward = true;
|
||||||
|
inventory.ClaimedJunctionChallengeRewards ??= [];
|
||||||
|
inventory.ClaimedJunctionChallengeRewards.push(data.Challenge);
|
||||||
|
const challengeMeta = Object.entries(ExportChallenges).find(arr => arr[0].endsWith("/" + data.Challenge))![1];
|
||||||
|
const inventoryChanges = {};
|
||||||
|
for (const reward of challengeMeta.countedRewards!) {
|
||||||
|
combineInventoryChanges(
|
||||||
|
inventoryChanges,
|
||||||
|
(await handleStoreItemAcquisition(reward.StoreItem, inventory, reward.ItemCount)).InventoryChanges
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
inventoryChanges: inventoryChanges // Yeah, it's "inventoryChanges" in the response here.
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IClaimJunctionChallengeRewardRequest {
|
||||||
|
Challenge: string;
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { getCalendarProgress, getInventory } from "@/src/services/inventoryService";
|
import { checkCalendarChallengeCompletion, getCalendarProgress, getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||||
import { getWorldState } from "@/src/services/worldStateService";
|
import { getWorldState } from "@/src/services/worldStateService";
|
||||||
@ -12,27 +12,23 @@ export const completeCalendarEventController: RequestHandler = async (req, res)
|
|||||||
const calendarProgress = getCalendarProgress(inventory);
|
const calendarProgress = getCalendarProgress(inventory);
|
||||||
const currentSeason = getWorldState().KnownCalendarSeasons[0];
|
const currentSeason = getWorldState().KnownCalendarSeasons[0];
|
||||||
let inventoryChanges: IInventoryChanges = {};
|
let inventoryChanges: IInventoryChanges = {};
|
||||||
let dayIndex = 0;
|
const dayIndex = calendarProgress.SeasonProgress.LastCompletedDayIdx + 1;
|
||||||
for (const day of currentSeason.Days) {
|
const day = currentSeason.Days[dayIndex];
|
||||||
if (day.events.length == 0 || day.events[0].type != "CET_CHALLENGE") {
|
if (day.events.length != 0) {
|
||||||
if (dayIndex == calendarProgress.SeasonProgress.LastCompletedDayIdx) {
|
if (day.events[0].type == "CET_CHALLENGE") {
|
||||||
if (day.events.length != 0) {
|
throw new Error(`completeCalendarEvent should not be used for challenges`);
|
||||||
const selection = day.events[parseInt(req.query.CompletedEventIdx as string)];
|
}
|
||||||
if (selection.type == "CET_REWARD") {
|
const selection = day.events[parseInt(req.query.CompletedEventIdx as string)];
|
||||||
inventoryChanges = (await handleStoreItemAcquisition(selection.reward!, inventory))
|
if (selection.type == "CET_REWARD") {
|
||||||
.InventoryChanges;
|
inventoryChanges = (await handleStoreItemAcquisition(selection.reward!, inventory)).InventoryChanges;
|
||||||
} else if (selection.type == "CET_UPGRADE") {
|
} else if (selection.type == "CET_UPGRADE") {
|
||||||
calendarProgress.YearProgress.Upgrades.push(selection.upgrade!);
|
calendarProgress.YearProgress.Upgrades.push(selection.upgrade!);
|
||||||
} else if (selection.type != "CET_PLOT") {
|
} else if (selection.type != "CET_PLOT") {
|
||||||
throw new Error(`unexpected selection type: ${selection.type}`);
|
throw new Error(`unexpected selection type: ${selection.type}`);
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++dayIndex;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
calendarProgress.SeasonProgress.LastCompletedDayIdx++;
|
calendarProgress.SeasonProgress.LastCompletedDayIdx = dayIndex;
|
||||||
|
checkCalendarChallengeCompletion(calendarProgress, currentSeason);
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({
|
res.json({
|
||||||
InventoryChanges: inventoryChanges,
|
InventoryChanges: inventoryChanges,
|
||||||
|
@ -62,7 +62,7 @@ export const confirmGuildInvitationGetController: RequestHandler = async (req, r
|
|||||||
await guild.save();
|
await guild.save();
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
...(await getGuildClient(guild, account._id.toString())),
|
...(await getGuildClient(guild, account)),
|
||||||
InventoryChanges: inventoryChanges
|
InventoryChanges: inventoryChanges
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountForRequest } from "@/src/services/loginService";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { Guild, GuildMember } from "@/src/models/guildModel";
|
import { Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
import { createUniqueClanName, getGuildClient, giveClanKey } from "@/src/services/guildService";
|
import { createUniqueClanName, getGuildClient, giveClanKey } from "@/src/services/guildService";
|
||||||
@ -7,11 +7,11 @@ import { getInventory } from "@/src/services/inventoryService";
|
|||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
|
||||||
export const createGuildController: RequestHandler = async (req, res) => {
|
export const createGuildController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const account = await getAccountForRequest(req);
|
||||||
const payload = getJSONfromString<ICreateGuildRequest>(String(req.body));
|
const payload = getJSONfromString<ICreateGuildRequest>(String(req.body));
|
||||||
|
|
||||||
// Remove pending applications for this account
|
// Remove pending applications for this account
|
||||||
await GuildMember.deleteMany({ accountId, status: 1 });
|
await GuildMember.deleteMany({ accountId: account._id, status: 1 });
|
||||||
|
|
||||||
// Create guild on database
|
// Create guild on database
|
||||||
const guild = new Guild({
|
const guild = new Guild({
|
||||||
@ -21,20 +21,20 @@ export const createGuildController: RequestHandler = async (req, res) => {
|
|||||||
|
|
||||||
// Create guild member on database
|
// Create guild member on database
|
||||||
await GuildMember.insertOne({
|
await GuildMember.insertOne({
|
||||||
accountId: accountId,
|
accountId: account._id,
|
||||||
guildId: guild._id,
|
guildId: guild._id,
|
||||||
status: 0,
|
status: 0,
|
||||||
rank: 0
|
rank: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
const inventory = await getInventory(accountId, "GuildId LevelKeys Recipes");
|
const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes");
|
||||||
inventory.GuildId = guild._id;
|
inventory.GuildId = guild._id;
|
||||||
const inventoryChanges: IInventoryChanges = {};
|
const inventoryChanges: IInventoryChanges = {};
|
||||||
giveClanKey(inventory, inventoryChanges);
|
giveClanKey(inventory, inventoryChanges);
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
...(await getGuildClient(guild, accountId)),
|
...(await getGuildClient(guild, account)),
|
||||||
InventoryChanges: inventoryChanges
|
InventoryChanges: inventoryChanges
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -4,9 +4,15 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
|
|||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
|
||||||
export const creditsController: RequestHandler = async (req, res) => {
|
export const creditsController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const inventory = (
|
||||||
|
await Promise.all([
|
||||||
const inventory = await getInventory(accountId, "RegularCredits TradesRemaining PremiumCreditsFree PremiumCredits");
|
getAccountIdForRequest(req),
|
||||||
|
getInventory(
|
||||||
|
req.query.accountId as string,
|
||||||
|
"RegularCredits TradesRemaining PremiumCreditsFree PremiumCredits"
|
||||||
|
)
|
||||||
|
])
|
||||||
|
)[1];
|
||||||
|
|
||||||
const response = {
|
const response = {
|
||||||
RegularCredits: inventory.RegularCredits,
|
RegularCredits: inventory.RegularCredits,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { ICrewMemberClient } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { ICrewMemberClient } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
@ -7,15 +8,23 @@ import { Types } from "mongoose";
|
|||||||
|
|
||||||
export const crewMembersController: RequestHandler = async (req, res) => {
|
export const crewMembersController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await getInventory(accountId, "CrewMembers");
|
const inventory = await getInventory(accountId, "CrewMembers NemesisHistory");
|
||||||
const data = getJSONfromString<ICrewMembersRequest>(String(req.body));
|
const data = getJSONfromString<ICrewMembersRequest>(String(req.body));
|
||||||
const dbCrewMember = inventory.CrewMembers.id(data.crewMember.ItemId.$oid)!;
|
if (data.crewMember.SecondInCommand) {
|
||||||
dbCrewMember.AssignedRole = data.crewMember.AssignedRole;
|
clearOnCall(inventory);
|
||||||
dbCrewMember.SkillEfficiency = data.crewMember.SkillEfficiency;
|
}
|
||||||
dbCrewMember.WeaponConfigIdx = data.crewMember.WeaponConfigIdx;
|
if (data.crewMember.ItemId.$oid == "000000000000000000000000") {
|
||||||
dbCrewMember.WeaponId = new Types.ObjectId(data.crewMember.WeaponId.$oid);
|
const convertedNemesis = inventory.NemesisHistory!.find(x => x.fp == data.crewMember.NemesisFingerprint)!;
|
||||||
dbCrewMember.Configs = data.crewMember.Configs;
|
convertedNemesis.SecondInCommand = data.crewMember.SecondInCommand;
|
||||||
dbCrewMember.SecondInCommand = data.crewMember.SecondInCommand;
|
} else {
|
||||||
|
const dbCrewMember = inventory.CrewMembers.id(data.crewMember.ItemId.$oid)!;
|
||||||
|
dbCrewMember.AssignedRole = data.crewMember.AssignedRole;
|
||||||
|
dbCrewMember.SkillEfficiency = data.crewMember.SkillEfficiency;
|
||||||
|
dbCrewMember.WeaponConfigIdx = data.crewMember.WeaponConfigIdx;
|
||||||
|
dbCrewMember.WeaponId = new Types.ObjectId(data.crewMember.WeaponId.$oid);
|
||||||
|
dbCrewMember.Configs = data.crewMember.Configs;
|
||||||
|
dbCrewMember.SecondInCommand = data.crewMember.SecondInCommand;
|
||||||
|
}
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({
|
res.json({
|
||||||
crewMemberId: data.crewMember.ItemId.$oid,
|
crewMemberId: data.crewMember.ItemId.$oid,
|
||||||
@ -26,3 +35,20 @@ export const crewMembersController: RequestHandler = async (req, res) => {
|
|||||||
interface ICrewMembersRequest {
|
interface ICrewMembersRequest {
|
||||||
crewMember: ICrewMemberClient;
|
crewMember: ICrewMemberClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const clearOnCall = (inventory: TInventoryDatabaseDocument): void => {
|
||||||
|
for (const cm of inventory.CrewMembers) {
|
||||||
|
if (cm.SecondInCommand) {
|
||||||
|
cm.SecondInCommand = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inventory.NemesisHistory) {
|
||||||
|
for (const cm of inventory.NemesisHistory) {
|
||||||
|
if (cm.SecondInCommand) {
|
||||||
|
cm.SecondInCommand = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
107
src/controllers/api/crewShipFusionController.ts
Normal file
107
src/controllers/api/crewShipFusionController.ts
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { addMiscItems, freeUpSlot, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
|
import { ICrewShipComponentFingerprint, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { ExportCustoms, ExportDojoRecipes } from "warframe-public-export-plus";
|
||||||
|
|
||||||
|
export const crewShipFusionController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
const payload = getJSONfromString<ICrewShipFusionRequest>(String(req.body));
|
||||||
|
|
||||||
|
const isWeapon = inventory.CrewShipWeapons.id(payload.PartA.$oid);
|
||||||
|
const itemA = isWeapon ?? inventory.CrewShipWeaponSkins.id(payload.PartA.$oid)!;
|
||||||
|
const category = isWeapon ? "CrewShipWeapons" : "CrewShipWeaponSkins";
|
||||||
|
const salvageCategory = isWeapon ? "CrewShipSalvagedWeapons" : "CrewShipSalvagedWeaponSkins";
|
||||||
|
const itemB = inventory[payload.SourceRecipe ? salvageCategory : category].id(payload.PartB.$oid)!;
|
||||||
|
const tierA = itemA.ItemType.charCodeAt(itemA.ItemType.length - 1) - 65;
|
||||||
|
const tierB = itemB.ItemType.charCodeAt(itemB.ItemType.length - 1) - 65;
|
||||||
|
|
||||||
|
const inventoryChanges: IInventoryChanges = {};
|
||||||
|
|
||||||
|
// Charge partial repair cost if fusing with an identified but unrepaired part
|
||||||
|
if (payload.SourceRecipe) {
|
||||||
|
const recipe = ExportDojoRecipes.research[payload.SourceRecipe];
|
||||||
|
updateCurrency(inventory, Math.round(recipe.price * 0.4), false, inventoryChanges);
|
||||||
|
const miscItemChanges = recipe.ingredients.map(x => ({ ...x, ItemCount: Math.round(x.ItemCount * -0.4) }));
|
||||||
|
addMiscItems(inventory, miscItemChanges);
|
||||||
|
inventoryChanges.MiscItems = miscItemChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove inferior item
|
||||||
|
if (payload.SourceRecipe) {
|
||||||
|
inventory[salvageCategory].pull({ _id: payload.PartB.$oid });
|
||||||
|
inventoryChanges.RemovedIdItems = [{ ItemId: payload.PartB }];
|
||||||
|
} else {
|
||||||
|
const inferiorId = tierA < tierB ? payload.PartA : payload.PartB;
|
||||||
|
inventory[category].pull({ _id: inferiorId.$oid });
|
||||||
|
inventoryChanges.RemovedIdItems = [{ ItemId: inferiorId }];
|
||||||
|
freeUpSlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS);
|
||||||
|
inventoryChanges[InventorySlot.RJ_COMPONENT_AND_ARMAMENTS] = { count: -1, platinum: 0, Slots: 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upgrade superior item
|
||||||
|
const superiorItem = tierA < tierB ? itemB : itemA;
|
||||||
|
const inferiorItem = tierA < tierB ? itemA : itemB;
|
||||||
|
const fingerprint: ICrewShipComponentFingerprint = JSON.parse(
|
||||||
|
superiorItem.UpgradeFingerprint!
|
||||||
|
) as ICrewShipComponentFingerprint;
|
||||||
|
const inferiorFingerprint: ICrewShipComponentFingerprint = inferiorItem.UpgradeFingerprint
|
||||||
|
? (JSON.parse(inferiorItem.UpgradeFingerprint) as ICrewShipComponentFingerprint)
|
||||||
|
: { compat: "", buffs: [] };
|
||||||
|
if (isWeapon) {
|
||||||
|
for (let i = 0; i != fingerprint.buffs.length; ++i) {
|
||||||
|
const buffA = fingerprint.buffs[i];
|
||||||
|
const buffB = i < inferiorFingerprint.buffs.length ? inferiorFingerprint.buffs[i] : undefined;
|
||||||
|
const fvalA = buffA.Value / 0x3fffffff;
|
||||||
|
const fvalB = (buffB?.Value ?? 0) / 0x3fffffff;
|
||||||
|
const percA = 0.3 + fvalA * (0.6 - 0.3);
|
||||||
|
const percB = 0.3 + fvalB * (0.6 - 0.3);
|
||||||
|
const newPerc = Math.min(0.6, Math.max(percA, percB) * FUSE_MULTIPLIERS[Math.abs(tierA - tierB)]);
|
||||||
|
const newFval = (newPerc - 0.3) / (0.6 - 0.3);
|
||||||
|
buffA.Value = Math.trunc(newFval * 0x3fffffff);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const superiorMeta = ExportCustoms[superiorItem.ItemType].randomisedUpgrades ?? [];
|
||||||
|
const inferiorMeta = ExportCustoms[inferiorItem.ItemType].randomisedUpgrades ?? [];
|
||||||
|
for (let i = 0; i != inferiorFingerprint.buffs.length; ++i) {
|
||||||
|
const buffA = fingerprint.buffs[i];
|
||||||
|
const buffB = inferiorFingerprint.buffs[i];
|
||||||
|
const fvalA = buffA.Value / 0x3fffffff;
|
||||||
|
const fvalB = buffB.Value / 0x3fffffff;
|
||||||
|
const rangeA = superiorMeta[i].range;
|
||||||
|
const rangeB = inferiorMeta[i].range;
|
||||||
|
const percA = rangeA[0] + fvalA * (rangeA[1] - rangeA[0]);
|
||||||
|
const percB = rangeB[0] + fvalB * (rangeB[1] - rangeB[0]);
|
||||||
|
const newPerc = Math.min(rangeA[1], Math.max(percA, percB) * FUSE_MULTIPLIERS[Math.abs(tierA - tierB)]);
|
||||||
|
const newFval = (newPerc - rangeA[0]) / (rangeA[1] - rangeA[0]);
|
||||||
|
buffA.Value = Math.trunc(newFval * 0x3fffffff);
|
||||||
|
}
|
||||||
|
if (inferiorFingerprint.SubroutineIndex) {
|
||||||
|
const useSuperiorSubroutine = tierA < tierB ? !payload.UseSubroutineA : payload.UseSubroutineA;
|
||||||
|
if (!useSuperiorSubroutine) {
|
||||||
|
fingerprint.SubroutineIndex = inferiorFingerprint.SubroutineIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
superiorItem.UpgradeFingerprint = JSON.stringify(fingerprint);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
inventoryChanges[category] = [superiorItem.toJSON() as any];
|
||||||
|
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
InventoryChanges: inventoryChanges
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ICrewShipFusionRequest {
|
||||||
|
PartA: IOid;
|
||||||
|
PartB: IOid;
|
||||||
|
SourceRecipe: string;
|
||||||
|
UseSubroutineA: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FUSE_MULTIPLIERS = [1.1, 1.05, 1.02];
|
@ -1,60 +1,529 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { TEndlessXpCategory } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IEndlessXpReward, IInventoryClient, TEndlessXpCategory } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { logger } from "@/src/utils/logger";
|
||||||
|
import { ExportRewards, ICountedStoreItem } from "warframe-public-export-plus";
|
||||||
|
import { getRandomElement } from "@/src/services/rngService";
|
||||||
|
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||||
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
|
||||||
export const endlessXpController: RequestHandler = async (req, res) => {
|
export const endlessXpController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await getInventory(accountId);
|
|
||||||
const payload = getJSONfromString<IEndlessXpRequest>(String(req.body));
|
const payload = getJSONfromString<IEndlessXpRequest>(String(req.body));
|
||||||
|
if (payload.Mode == "r") {
|
||||||
inventory.EndlessXP ??= [];
|
const inventory = await getInventory(accountId, "EndlessXP");
|
||||||
const entry = inventory.EndlessXP.find(x => x.Category == payload.Category);
|
inventory.EndlessXP ??= [];
|
||||||
if (entry) {
|
let entry = inventory.EndlessXP.find(x => x.Category == payload.Category);
|
||||||
entry.Choices = payload.Choices;
|
if (!entry) {
|
||||||
} else {
|
entry = {
|
||||||
inventory.EndlessXP.push({
|
Category: payload.Category,
|
||||||
Category: payload.Category,
|
Earn: 0,
|
||||||
Choices: payload.Choices
|
Claim: 0,
|
||||||
});
|
Choices: payload.Choices,
|
||||||
}
|
PendingRewards: []
|
||||||
await inventory.save();
|
};
|
||||||
|
inventory.EndlessXP.push(entry);
|
||||||
res.json({
|
|
||||||
NewProgress: {
|
|
||||||
Category: payload.Category,
|
|
||||||
Earn: 0,
|
|
||||||
Claim: 0,
|
|
||||||
BonusAvailable: {
|
|
||||||
$date: {
|
|
||||||
$numberLong: "9999999999999"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Expiry: {
|
|
||||||
$date: {
|
|
||||||
$numberLong: "9999999999999"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Choices: payload.Choices,
|
|
||||||
PendingRewards: [
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 190,
|
|
||||||
Rewards: [
|
|
||||||
{
|
|
||||||
StoreItem: "/Lotus/StoreItems/Upgrades/Mods/Aura/PlayerHealthAuraMod",
|
|
||||||
ItemCount: 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
// ...
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
const weekStart = 1734307200_000 + Math.trunc((Date.now() - 1734307200_000) / 604800000) * 604800000;
|
||||||
|
const weekEnd = weekStart + 604800000;
|
||||||
|
|
||||||
|
entry.Earn = 0;
|
||||||
|
entry.Claim = 0;
|
||||||
|
entry.BonusAvailable = new Date(weekStart);
|
||||||
|
entry.Expiry = new Date(weekEnd);
|
||||||
|
entry.Choices = payload.Choices;
|
||||||
|
entry.PendingRewards =
|
||||||
|
payload.Category == "EXC_HARD"
|
||||||
|
? generateHardModeRewards(payload.Choices)
|
||||||
|
: generateNormalModeRewards(payload.Choices);
|
||||||
|
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
NewProgress: inventory.toJSON<IInventoryClient>().EndlessXP!.find(x => x.Category == payload.Category)!
|
||||||
|
});
|
||||||
|
} else if (payload.Mode == "c") {
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
const entry = inventory.EndlessXP!.find(x => x.Category == payload.Category)!;
|
||||||
|
const inventoryChanges: IInventoryChanges = {};
|
||||||
|
for (const reward of entry.PendingRewards) {
|
||||||
|
if (entry.Claim < reward.RequiredTotalXp && reward.RequiredTotalXp <= entry.Earn) {
|
||||||
|
combineInventoryChanges(
|
||||||
|
inventoryChanges,
|
||||||
|
(
|
||||||
|
await handleStoreItemAcquisition(
|
||||||
|
reward.Rewards[0].StoreItem,
|
||||||
|
inventory,
|
||||||
|
reward.Rewards[0].ItemCount
|
||||||
|
)
|
||||||
|
).InventoryChanges
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entry.Claim = entry.Earn;
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
InventoryChanges: inventoryChanges,
|
||||||
|
ClaimedXp: entry.Claim
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
||||||
|
throw new Error(`unexpected endlessXp mode: ${payload.Mode}`);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IEndlessXpRequest {
|
type IEndlessXpRequest =
|
||||||
Mode: string; // "r"
|
| {
|
||||||
Category: TEndlessXpCategory;
|
Mode: "r";
|
||||||
Choices: string[];
|
Category: TEndlessXpCategory;
|
||||||
}
|
Choices: string[];
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
Mode: "c" | "something else";
|
||||||
|
Category: TEndlessXpCategory;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateRandomRewards = (deckName: string): ICountedStoreItem[] => {
|
||||||
|
const reward = getRandomElement(ExportRewards[deckName][0])!;
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
StoreItem: reward.type,
|
||||||
|
ItemCount: reward.itemCount
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalModeChosenRewards: Record<string, string[]> = {
|
||||||
|
Excalibur: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Excalibur/RadialJavelinAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburBlueprint"
|
||||||
|
],
|
||||||
|
Trinity: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinityHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinityChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Trinity/EnergyVampireAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinitySystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinityBlueprint"
|
||||||
|
],
|
||||||
|
Ember: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Ember/WorldOnFireAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberBlueprint"
|
||||||
|
],
|
||||||
|
Loki: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKIHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKIChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Loki/InvisibilityAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKISystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKIBlueprint"
|
||||||
|
],
|
||||||
|
Mag: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Mag/CrushAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagBlueprint"
|
||||||
|
],
|
||||||
|
Rhino: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Rhino/RhinoChargeAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoBlueprint"
|
||||||
|
],
|
||||||
|
Ash: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Ninja/GlaiveAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshBlueprint"
|
||||||
|
],
|
||||||
|
Frost: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Frost/IceShieldAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostBlueprint"
|
||||||
|
],
|
||||||
|
Nyx: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Jade/SelfBulletAttractorAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxBlueprint"
|
||||||
|
],
|
||||||
|
Saryn: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Saryn/PoisonAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynBlueprint"
|
||||||
|
],
|
||||||
|
Vauban: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Trapper/LevTrapAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperBlueprint"
|
||||||
|
],
|
||||||
|
Nova: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/AntiMatter/MolecularPrimeAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaBlueprint"
|
||||||
|
],
|
||||||
|
Nekros: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Necro/CloneTheDeadAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroBlueprint"
|
||||||
|
],
|
||||||
|
Valkyr: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Berserker/IntimidateAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerBlueprint"
|
||||||
|
],
|
||||||
|
Oberon: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Paladin/RegenerationAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinBlueprint"
|
||||||
|
],
|
||||||
|
Hydroid: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Pirate/CannonBarrageAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidBlueprint"
|
||||||
|
],
|
||||||
|
Mirage: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Harlequin/LightAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinBlueprint"
|
||||||
|
],
|
||||||
|
Limbo: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Magician/TearInSpaceAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianBlueprint"
|
||||||
|
],
|
||||||
|
Mesa: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Cowgirl/GunFuPvPAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerBlueprint"
|
||||||
|
],
|
||||||
|
Chroma: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Dragon/DragonLuckAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaBlueprint"
|
||||||
|
],
|
||||||
|
Atlas: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Brawler/BrawlerPassiveAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerBlueprint"
|
||||||
|
],
|
||||||
|
Ivara: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Ranger/RangerStealAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerBlueprint"
|
||||||
|
],
|
||||||
|
Inaros: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummyHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummyChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Sandman/SandmanSwarmAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummySystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummyBlueprint"
|
||||||
|
],
|
||||||
|
Titania: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairyHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairyChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Fairy/FairyFlightAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairySystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairyBlueprint"
|
||||||
|
],
|
||||||
|
Nidus: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Infestation/InfestPodsAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusBlueprint"
|
||||||
|
],
|
||||||
|
Octavia: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Bard/BardCharmAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaBlueprint"
|
||||||
|
],
|
||||||
|
Harrow: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Priest/PriestPactAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestBlueprint"
|
||||||
|
],
|
||||||
|
Gara: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Glass/GlassFragmentAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassBlueprint"
|
||||||
|
],
|
||||||
|
Khora: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Khora/KhoraCrackAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraBlueprint"
|
||||||
|
],
|
||||||
|
Revenant: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Revenant/RevenantMarkAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantBlueprint"
|
||||||
|
],
|
||||||
|
Garuda: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Garuda/GarudaUnstoppableAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaBlueprint"
|
||||||
|
],
|
||||||
|
Baruuk: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/Pacifist/PacifistFistAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistBlueprint"
|
||||||
|
],
|
||||||
|
Hildryn: [
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeHelmetBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeChassisBlueprint",
|
||||||
|
"/Lotus/StoreItems/Powersuits/IronFrame/IronFrameStripAugmentCard",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeSystemsBlueprint",
|
||||||
|
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeBlueprint"
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateNormalModeRewards = (choices: string[]): IEndlessXpReward[] => {
|
||||||
|
const choiceRewards = normalModeChosenRewards[choices[0]];
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
RequiredTotalXp: 190,
|
||||||
|
Rewards: generateRandomRewards(
|
||||||
|
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalSilverRewards"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RequiredTotalXp: 400,
|
||||||
|
Rewards: [
|
||||||
|
{
|
||||||
|
StoreItem: choiceRewards[0],
|
||||||
|
ItemCount: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RequiredTotalXp: 630,
|
||||||
|
Rewards: generateRandomRewards(
|
||||||
|
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalSilverRewards"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RequiredTotalXp: 890,
|
||||||
|
Rewards: generateRandomRewards(
|
||||||
|
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalMODRewards"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RequiredTotalXp: 1190,
|
||||||
|
Rewards: [
|
||||||
|
{
|
||||||
|
StoreItem: choiceRewards[1],
|
||||||
|
ItemCount: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RequiredTotalXp: 1540,
|
||||||
|
Rewards: generateRandomRewards(
|
||||||
|
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalGoldRewards"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RequiredTotalXp: 1950,
|
||||||
|
Rewards: [
|
||||||
|
{
|
||||||
|
StoreItem: choiceRewards[2],
|
||||||
|
ItemCount: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RequiredTotalXp: 2430,
|
||||||
|
Rewards: [
|
||||||
|
{
|
||||||
|
StoreItem: choiceRewards[3],
|
||||||
|
ItemCount: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RequiredTotalXp: 2990,
|
||||||
|
Rewards: generateRandomRewards(
|
||||||
|
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalArcaneRewards"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RequiredTotalXp: 3640,
|
||||||
|
Rewards: [
|
||||||
|
{
|
||||||
|
StoreItem: choiceRewards[4],
|
||||||
|
ItemCount: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const hardModeChosenRewards: Record<string, string> = {
|
||||||
|
Braton: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BratonIncarnonUnlocker",
|
||||||
|
Lato: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/LatoIncarnonUnlocker",
|
||||||
|
Skana: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/SkanaIncarnonUnlocker",
|
||||||
|
Paris: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/ParisIncarnonUnlocker",
|
||||||
|
Kunai: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/KunaiIncarnonUnlocker",
|
||||||
|
Boar: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BoarIncarnonUnlocker",
|
||||||
|
Gammacor: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/GammacorIncarnonUnlocker",
|
||||||
|
Anku: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/AnkuIncarnonUnlocker",
|
||||||
|
Gorgon: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/GorgonIncarnonUnlocker",
|
||||||
|
Angstrum: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/AngstrumIncarnonUnlocker",
|
||||||
|
Bo: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/BoIncarnonUnlocker",
|
||||||
|
Latron: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/LatronIncarnonUnlocker",
|
||||||
|
Furis: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/FurisIncarnonUnlocker",
|
||||||
|
Furax: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/FuraxIncarnonUnlocker",
|
||||||
|
Strun: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/StrunIncarnonUnlocker",
|
||||||
|
Lex: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/LexIncarnonUnlocker",
|
||||||
|
Magistar: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/MagistarIncarnonUnlocker",
|
||||||
|
Boltor: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BoltorIncarnonUnlocker",
|
||||||
|
Bronco: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/BroncoIncarnonUnlocker",
|
||||||
|
CeramicDagger: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/CeramicDaggerIncarnonUnlocker",
|
||||||
|
Torid: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/ToridIncarnonUnlocker",
|
||||||
|
DualToxocyst: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/DualToxocystIncarnonUnlocker",
|
||||||
|
DualIchor: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/DualIchorIncarnonUnlocker",
|
||||||
|
Miter: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/MiterIncarnonUnlocker",
|
||||||
|
Atomos: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/AtomosIncarnonUnlocker",
|
||||||
|
AckAndBrunt: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/AckAndBruntIncarnonUnlocker",
|
||||||
|
Soma: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/SomaIncarnonUnlocker",
|
||||||
|
Vasto: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/VastoIncarnonUnlocker",
|
||||||
|
NamiSolo: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/NamiSoloIncarnonUnlocker",
|
||||||
|
Burston: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BurstonIncarnonUnlocker",
|
||||||
|
Zylok: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/ZylokIncarnonUnlocker",
|
||||||
|
Sibear: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/SibearIncarnonUnlocker",
|
||||||
|
Dread: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/DreadIncarnonUnlocker",
|
||||||
|
Despair: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/DespairIncarnonUnlocker",
|
||||||
|
Hate: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/HateIncarnonUnlocker",
|
||||||
|
Dera: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/DeraIncarnonUnlocker",
|
||||||
|
Cestra: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/CestraIncarnonUnlocker",
|
||||||
|
Okina: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/OkinaIncarnonUnlocker",
|
||||||
|
Sybaris: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/SybarisIncarnonUnlocker",
|
||||||
|
Sicarus: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/SicarusIncarnonUnlocker",
|
||||||
|
RivenPrimary: "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawRifleRandomMod",
|
||||||
|
RivenSecondary: "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawPistolRandomMod",
|
||||||
|
RivenMelee: "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawMeleeRandomMod",
|
||||||
|
Kuva: "/Lotus/Types/Game/DuviriEndless/CircuitSteelPathBIGKuvaReward"
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateHardModeRewards = (choices: string[]): IEndlessXpReward[] => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
RequiredTotalXp: 285,
|
||||||
|
Rewards: generateRandomRewards(
|
||||||
|
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSilverRewards"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RequiredTotalXp: 600,
|
||||||
|
Rewards: generateRandomRewards(
|
||||||
|
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathArcaneRewards"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RequiredTotalXp: 945,
|
||||||
|
Rewards: generateRandomRewards(
|
||||||
|
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSilverRewards"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RequiredTotalXp: 1335,
|
||||||
|
Rewards: generateRandomRewards(
|
||||||
|
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSilverRewards"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RequiredTotalXp: 1785,
|
||||||
|
Rewards: [
|
||||||
|
{
|
||||||
|
StoreItem: hardModeChosenRewards[choices[0]],
|
||||||
|
ItemCount: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RequiredTotalXp: 2310,
|
||||||
|
Rewards: generateRandomRewards(
|
||||||
|
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathGoldRewards"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RequiredTotalXp: 2925,
|
||||||
|
Rewards: generateRandomRewards(
|
||||||
|
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathGoldRewards"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RequiredTotalXp: 3645,
|
||||||
|
Rewards: generateRandomRewards(
|
||||||
|
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathArcaneRewards"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RequiredTotalXp: 4485,
|
||||||
|
Rewards: generateRandomRewards(
|
||||||
|
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSteelEssenceRewards"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RequiredTotalXp: 5460,
|
||||||
|
Rewards: [
|
||||||
|
{
|
||||||
|
StoreItem: hardModeChosenRewards[choices[1]],
|
||||||
|
ItemCount: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
@ -30,15 +30,14 @@ export const fishmongerController: RequestHandler = async (req, res) => {
|
|||||||
miscItemChanges.push({ ItemType: fish.ItemType, ItemCount: fish.ItemCount * -1 });
|
miscItemChanges.push({ ItemType: fish.ItemType, ItemCount: fish.ItemCount * -1 });
|
||||||
}
|
}
|
||||||
addMiscItems(inventory, miscItemChanges);
|
addMiscItems(inventory, miscItemChanges);
|
||||||
let affiliationMod;
|
if (gainedStanding && syndicateTag) addStanding(inventory, syndicateTag, gainedStanding);
|
||||||
if (gainedStanding && syndicateTag) affiliationMod = addStanding(inventory, syndicateTag, gainedStanding);
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({
|
res.json({
|
||||||
InventoryChanges: {
|
InventoryChanges: {
|
||||||
MiscItems: miscItemChanges
|
MiscItems: miscItemChanges
|
||||||
},
|
},
|
||||||
SyndicateTag: syndicateTag,
|
SyndicateTag: syndicateTag,
|
||||||
StandingChange: affiliationMod?.Standing || 0
|
StandingChange: gainedStanding
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ export const focusController: RequestHandler = async (req, res) => {
|
|||||||
inventory.FocusAbility ??= focusType;
|
inventory.FocusAbility ??= focusType;
|
||||||
inventory.FocusUpgrades.push({ ItemType: focusType });
|
inventory.FocusUpgrades.push({ ItemType: focusType });
|
||||||
if (inventory.FocusXP) {
|
if (inventory.FocusXP) {
|
||||||
inventory.FocusXP[focusPolarity] -= cost;
|
inventory.FocusXP[focusPolarity]! -= cost;
|
||||||
}
|
}
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({
|
res.json({
|
||||||
@ -64,7 +64,9 @@ export const focusController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
res.end();
|
res.json({
|
||||||
|
FocusUpgrade: { ItemType: focusType }
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case FocusOperation.UnlockUpgrade: {
|
case FocusOperation.UnlockUpgrade: {
|
||||||
@ -76,7 +78,7 @@ export const focusController: RequestHandler = async (req, res) => {
|
|||||||
cost += ExportFocusUpgrades[focusType].baseFocusPointCost;
|
cost += ExportFocusUpgrades[focusType].baseFocusPointCost;
|
||||||
inventory.FocusUpgrades.push({ ItemType: focusType, Level: 0 });
|
inventory.FocusUpgrades.push({ ItemType: focusType, Level: 0 });
|
||||||
}
|
}
|
||||||
inventory.FocusXP![focusPolarity] -= cost;
|
inventory.FocusXP![focusPolarity]! -= cost;
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({
|
res.json({
|
||||||
FocusTypes: request.FocusTypes,
|
FocusTypes: request.FocusTypes,
|
||||||
@ -94,7 +96,7 @@ export const focusController: RequestHandler = async (req, res) => {
|
|||||||
const focusUpgradeDb = inventory.FocusUpgrades.find(entry => entry.ItemType == focusUpgrade.ItemType)!;
|
const focusUpgradeDb = inventory.FocusUpgrades.find(entry => entry.ItemType == focusUpgrade.ItemType)!;
|
||||||
focusUpgradeDb.Level = focusUpgrade.Level;
|
focusUpgradeDb.Level = focusUpgrade.Level;
|
||||||
}
|
}
|
||||||
inventory.FocusXP![focusPolarity] -= cost;
|
inventory.FocusXP![focusPolarity]! -= cost;
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({
|
res.json({
|
||||||
FocusInfos: request.FocusInfos,
|
FocusInfos: request.FocusInfos,
|
||||||
@ -121,7 +123,7 @@ export const focusController: RequestHandler = async (req, res) => {
|
|||||||
const request = JSON.parse(String(req.body)) as IUnbindUpgradeRequest;
|
const request = JSON.parse(String(req.body)) as IUnbindUpgradeRequest;
|
||||||
const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]);
|
const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]);
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
inventory.FocusXP![focusPolarity] -= 750_000 * request.FocusTypes.length;
|
inventory.FocusXP![focusPolarity]! -= 750_000 * request.FocusTypes.length;
|
||||||
addMiscItems(inventory, [
|
addMiscItems(inventory, [
|
||||||
{
|
{
|
||||||
ItemType: "/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantItem",
|
ItemType: "/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantItem",
|
||||||
@ -166,8 +168,10 @@ export const focusController: RequestHandler = async (req, res) => {
|
|||||||
shard.ItemCount *= -1;
|
shard.ItemCount *= -1;
|
||||||
}
|
}
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
inventory.FocusXP ??= { AP_POWER: 0, AP_TACTIC: 0, AP_DEFENSE: 0, AP_ATTACK: 0, AP_WARD: 0 };
|
const polarity = request.Polarity;
|
||||||
inventory.FocusXP[request.Polarity] += xp;
|
inventory.FocusXP ??= {};
|
||||||
|
inventory.FocusXP[polarity] ??= 0;
|
||||||
|
inventory.FocusXP[polarity] += xp;
|
||||||
addMiscItems(inventory, request.Shards);
|
addMiscItems(inventory, request.Shards);
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
break;
|
break;
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
import { DailyDeal } from "@/src/models/worldStateModel";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
export const getDailyDealStockLevelsController: RequestHandler = (req, res) => {
|
export const getDailyDealStockLevelsController: RequestHandler = async (req, res) => {
|
||||||
|
const dailyDeal = (await DailyDeal.findOne({ StoreItem: req.query.productName }, "AmountSold"))!;
|
||||||
res.json({
|
res.json({
|
||||||
StoreItem: req.query.productName,
|
StoreItem: req.query.productName,
|
||||||
AmountSold: 0
|
AmountSold: dailyDeal.AmountSold
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,15 +1,54 @@
|
|||||||
import { Request, Response } from "express";
|
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
|
import { Friendship } from "@/src/models/friendModel";
|
||||||
|
import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "@/src/services/friendService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { IFriendInfo } from "@/src/types/friendTypes";
|
||||||
|
import { Request, RequestHandler, Response } from "express";
|
||||||
|
|
||||||
// POST with {} instead of GET as of 38.5.0
|
// POST with {} instead of GET as of 38.5.0
|
||||||
const getFriendsController = (_request: Request, response: Response): void => {
|
export const getFriendsController: RequestHandler = async (req: Request, res: Response) => {
|
||||||
response.writeHead(200, {
|
const accountId = await getAccountIdForRequest(req);
|
||||||
//Connection: "keep-alive",
|
const response: IGetFriendsResponse = {
|
||||||
//"Content-Encoding": "gzip",
|
Current: [],
|
||||||
"Content-Type": "text/html",
|
IncomingFriendRequests: [],
|
||||||
// charset: "UTF - 8",
|
OutgoingFriendRequests: []
|
||||||
"Content-Length": "3"
|
};
|
||||||
});
|
const [internalFriendships, externalFriendships] = await Promise.all([
|
||||||
response.end(Buffer.from([0x7b, 0x7d, 0x0a]));
|
Friendship.find({ owner: accountId }),
|
||||||
|
Friendship.find({ friend: accountId }, "owner Note")
|
||||||
|
]);
|
||||||
|
for (const externalFriendship of externalFriendships) {
|
||||||
|
if (!internalFriendships.find(x => x.friend.equals(externalFriendship.owner))) {
|
||||||
|
response.IncomingFriendRequests.push({
|
||||||
|
_id: toOid(externalFriendship.owner),
|
||||||
|
Note: externalFriendship.Note
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const internalFriendship of internalFriendships) {
|
||||||
|
const friendInfo: IFriendInfo = {
|
||||||
|
_id: toOid(internalFriendship.friend)
|
||||||
|
};
|
||||||
|
if (externalFriendships.find(x => x.owner.equals(internalFriendship.friend))) {
|
||||||
|
response.Current.push(friendInfo);
|
||||||
|
} else {
|
||||||
|
response.OutgoingFriendRequests.push(friendInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const promises: Promise<void>[] = [];
|
||||||
|
for (const arr of Object.values(response)) {
|
||||||
|
for (const friendInfo of arr) {
|
||||||
|
promises.push(addAccountDataToFriendInfo(friendInfo));
|
||||||
|
promises.push(addInventoryDataToFriendInfo(friendInfo));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
|
res.json(response);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { getFriendsController };
|
// interface IGetFriendsResponse {
|
||||||
|
// Current: IFriendInfo[];
|
||||||
|
// IncomingFriendRequests: IFriendInfo[];
|
||||||
|
// OutgoingFriendRequests: IFriendInfo[];
|
||||||
|
// }
|
||||||
|
type IGetFriendsResponse = Record<"Current" | "IncomingFriendRequests" | "OutgoingFriendRequests", IFriendInfo[]>;
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { Guild } from "@/src/models/guildModel";
|
import { Guild } from "@/src/models/guildModel";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountForRequest } from "@/src/services/loginService";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { createUniqueClanName, getGuildClient } from "@/src/services/guildService";
|
import { createUniqueClanName, getGuildClient } from "@/src/services/guildService";
|
||||||
|
|
||||||
export const getGuildController: RequestHandler = async (req, res) => {
|
export const getGuildController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const account = await getAccountForRequest(req);
|
||||||
const inventory = await getInventory(accountId, "GuildId");
|
const inventory = await getInventory(account._id.toString(), "GuildId");
|
||||||
if (inventory.GuildId) {
|
if (inventory.GuildId) {
|
||||||
const guild = await Guild.findById(inventory.GuildId);
|
const guild = await Guild.findById(inventory.GuildId);
|
||||||
if (guild) {
|
if (guild) {
|
||||||
@ -24,7 +24,7 @@ export const getGuildController: RequestHandler = async (req, res) => {
|
|||||||
guild.CeremonyResetDate = undefined;
|
guild.CeremonyResetDate = undefined;
|
||||||
await guild.save();
|
await guild.save();
|
||||||
}
|
}
|
||||||
res.json(await getGuildClient(guild, accountId));
|
res.json(await getGuildClient(guild, account));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import { RequestHandler } from "express";
|
|||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { Guild } from "@/src/models/guildModel";
|
import { Guild } from "@/src/models/guildModel";
|
||||||
import { getDojoClient } from "@/src/services/guildService";
|
import { getDojoClient } from "@/src/services/guildService";
|
||||||
|
import { Account } from "@/src/models/loginModel";
|
||||||
|
|
||||||
export const getGuildDojoController: RequestHandler = async (req, res) => {
|
export const getGuildDojoController: RequestHandler = async (req, res) => {
|
||||||
const guildId = req.query.guildId as string;
|
const guildId = req.query.guildId as string;
|
||||||
@ -25,7 +26,8 @@ export const getGuildDojoController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const payload: IGetGuildDojoRequest = req.body ? (JSON.parse(String(req.body)) as IGetGuildDojoRequest) : {};
|
const payload: IGetGuildDojoRequest = req.body ? (JSON.parse(String(req.body)) as IGetGuildDojoRequest) : {};
|
||||||
res.json(await getDojoClient(guild, 0, payload.ComponentId));
|
const account = await Account.findById(req.query.accountId as string);
|
||||||
|
res.json(await getDojoClient(guild, 0, payload.ComponentId, account?.BuildLabel));
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IGetGuildDojoRequest {
|
interface IGetGuildDojoRequest {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
import { Account, Ignore } from "@/src/models/loginModel";
|
import { Account, Ignore } from "@/src/models/loginModel";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { IFriendInfo } from "@/src/types/guildTypes";
|
import { IFriendInfo } from "@/src/types/friendTypes";
|
||||||
import { parallelForeach } from "@/src/utils/async-utils";
|
import { parallelForeach } from "@/src/utils/async-utils";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ import { config } from "@/src/services/configService";
|
|||||||
import allShipFeatures from "@/static/fixed_responses/allShipFeatures.json";
|
import allShipFeatures from "@/static/fixed_responses/allShipFeatures.json";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { createGarden, 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 { toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
import { IGetShipResponse } from "@/src/types/shipTypes";
|
import { IGetShipResponse } from "@/src/types/shipTypes";
|
||||||
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
|
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
|
||||||
@ -21,7 +20,6 @@ export const getShipController: RequestHandler = async (req, res) => {
|
|||||||
|
|
||||||
const personalRooms = personalRoomsDb.toJSON<IPersonalRoomsClient>();
|
const personalRooms = personalRoomsDb.toJSON<IPersonalRoomsClient>();
|
||||||
const loadout = await getLoadout(accountId);
|
const loadout = await getLoadout(accountId);
|
||||||
const ship = await getShip(personalRoomsDb.activeShipId, "ShipAttachments SkinFlavourItem");
|
|
||||||
|
|
||||||
const getShipResponse: IGetShipResponse = {
|
const getShipResponse: IGetShipResponse = {
|
||||||
ShipOwnerId: accountId,
|
ShipOwnerId: accountId,
|
||||||
@ -31,8 +29,8 @@ export const getShipController: RequestHandler = async (req, res) => {
|
|||||||
ShipId: toOid(personalRoomsDb.activeShipId),
|
ShipId: toOid(personalRoomsDb.activeShipId),
|
||||||
ShipInterior: {
|
ShipInterior: {
|
||||||
Colors: personalRooms.ShipInteriorColors,
|
Colors: personalRooms.ShipInteriorColors,
|
||||||
ShipAttachments: ship.ShipAttachments,
|
ShipAttachments: { HOOD_ORNAMENT: "" },
|
||||||
SkinFlavourItem: ship.SkinFlavourItem
|
SkinFlavourItem: ""
|
||||||
},
|
},
|
||||||
FavouriteLoadoutId: personalRooms.Ship.FavouriteLoadoutId
|
FavouriteLoadoutId: personalRooms.Ship.FavouriteLoadoutId
|
||||||
? toOid(personalRooms.Ship.FavouriteLoadoutId)
|
? toOid(personalRooms.Ship.FavouriteLoadoutId)
|
||||||
|
@ -1,14 +1,29 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getVendorManifestByTypeName } from "@/src/services/serversideVendorsService";
|
import { applyStandingToVendorManifest, getVendorManifestByTypeName } from "@/src/services/serversideVendorsService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { config } from "@/src/services/configService";
|
||||||
|
|
||||||
export const getVendorInfoController: RequestHandler = (req, res) => {
|
export const getVendorInfoController: RequestHandler = async (req, res) => {
|
||||||
if (typeof req.query.vendor == "string") {
|
let manifest = getVendorManifestByTypeName(req.query.vendor as string);
|
||||||
const manifest = getVendorManifestByTypeName(req.query.vendor);
|
if (!manifest) {
|
||||||
if (!manifest) {
|
throw new Error(`Unknown vendor: ${req.query.vendor as string}`);
|
||||||
throw new Error(`Unknown vendor: ${req.query.vendor}`);
|
|
||||||
}
|
|
||||||
res.json(manifest);
|
|
||||||
} else {
|
|
||||||
res.status(400).end();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For testing purposes, authenticating with this endpoint is optional here, but would be required on live.
|
||||||
|
if (req.query.accountId) {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
manifest = applyStandingToVendorManifest(inventory, manifest);
|
||||||
|
if (config.dev?.keepVendorsExpired) {
|
||||||
|
manifest = {
|
||||||
|
VendorInfo: {
|
||||||
|
...manifest.VendorInfo,
|
||||||
|
Expiry: { $date: { $numberLong: "0" } }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json(manifest);
|
||||||
};
|
};
|
||||||
|
@ -1,16 +1,34 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { Account } from "@/src/models/loginModel";
|
import { Account } from "@/src/models/loginModel";
|
||||||
|
import { areFriends } from "@/src/services/friendService";
|
||||||
import { createMessage } from "@/src/services/inboxService";
|
import { createMessage } from "@/src/services/inboxService";
|
||||||
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
import {
|
||||||
|
combineInventoryChanges,
|
||||||
|
getEffectiveAvatarImageType,
|
||||||
|
getInventory,
|
||||||
|
updateCurrency
|
||||||
|
} from "@/src/services/inventoryService";
|
||||||
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||||
|
import { handleDailyDealPurchase, handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||||
import { IOid } from "@/src/types/commonTypes";
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
import { IPurchaseParams } from "@/src/types/purchaseTypes";
|
import { IPurchaseParams, IPurchaseResponse, PurchaseSource } from "@/src/types/purchaseTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { ExportFlavour } from "warframe-public-export-plus";
|
import { ExportBundles, ExportFlavour } from "warframe-public-export-plus";
|
||||||
|
|
||||||
|
const checkPurchaseParams = (params: IPurchaseParams): boolean => {
|
||||||
|
switch (params.Source) {
|
||||||
|
case PurchaseSource.Market:
|
||||||
|
return params.UsePremium;
|
||||||
|
|
||||||
|
case PurchaseSource.DailyDeal:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
export const giftingController: RequestHandler = async (req, res) => {
|
export const giftingController: RequestHandler = async (req, res) => {
|
||||||
const data = getJSONfromString<IGiftingRequest>(String(req.body));
|
const data = getJSONfromString<IGiftingRequest>(String(req.body));
|
||||||
if (data.PurchaseParams.Source != 0 || !data.PurchaseParams.UsePremium) {
|
if (!checkPurchaseParams(data.PurchaseParams)) {
|
||||||
throw new Error(`unexpected purchase params in gifting request: ${String(req.body)}`);
|
throw new Error(`unexpected purchase params in gifting request: ${String(req.body)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,8 +48,11 @@ export const giftingController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cannot gift to players who have gifting disabled.
|
// Cannot gift to players who have gifting disabled.
|
||||||
// TODO: Also consider GIFT_MODE_FRIENDS once friends are implemented
|
const senderAccount = await getAccountForRequest(req);
|
||||||
if (inventory.Settings?.GiftMode == "GIFT_MODE_NONE") {
|
if (
|
||||||
|
inventory.Settings?.GiftMode == "GIFT_MODE_NONE" ||
|
||||||
|
(inventory.Settings?.GiftMode == "GIFT_MODE_FRIENDS" && !(await areFriends(account._id, senderAccount._id)))
|
||||||
|
) {
|
||||||
res.status(400).send("17").end();
|
res.status(400).send("17").end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -40,11 +61,7 @@ export const giftingController: RequestHandler = async (req, res) => {
|
|||||||
// TODO: Cannot gift archwing items to players that have not completed the archwing quest. (Code 7)
|
// TODO: Cannot gift 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)
|
// 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());
|
||||||
const senderInventory = await getInventory(
|
|
||||||
senderAccount._id.toString(),
|
|
||||||
"PremiumCredits PremiumCreditsFree ActiveAvatarImageType GiftsRemaining"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (senderInventory.GiftsRemaining == 0) {
|
if (senderInventory.GiftsRemaining == 0) {
|
||||||
res.status(400).send("10").end();
|
res.status(400).send("10").end();
|
||||||
@ -52,7 +69,23 @@ export const giftingController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
senderInventory.GiftsRemaining -= 1;
|
senderInventory.GiftsRemaining -= 1;
|
||||||
|
|
||||||
updateCurrency(senderInventory, data.PurchaseParams.ExpectedPrice, true);
|
const response: IPurchaseResponse = {
|
||||||
|
InventoryChanges: {}
|
||||||
|
};
|
||||||
|
if (data.PurchaseParams.Source == PurchaseSource.DailyDeal) {
|
||||||
|
await handleDailyDealPurchase(senderInventory, data.PurchaseParams, response);
|
||||||
|
} else {
|
||||||
|
updateCurrency(senderInventory, data.PurchaseParams.ExpectedPrice, true, response.InventoryChanges);
|
||||||
|
}
|
||||||
|
if (data.PurchaseParams.StoreItem in ExportBundles) {
|
||||||
|
const bundle = ExportBundles[data.PurchaseParams.StoreItem];
|
||||||
|
if (bundle.giftingBonus) {
|
||||||
|
combineInventoryChanges(
|
||||||
|
response.InventoryChanges,
|
||||||
|
(await handleStoreItemAcquisition(bundle.giftingBonus, senderInventory)).InventoryChanges
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
await senderInventory.save();
|
await senderInventory.save();
|
||||||
|
|
||||||
const senderName = getSuffixedName(senderAccount);
|
const senderName = getSuffixedName(senderAccount);
|
||||||
@ -71,7 +104,7 @@ export const giftingController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
sub: "/Lotus/Language/Menu/GiftReceivedSubject",
|
sub: "/Lotus/Language/Menu/GiftReceivedSubject",
|
||||||
icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
|
icon: ExportFlavour[getEffectiveAvatarImageType(senderInventory)].icon,
|
||||||
gifts: [
|
gifts: [
|
||||||
{
|
{
|
||||||
GiftType: data.PurchaseParams.StoreItem
|
GiftType: data.PurchaseParams.StoreItem
|
||||||
@ -80,7 +113,7 @@ export const giftingController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
res.end();
|
res.json(response);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IGiftingRequest {
|
interface IGiftingRequest {
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
scaleRequiredCount,
|
scaleRequiredCount,
|
||||||
setGuildTechLogState
|
setGuildTechLogState
|
||||||
} from "@/src/services/guildService";
|
} from "@/src/services/guildService";
|
||||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
|
import { ExportDojoRecipes, ExportRailjackWeapons } from "warframe-public-export-plus";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import {
|
import {
|
||||||
addCrewShipWeaponSkin,
|
addCrewShipWeaponSkin,
|
||||||
@ -104,7 +104,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
|||||||
) {
|
) {
|
||||||
throw new Error(`unexpected TechProductCategory: ${data.TechProductCategory}`);
|
throw new Error(`unexpected TechProductCategory: ${data.TechProductCategory}`);
|
||||||
}
|
}
|
||||||
if (!inventory[getSalvageCategory(data.TechProductCategory)].id(data.CategoryItemId)) {
|
if (!inventory[getSalvageCategory(data.TechProductCategory)].id(data.CategoryItemId!)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`no item with id ${data.CategoryItemId} in ${getSalvageCategory(data.TechProductCategory)} array`
|
`no item with id ${data.CategoryItemId} in ${getSalvageCategory(data.TechProductCategory)} array`
|
||||||
);
|
);
|
||||||
@ -442,6 +442,7 @@ const finishComponentRepair = (
|
|||||||
...(category == "CrewShipWeaponSkins"
|
...(category == "CrewShipWeaponSkins"
|
||||||
? addCrewShipWeaponSkin(inventory, salvageItem.ItemType, salvageItem.UpgradeFingerprint)
|
? addCrewShipWeaponSkin(inventory, salvageItem.ItemType, salvageItem.UpgradeFingerprint)
|
||||||
: addEquipment(inventory, category, salvageItem.ItemType, {
|
: addEquipment(inventory, category, salvageItem.ItemType, {
|
||||||
|
UpgradeType: ExportRailjackWeapons[salvageItem.ItemType].defaultUpgrades?.[0].ItemType,
|
||||||
UpgradeFingerprint: salvageItem.UpgradeFingerprint
|
UpgradeFingerprint: salvageItem.UpgradeFingerprint
|
||||||
})),
|
})),
|
||||||
...occupySlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS, false)
|
...occupySlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS, false)
|
||||||
|
@ -1,17 +1,24 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountForRequest } from "@/src/services/loginService";
|
||||||
import { createNewSession } from "@/src/managers/sessionManager";
|
import { createNewSession } from "@/src/managers/sessionManager";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { ISession } from "@/src/types/session";
|
import { ISession } from "@/src/types/session";
|
||||||
|
import { JSONParse } from "json-with-bigint";
|
||||||
|
import { toOid2, version_compare } from "@/src/helpers/inventoryHelpers";
|
||||||
|
|
||||||
const hostSessionController: RequestHandler = async (req, res) => {
|
const hostSessionController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const account = await getAccountForRequest(req);
|
||||||
const hostSessionRequest = JSON.parse(req.body as string) as ISession;
|
const hostSessionRequest = JSONParse(String(req.body)) as ISession;
|
||||||
logger.debug("HostSession Request", { hostSessionRequest });
|
logger.debug("HostSession Request", { hostSessionRequest });
|
||||||
const session = createNewSession(hostSessionRequest, accountId);
|
const session = createNewSession(hostSessionRequest, account._id);
|
||||||
logger.debug(`New Session Created`, { session });
|
logger.debug(`New Session Created`, { session });
|
||||||
|
|
||||||
res.json({ sessionId: { $oid: session.sessionId }, rewardSeed: 99999999 });
|
if (account.BuildLabel && version_compare(account.BuildLabel, "2015.03.21.08.17") < 0) {
|
||||||
|
// U15 or below
|
||||||
|
res.send(session.sessionId.toString());
|
||||||
|
} else {
|
||||||
|
res.json({ sessionId: toOid2(session.sessionId, account.BuildLabel), rewardSeed: 99999999 });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export { hostSessionController };
|
export { hostSessionController };
|
||||||
|
@ -9,7 +9,12 @@ import {
|
|||||||
getMessage
|
getMessage
|
||||||
} from "@/src/services/inboxService";
|
} from "@/src/services/inboxService";
|
||||||
import { getAccountForRequest, getAccountFromSuffixedName, getSuffixedName } from "@/src/services/loginService";
|
import { getAccountForRequest, getAccountFromSuffixedName, getSuffixedName } from "@/src/services/loginService";
|
||||||
import { addItems, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
|
import {
|
||||||
|
addItems,
|
||||||
|
combineInventoryChanges,
|
||||||
|
getEffectiveAvatarImageType,
|
||||||
|
getInventory
|
||||||
|
} from "@/src/services/inventoryService";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { ExportFlavour } from "warframe-public-export-plus";
|
import { ExportFlavour } from "warframe-public-export-plus";
|
||||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||||
@ -88,7 +93,7 @@ export const inboxController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
sub: "/Lotus/Language/Menu/GiftReceivedConfirmationSubject",
|
sub: "/Lotus/Language/Menu/GiftReceivedConfirmationSubject",
|
||||||
icon: ExportFlavour[inventory.ActiveAvatarImageType].icon,
|
icon: ExportFlavour[getEffectiveAvatarImageType(inventory)].icon,
|
||||||
highPriority: true
|
highPriority: true
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountForRequest } from "@/src/services/loginService";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { getInventory, addMiscItems, updateCurrency, addRecipes, freeUpSlot } from "@/src/services/inventoryService";
|
import { getInventory, addMiscItems, updateCurrency, addRecipes, freeUpSlot } from "@/src/services/inventoryService";
|
||||||
import { IOid } from "@/src/types/commonTypes";
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
@ -12,7 +12,7 @@ import {
|
|||||||
} from "@/src/types/inventoryTypes/inventoryTypes";
|
} from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { ExportMisc } from "warframe-public-export-plus";
|
import { ExportMisc } from "warframe-public-export-plus";
|
||||||
import { getRecipe } from "@/src/services/itemDataService";
|
import { getRecipe } from "@/src/services/itemDataService";
|
||||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
import { toMongoDate, version_compare } from "@/src/helpers/inventoryHelpers";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { colorToShard } from "@/src/helpers/shardHelper";
|
import { colorToShard } from "@/src/helpers/shardHelper";
|
||||||
import { config } from "@/src/services/configService";
|
import { config } from "@/src/services/configService";
|
||||||
@ -23,15 +23,16 @@ import {
|
|||||||
} from "@/src/services/infestedFoundryService";
|
} from "@/src/services/infestedFoundryService";
|
||||||
|
|
||||||
export const infestedFoundryController: RequestHandler = async (req, res) => {
|
export const infestedFoundryController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const account = await getAccountForRequest(req);
|
||||||
switch (req.query.mode) {
|
switch (req.query.mode) {
|
||||||
case "s": {
|
case "s": {
|
||||||
// shard installation
|
// shard installation
|
||||||
const request = getJSONfromString<IShardInstallRequest>(String(req.body));
|
const request = getJSONfromString<IShardInstallRequest>(String(req.body));
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(account._id.toString());
|
||||||
const suit = inventory.Suits.id(request.SuitId.$oid)!;
|
const suit = inventory.Suits.id(request.SuitId.$oid)!;
|
||||||
if (!suit.ArchonCrystalUpgrades || suit.ArchonCrystalUpgrades.length != 5) {
|
suit.ArchonCrystalUpgrades ??= [];
|
||||||
suit.ArchonCrystalUpgrades = [{}, {}, {}, {}, {}];
|
while (suit.ArchonCrystalUpgrades.length < request.Slot) {
|
||||||
|
suit.ArchonCrystalUpgrades.push({});
|
||||||
}
|
}
|
||||||
suit.ArchonCrystalUpgrades[request.Slot] = {
|
suit.ArchonCrystalUpgrades[request.Slot] = {
|
||||||
UpgradeType: request.UpgradeType,
|
UpgradeType: request.UpgradeType,
|
||||||
@ -56,7 +57,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
case "x": {
|
case "x": {
|
||||||
// shard removal
|
// shard removal
|
||||||
const request = getJSONfromString<IShardUninstallRequest>(String(req.body));
|
const request = getJSONfromString<IShardUninstallRequest>(String(req.body));
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(account._id.toString());
|
||||||
const suit = inventory.Suits.id(request.SuitId.$oid)!;
|
const suit = inventory.Suits.id(request.SuitId.$oid)!;
|
||||||
|
|
||||||
const miscItemChanges: IMiscItem[] = [];
|
const miscItemChanges: IMiscItem[] = [];
|
||||||
@ -70,18 +71,30 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
ItemCount: 1
|
ItemCount: 1
|
||||||
});
|
});
|
||||||
addMiscItems(inventory, miscItemChanges);
|
addMiscItems(inventory, miscItemChanges);
|
||||||
|
|
||||||
|
// consume resources
|
||||||
|
if (!config.infiniteHelminthMaterials) {
|
||||||
|
let type: string;
|
||||||
|
let count: number;
|
||||||
|
if (account.BuildLabel && version_compare(account.BuildLabel, "2025.05.20.10.18") < 0) {
|
||||||
|
// < 38.6.0
|
||||||
|
type = "/Lotus/Types/Items/InfestedFoundry/HelminthBile";
|
||||||
|
count = 300;
|
||||||
|
} else {
|
||||||
|
// >= 38.6.0
|
||||||
|
type =
|
||||||
|
archonCrystalRemovalResource[
|
||||||
|
suit.ArchonCrystalUpgrades![request.Slot].Color!.replace("_MYTHIC", "")
|
||||||
|
];
|
||||||
|
count = suit.ArchonCrystalUpgrades![request.Slot].Color!.indexOf("_MYTHIC") != -1 ? 300 : 150;
|
||||||
|
}
|
||||||
|
inventory.InfestedFoundry!.Resources!.find(x => x.ItemType == type)!.Count -= count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove from suit
|
// remove from suit
|
||||||
suit.ArchonCrystalUpgrades![request.Slot] = {};
|
suit.ArchonCrystalUpgrades![request.Slot].UpgradeType = undefined;
|
||||||
|
suit.ArchonCrystalUpgrades![request.Slot].Color = undefined;
|
||||||
if (!config.infiniteHelminthMaterials) {
|
|
||||||
// remove bile
|
|
||||||
const bile = inventory.InfestedFoundry!.Resources!.find(
|
|
||||||
x => x.ItemType == "/Lotus/Types/Items/InfestedFoundry/HelminthBile"
|
|
||||||
)!;
|
|
||||||
bile.Count -= 300;
|
|
||||||
}
|
|
||||||
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
|
|
||||||
@ -99,7 +112,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
case "n": {
|
case "n": {
|
||||||
// name the beast
|
// name the beast
|
||||||
const request = getJSONfromString<IHelminthNameRequest>(String(req.body));
|
const request = getJSONfromString<IHelminthNameRequest>(String(req.body));
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(account._id.toString());
|
||||||
inventory.InfestedFoundry ??= {};
|
inventory.InfestedFoundry ??= {};
|
||||||
inventory.InfestedFoundry.Name = request.newName;
|
inventory.InfestedFoundry.Name = request.newName;
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
@ -122,7 +135,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const request = getJSONfromString<IHelminthFeedRequest>(String(req.body));
|
const request = getJSONfromString<IHelminthFeedRequest>(String(req.body));
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(account._id.toString());
|
||||||
inventory.InfestedFoundry ??= {};
|
inventory.InfestedFoundry ??= {};
|
||||||
inventory.InfestedFoundry.Resources ??= [];
|
inventory.InfestedFoundry.Resources ??= [];
|
||||||
|
|
||||||
@ -218,7 +231,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
case "o": {
|
case "o": {
|
||||||
// offerings update
|
// offerings update
|
||||||
const request = getJSONfromString<IHelminthOfferingsUpdate>(String(req.body));
|
const request = getJSONfromString<IHelminthOfferingsUpdate>(String(req.body));
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(account._id.toString());
|
||||||
inventory.InfestedFoundry ??= {};
|
inventory.InfestedFoundry ??= {};
|
||||||
inventory.InfestedFoundry.InvigorationIndex = request.OfferingsIndex;
|
inventory.InfestedFoundry.InvigorationIndex = request.OfferingsIndex;
|
||||||
inventory.InfestedFoundry.InvigorationSuitOfferings = request.SuitTypes;
|
inventory.InfestedFoundry.InvigorationSuitOfferings = request.SuitTypes;
|
||||||
@ -239,7 +252,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
case "a": {
|
case "a": {
|
||||||
// subsume warframe
|
// subsume warframe
|
||||||
const request = getJSONfromString<IHelminthSubsumeRequest>(String(req.body));
|
const request = getJSONfromString<IHelminthSubsumeRequest>(String(req.body));
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(account._id.toString());
|
||||||
const recipe = getRecipe(request.Recipe)!;
|
const recipe = getRecipe(request.Recipe)!;
|
||||||
if (!config.infiniteHelminthMaterials) {
|
if (!config.infiniteHelminthMaterials) {
|
||||||
for (const ingredient of recipe.secretIngredients!) {
|
for (const ingredient of recipe.secretIngredients!) {
|
||||||
@ -289,7 +302,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
|
|
||||||
case "r": {
|
case "r": {
|
||||||
// rush subsume
|
// rush subsume
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(account._id.toString());
|
||||||
const currencyChanges = updateCurrency(inventory, 50, true);
|
const currencyChanges = updateCurrency(inventory, 50, true);
|
||||||
const recipeChanges = handleSubsumeCompletion(inventory);
|
const recipeChanges = handleSubsumeCompletion(inventory);
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
@ -307,7 +320,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
|
|
||||||
case "u": {
|
case "u": {
|
||||||
const request = getJSONfromString<IHelminthInvigorationRequest>(String(req.body));
|
const request = getJSONfromString<IHelminthInvigorationRequest>(String(req.body));
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(account._id.toString());
|
||||||
const suit = inventory.Suits.id(request.SuitId.$oid)!;
|
const suit = inventory.Suits.id(request.SuitId.$oid)!;
|
||||||
const upgradesExpiry = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
const upgradesExpiry = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
||||||
suit.OffensiveUpgrade = request.OffensiveUpgradeType;
|
suit.OffensiveUpgrade = request.OffensiveUpgradeType;
|
||||||
@ -340,7 +353,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "custom_unlockall": {
|
case "custom_unlockall": {
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(account._id.toString());
|
||||||
inventory.InfestedFoundry ??= {};
|
inventory.InfestedFoundry ??= {};
|
||||||
inventory.InfestedFoundry.XP ??= 0;
|
inventory.InfestedFoundry.XP ??= 0;
|
||||||
if (151875_00 > inventory.InfestedFoundry.XP) {
|
if (151875_00 > inventory.InfestedFoundry.XP) {
|
||||||
@ -439,3 +452,12 @@ const apetiteModel = (x: number): number => {
|
|||||||
}
|
}
|
||||||
return 3;
|
return 3;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const archonCrystalRemovalResource: Record<string, string> = {
|
||||||
|
ACC_RED: "/Lotus/Types/Items/InfestedFoundry/HelminthOxides",
|
||||||
|
ACC_YELLOW: "/Lotus/Types/Items/InfestedFoundry/HelminthBile",
|
||||||
|
ACC_BLUE: "/Lotus/Types/Items/InfestedFoundry/HelminthSynthetics",
|
||||||
|
ACC_GREEN: "/Lotus/Types/Items/InfestedFoundry/HelminthBiotics",
|
||||||
|
ACC_ORANGE: "/Lotus/Types/Items/InfestedFoundry/HelminthPheromones",
|
||||||
|
ACC_PURPLE: "/Lotus/Types/Items/InfestedFoundry/HelminthCalx"
|
||||||
|
};
|
||||||
|
@ -6,13 +6,7 @@ import allDialogue from "@/static/fixed_responses/allDialogue.json";
|
|||||||
import { ILoadoutDatabase } from "@/src/types/saveLoadoutTypes";
|
import { ILoadoutDatabase } from "@/src/types/saveLoadoutTypes";
|
||||||
import { IInventoryClient, IShipInventory, equipmentKeys } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IInventoryClient, IShipInventory, equipmentKeys } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { IPolarity, ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { IPolarity, ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
import {
|
import { ExportCustoms, ExportFlavour, ExportResources, ExportVirtuals } from "warframe-public-export-plus";
|
||||||
ExportCustoms,
|
|
||||||
ExportFlavour,
|
|
||||||
ExportRegions,
|
|
||||||
ExportResources,
|
|
||||||
ExportVirtuals
|
|
||||||
} from "warframe-public-export-plus";
|
|
||||||
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "@/src/services/infestedFoundryService";
|
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "@/src/services/infestedFoundryService";
|
||||||
import {
|
import {
|
||||||
addMiscItems,
|
addMiscItems,
|
||||||
@ -22,9 +16,16 @@ import {
|
|||||||
generateRewardSeed
|
generateRewardSeed
|
||||||
} from "@/src/services/inventoryService";
|
} from "@/src/services/inventoryService";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { catBreadHash } from "@/src/helpers/stringHelpers";
|
import { addString, catBreadHash } from "@/src/helpers/stringHelpers";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { isNemesisCompatibleWithVersion } from "@/src/helpers/nemesisHelpers";
|
import { getNemesisManifest } from "@/src/helpers/nemesisHelpers";
|
||||||
|
import { getPersonalRooms } from "@/src/services/personalRoomsService";
|
||||||
|
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
|
||||||
|
import { Ship } from "@/src/models/shipModel";
|
||||||
|
import { toLegacyOid, toOid, version_compare } from "@/src/helpers/inventoryHelpers";
|
||||||
|
import { Inbox } from "@/src/models/inboxModel";
|
||||||
|
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
||||||
|
import { DailyDeal } from "@/src/models/worldStateModel";
|
||||||
|
|
||||||
export const inventoryController: RequestHandler = async (request, response) => {
|
export const inventoryController: RequestHandler = async (request, response) => {
|
||||||
const account = await getAccountForRequest(request);
|
const account = await getAccountForRequest(request);
|
||||||
@ -38,6 +39,8 @@ export const inventoryController: RequestHandler = async (request, response) =>
|
|||||||
|
|
||||||
// Handle daily reset
|
// Handle daily reset
|
||||||
if (!inventory.NextRefill || Date.now() >= inventory.NextRefill.getTime()) {
|
if (!inventory.NextRefill || Date.now() >= inventory.NextRefill.getTime()) {
|
||||||
|
const today = Math.trunc(Date.now() / 86400000);
|
||||||
|
|
||||||
for (const key of allDailyAffiliationKeys) {
|
for (const key of allDailyAffiliationKeys) {
|
||||||
inventory[key] = 16000 + inventory.PlayerLevel * 500;
|
inventory[key] = 16000 + inventory.PlayerLevel * 500;
|
||||||
}
|
}
|
||||||
@ -48,12 +51,12 @@ export const inventoryController: RequestHandler = async (request, response) =>
|
|||||||
inventory.LibraryAvailableDailyTaskInfo = createLibraryDailyTask();
|
inventory.LibraryAvailableDailyTaskInfo = createLibraryDailyTask();
|
||||||
|
|
||||||
if (inventory.NextRefill) {
|
if (inventory.NextRefill) {
|
||||||
|
const lastLoginDay = Math.trunc(inventory.NextRefill.getTime() / 86400000) - 1;
|
||||||
|
const daysPassed = today - lastLoginDay;
|
||||||
|
|
||||||
if (config.noArgonCrystalDecay) {
|
if (config.noArgonCrystalDecay) {
|
||||||
inventory.FoundToday = undefined;
|
inventory.FoundToday = undefined;
|
||||||
} else {
|
} else {
|
||||||
const lastLoginDay = Math.trunc(inventory.NextRefill.getTime() / 86400000) - 1;
|
|
||||||
const today = Math.trunc(Date.now() / 86400000);
|
|
||||||
const daysPassed = today - lastLoginDay;
|
|
||||||
for (let i = 0; i != daysPassed; ++i) {
|
for (let i = 0; i != daysPassed; ++i) {
|
||||||
const numArgonCrystals =
|
const numArgonCrystals =
|
||||||
inventory.MiscItems.find(x => x.ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal")
|
inventory.MiscItems.find(x => x.ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal")
|
||||||
@ -85,11 +88,29 @@ export const inventoryController: RequestHandler = async (request, response) =>
|
|||||||
inventory.FoundToday = undefined;
|
inventory.FoundToday = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (inventory.UsedDailyDeals.length != 0) {
|
||||||
|
if (daysPassed == 1) {
|
||||||
|
const todayAt0Utc = today * 86400000;
|
||||||
|
const darvoIndex = Math.trunc((todayAt0Utc - 25200000) / (26 * unixTimesInMs.hour));
|
||||||
|
const darvoStart = darvoIndex * (26 * unixTimesInMs.hour) + 25200000;
|
||||||
|
const darvoOid =
|
||||||
|
((darvoStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "adc51a72f7324d95";
|
||||||
|
const deal = await DailyDeal.findById(darvoOid);
|
||||||
|
if (deal) {
|
||||||
|
inventory.UsedDailyDeals = inventory.UsedDailyDeals.filter(x => x == deal.StoreItem); // keep only the deal that came into this new day with us
|
||||||
|
} else {
|
||||||
|
inventory.UsedDailyDeals = [];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
inventory.UsedDailyDeals = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanupInventory(inventory);
|
cleanupInventory(inventory);
|
||||||
|
|
||||||
inventory.NextRefill = new Date((Math.trunc(Date.now() / 86400000) + 1) * 86400000);
|
inventory.NextRefill = new Date((today + 1) * 86400000); // tomorrow at 0 UTC
|
||||||
//await inventory.save();
|
//await inventory.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,13 +151,20 @@ export const getInventoryResponse = async (
|
|||||||
xpBasedLevelCapDisabled: boolean,
|
xpBasedLevelCapDisabled: boolean,
|
||||||
buildLabel: string | undefined
|
buildLabel: string | undefined
|
||||||
): Promise<IInventoryClient> => {
|
): Promise<IInventoryClient> => {
|
||||||
const inventoryWithLoadOutPresets = await inventory.populate<{ LoadOutPresets: ILoadoutDatabase }>(
|
const [inventoryWithLoadOutPresets, ships, latestMessage] = await Promise.all([
|
||||||
"LoadOutPresets"
|
inventory.populate<{ LoadOutPresets: ILoadoutDatabase }>("LoadOutPresets"),
|
||||||
);
|
Ship.find({ ShipOwnerId: inventory.accountOwnerId }),
|
||||||
const inventoryWithLoadOutPresetsAndShips = await inventoryWithLoadOutPresets.populate<{ Ships: IShipInventory }>(
|
Inbox.findOne({ ownerId: inventory.accountOwnerId }, "_id").sort({ date: -1 })
|
||||||
"Ships"
|
]);
|
||||||
);
|
const inventoryResponse = inventoryWithLoadOutPresets.toJSON<IInventoryClient>();
|
||||||
const inventoryResponse = inventoryWithLoadOutPresetsAndShips.toJSON<IInventoryClient>();
|
inventoryResponse.Ships = ships.map(x => x.toJSON<IShipInventory>());
|
||||||
|
|
||||||
|
// In case mission inventory update added an inbox message, we need to send the Mailbox part so the client knows to refresh it.
|
||||||
|
if (latestMessage) {
|
||||||
|
inventoryResponse.Mailbox = {
|
||||||
|
LastInboxId: toOid(latestMessage._id)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (config.infiniteCredits) {
|
if (config.infiniteCredits) {
|
||||||
inventoryResponse.RegularCredits = 999999999;
|
inventoryResponse.RegularCredits = 999999999;
|
||||||
@ -164,18 +192,6 @@ export const getInventoryResponse = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.unlockAllMissions) {
|
|
||||||
inventoryResponse.Missions = [];
|
|
||||||
for (const tag of Object.keys(ExportRegions)) {
|
|
||||||
inventoryResponse.Missions.push({
|
|
||||||
Completes: 1,
|
|
||||||
Tier: 1,
|
|
||||||
Tag: tag
|
|
||||||
});
|
|
||||||
}
|
|
||||||
addString(inventoryResponse.NodeIntrosCompleted, "TeshinHardModeUnlocked");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.unlockAllShipDecorations) {
|
if (config.unlockAllShipDecorations) {
|
||||||
inventoryResponse.ShipDecorations = [];
|
inventoryResponse.ShipDecorations = [];
|
||||||
for (const [uniqueName, item] of Object.entries(ExportResources)) {
|
for (const [uniqueName, item] of Object.entries(ExportResources)) {
|
||||||
@ -297,29 +313,77 @@ export const getInventoryResponse = async (
|
|||||||
applyCheatsToInfestedFoundry(inventoryResponse.InfestedFoundry);
|
applyCheatsToInfestedFoundry(inventoryResponse.InfestedFoundry);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Omitting this field so opening the navigation resyncs the inventory which is more desirable for typical usage.
|
|
||||||
inventoryResponse.LastInventorySync = undefined;
|
|
||||||
|
|
||||||
// Set 2FA enabled so trading post can be used
|
// Set 2FA enabled so trading post can be used
|
||||||
inventoryResponse.HWIDProtectEnabled = true;
|
inventoryResponse.HWIDProtectEnabled = true;
|
||||||
|
|
||||||
// Fix nemesis for older versions
|
if (buildLabel) {
|
||||||
if (
|
// Fix nemesis for older versions
|
||||||
inventoryResponse.Nemesis &&
|
if (
|
||||||
buildLabel &&
|
inventoryResponse.Nemesis &&
|
||||||
!isNemesisCompatibleWithVersion(inventoryResponse.Nemesis, buildLabel)
|
version_compare(buildLabel, getNemesisManifest(inventoryResponse.Nemesis.manifest).minBuild) < 0
|
||||||
) {
|
) {
|
||||||
inventoryResponse.Nemesis = undefined;
|
inventoryResponse.Nemesis = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version_compare(buildLabel, "2018.02.22.14.34") < 0) {
|
||||||
|
const personalRoomsDb = await getPersonalRooms(inventory.accountOwnerId.toString());
|
||||||
|
const personalRooms = personalRoomsDb.toJSON<IPersonalRoomsClient>();
|
||||||
|
inventoryResponse.Ship = personalRooms.Ship;
|
||||||
|
|
||||||
|
if (version_compare(buildLabel, "2016.12.21.19.13") <= 0) {
|
||||||
|
// U19.5 and below use $id instead of $oid
|
||||||
|
for (const category of equipmentKeys) {
|
||||||
|
for (const item of inventoryResponse[category]) {
|
||||||
|
toLegacyOid(item.ItemId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const upgrade of inventoryResponse.Upgrades) {
|
||||||
|
toLegacyOid(upgrade.ItemId);
|
||||||
|
}
|
||||||
|
if (inventoryResponse.BrandedSuits) {
|
||||||
|
for (const id of inventoryResponse.BrandedSuits) {
|
||||||
|
toLegacyOid(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.unlockAllProfitTakerStages) {
|
||||||
|
inventoryResponse.CompletedJobChains ??= [];
|
||||||
|
const EudicoHeists = inventoryResponse.CompletedJobChains.find(x => x.LocationTag == "EudicoHeists");
|
||||||
|
if (EudicoHeists) {
|
||||||
|
EudicoHeists.Jobs = allEudicoHeistJobs;
|
||||||
|
} else {
|
||||||
|
inventoryResponse.CompletedJobChains.push({
|
||||||
|
LocationTag: "EudicoHeists",
|
||||||
|
Jobs: allEudicoHeistJobs
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.unlockAllSimarisResearchEntries) {
|
||||||
|
inventoryResponse.LibraryPersonalTarget = undefined;
|
||||||
|
inventoryResponse.LibraryPersonalProgress = [
|
||||||
|
"/Lotus/Types/Game/Library/Targets/Research1Target",
|
||||||
|
"/Lotus/Types/Game/Library/Targets/Research2Target",
|
||||||
|
"/Lotus/Types/Game/Library/Targets/Research3Target",
|
||||||
|
"/Lotus/Types/Game/Library/Targets/Research4Target",
|
||||||
|
"/Lotus/Types/Game/Library/Targets/Research5Target",
|
||||||
|
"/Lotus/Types/Game/Library/Targets/Research6Target",
|
||||||
|
"/Lotus/Types/Game/Library/Targets/Research7Target"
|
||||||
|
].map(type => ({ TargetType: type, Scans: 10, Completed: true }));
|
||||||
}
|
}
|
||||||
|
|
||||||
return inventoryResponse;
|
return inventoryResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
const addString = (arr: string[], str: string): void => {
|
const allEudicoHeistJobs = [
|
||||||
if (!arr.find(x => x == str)) {
|
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyOne",
|
||||||
arr.push(str);
|
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyTwo",
|
||||||
}
|
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyThree",
|
||||||
};
|
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyFour"
|
||||||
|
];
|
||||||
|
|
||||||
const getExpRequiredForMr = (rank: number): number => {
|
const getExpRequiredForMr = (rank: number): number => {
|
||||||
if (rank <= 30) {
|
if (rank <= 30) {
|
||||||
|
@ -4,23 +4,37 @@ import { config } from "@/src/services/configService";
|
|||||||
import { buildConfig } from "@/src/services/buildConfigService";
|
import { buildConfig } from "@/src/services/buildConfigService";
|
||||||
|
|
||||||
import { Account } from "@/src/models/loginModel";
|
import { Account } from "@/src/models/loginModel";
|
||||||
import { createAccount, isCorrectPassword, isNameTaken } from "@/src/services/loginService";
|
import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "@/src/services/loginService";
|
||||||
import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes";
|
import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { version_compare } from "@/src/services/worldStateService";
|
import { version_compare } from "@/src/helpers/inventoryHelpers";
|
||||||
|
import { sendWsBroadcastTo } from "@/src/services/webService";
|
||||||
|
|
||||||
export const loginController: RequestHandler = async (request, response) => {
|
export const loginController: RequestHandler = async (request, response) => {
|
||||||
const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
|
const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
|
||||||
|
|
||||||
const account = await Account.findOne({ email: loginRequest.email });
|
const account = await Account.findOne({ email: loginRequest.email });
|
||||||
const nonce = Math.round(Math.random() * Number.MAX_SAFE_INTEGER);
|
|
||||||
|
|
||||||
const buildLabel: string =
|
const buildLabel: string =
|
||||||
typeof request.query.buildLabel == "string"
|
typeof request.query.buildLabel == "string"
|
||||||
? request.query.buildLabel.split(" ").join("+")
|
? request.query.buildLabel.split(" ").join("+")
|
||||||
: buildConfig.buildLabel;
|
: buildConfig.buildLabel;
|
||||||
|
|
||||||
const myAddress = request.host.indexOf("warframe.com") == -1 ? request.host : config.myAddress;
|
let myAddress: string;
|
||||||
|
let myUrlBase: string = request.protocol + "://";
|
||||||
|
if (request.host.indexOf("warframe.com") == -1) {
|
||||||
|
// Client request was redirected cleanly, so we know it can reach us how it's reaching us now.
|
||||||
|
myAddress = request.hostname;
|
||||||
|
myUrlBase += request.host;
|
||||||
|
} else {
|
||||||
|
// Don't know how the client reached us, hoping the config does.
|
||||||
|
myAddress = config.myAddress;
|
||||||
|
myUrlBase += myAddress;
|
||||||
|
const port: number = request.protocol == "http" ? config.httpPort || 80 : config.httpsPort || 443;
|
||||||
|
if (port != (request.protocol == "http" ? 80 : 443)) {
|
||||||
|
myUrlBase += ":" + port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!account &&
|
!account &&
|
||||||
@ -28,30 +42,19 @@ export const loginController: RequestHandler = async (request, response) => {
|
|||||||
loginRequest.ClientType == "webui-register")
|
loginRequest.ClientType == "webui-register")
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const nameFromEmail = loginRequest.email.substring(0, loginRequest.email.indexOf("@"));
|
const name = await getUsernameFromEmail(loginRequest.email);
|
||||||
let name = nameFromEmail || loginRequest.email.substring(1) || "SpaceNinja";
|
|
||||||
if (await isNameTaken(name)) {
|
|
||||||
let suffix = 0;
|
|
||||||
do {
|
|
||||||
++suffix;
|
|
||||||
name = nameFromEmail + suffix;
|
|
||||||
} while (await isNameTaken(name));
|
|
||||||
}
|
|
||||||
const newAccount = await createAccount({
|
const newAccount = await createAccount({
|
||||||
email: loginRequest.email,
|
email: loginRequest.email,
|
||||||
password: loginRequest.password,
|
password: loginRequest.password,
|
||||||
DisplayName: name,
|
DisplayName: name,
|
||||||
CountryCode: loginRequest.lang.toUpperCase(),
|
CountryCode: loginRequest.lang?.toUpperCase() ?? "EN",
|
||||||
ClientType: loginRequest.ClientType == "webui-register" ? "webui" : loginRequest.ClientType,
|
ClientType: loginRequest.ClientType,
|
||||||
CrossPlatformAllowed: true,
|
Nonce: createNonce(),
|
||||||
ForceLogoutVersion: 0,
|
BuildLabel: buildLabel,
|
||||||
ConsentNeeded: false,
|
LastLogin: new Date()
|
||||||
TrackedSettings: [],
|
|
||||||
Nonce: nonce,
|
|
||||||
BuildLabel: buildLabel
|
|
||||||
});
|
});
|
||||||
logger.debug("created new account");
|
logger.debug("created new account");
|
||||||
response.json(createLoginResponse(myAddress, newAccount, buildLabel));
|
response.json(createLoginResponse(myAddress, myUrlBase, newAccount, buildLabel));
|
||||||
return;
|
return;
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
@ -65,51 +68,59 @@ export const loginController: RequestHandler = async (request, response) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loginRequest.ClientType == "webui-register") {
|
|
||||||
response.status(400).json({ error: "account already exists" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isCorrectPassword(loginRequest.password, account.password)) {
|
if (!isCorrectPassword(loginRequest.password, account.password)) {
|
||||||
response.status(400).json({ error: "incorrect login data" });
|
response.status(400).json({ error: "incorrect login data" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loginRequest.ClientType == "webui") {
|
if (account.Nonce && account.ClientType != "webui" && !account.Dropped && !loginRequest.kick) {
|
||||||
if (!account.Nonce) {
|
// U17 seems to handle "nonce still set" like a login failure.
|
||||||
account.ClientType = "webui";
|
if (version_compare(buildLabel, "2015.12.05.18.07") >= 0) {
|
||||||
account.Nonce = nonce;
|
response.status(400).send({ error: "nonce still set" });
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (account.Nonce && account.ClientType != "webui" && !account.Dropped && !loginRequest.kick) {
|
|
||||||
response.status(400).json({ error: "nonce still set" });
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
account.ClientType = loginRequest.ClientType;
|
|
||||||
account.Nonce = nonce;
|
|
||||||
account.CountryCode = loginRequest.lang.toUpperCase();
|
|
||||||
account.BuildLabel = buildLabel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
account.ClientType = loginRequest.ClientType;
|
||||||
|
account.Nonce = createNonce();
|
||||||
|
account.CountryCode = loginRequest.lang?.toUpperCase() ?? "EN";
|
||||||
|
account.BuildLabel = buildLabel;
|
||||||
|
account.LastLogin = new Date();
|
||||||
await account.save();
|
await account.save();
|
||||||
|
|
||||||
response.json(createLoginResponse(myAddress, account.toJSON(), buildLabel));
|
// Tell WebUI its nonce has been invalidated
|
||||||
|
sendWsBroadcastTo(account._id.toString(), { logged_out: true });
|
||||||
|
|
||||||
|
response.json(createLoginResponse(myAddress, myUrlBase, account.toJSON(), buildLabel));
|
||||||
};
|
};
|
||||||
|
|
||||||
const createLoginResponse = (myAddress: string, account: IDatabaseAccountJson, buildLabel: string): ILoginResponse => {
|
const createLoginResponse = (
|
||||||
|
myAddress: string,
|
||||||
|
myUrlBase: string,
|
||||||
|
account: IDatabaseAccountJson,
|
||||||
|
buildLabel: string
|
||||||
|
): ILoginResponse => {
|
||||||
const resp: ILoginResponse = {
|
const resp: ILoginResponse = {
|
||||||
id: account.id,
|
id: account.id,
|
||||||
DisplayName: account.DisplayName,
|
DisplayName: account.DisplayName,
|
||||||
CountryCode: account.CountryCode,
|
CountryCode: account.CountryCode,
|
||||||
AmazonAuthToken: account.AmazonAuthToken,
|
AmazonAuthToken: account.AmazonAuthToken,
|
||||||
AmazonRefreshToken: account.AmazonRefreshToken,
|
AmazonRefreshToken: account.AmazonRefreshToken,
|
||||||
ConsentNeeded: account.ConsentNeeded,
|
|
||||||
TrackedSettings: account.TrackedSettings,
|
|
||||||
Nonce: account.Nonce,
|
Nonce: account.Nonce,
|
||||||
IRC: config.myIrcAddresses ?? [myAddress],
|
|
||||||
NRS: config.NRS,
|
|
||||||
BuildLabel: buildLabel
|
BuildLabel: buildLabel
|
||||||
};
|
};
|
||||||
|
if (version_compare(buildLabel, "2015.02.13.10.41") >= 0) {
|
||||||
|
resp.NRS = config.NRS;
|
||||||
|
}
|
||||||
|
if (version_compare(buildLabel, "2015.05.14.16.29") >= 0) {
|
||||||
|
// U17 and up
|
||||||
|
resp.IRC = config.myIrcAddresses ?? [myAddress];
|
||||||
|
}
|
||||||
|
if (version_compare(buildLabel, "2018.11.08.14.45") >= 0) {
|
||||||
|
// U24 and up
|
||||||
|
resp.ConsentNeeded = account.ConsentNeeded;
|
||||||
|
resp.TrackedSettings = account.TrackedSettings;
|
||||||
|
}
|
||||||
if (version_compare(buildLabel, "2019.08.29.20.01") >= 0) {
|
if (version_compare(buildLabel, "2019.08.29.20.01") >= 0) {
|
||||||
// U25.7 and up
|
// U25.7 and up
|
||||||
resp.ForceLogoutVersion = account.ForceLogoutVersion;
|
resp.ForceLogoutVersion = account.ForceLogoutVersion;
|
||||||
@ -126,11 +137,11 @@ const createLoginResponse = (myAddress: string, account: IDatabaseAccountJson, b
|
|||||||
}
|
}
|
||||||
if (version_compare(buildLabel, "2022.09.06.19.24") >= 0) {
|
if (version_compare(buildLabel, "2022.09.06.19.24") >= 0) {
|
||||||
resp.CrossPlatformAllowed = account.CrossPlatformAllowed;
|
resp.CrossPlatformAllowed = account.CrossPlatformAllowed;
|
||||||
resp.HUB = `https://${myAddress}/api/`;
|
resp.HUB = `${myUrlBase}/api/`;
|
||||||
resp.MatchmakingBuildId = buildConfig.matchmakingBuildId;
|
resp.MatchmakingBuildId = buildConfig.matchmakingBuildId;
|
||||||
}
|
}
|
||||||
if (version_compare(buildLabel, "2023.04.25.23.40") >= 0) {
|
if (version_compare(buildLabel, "2023.04.25.23.40") >= 0) {
|
||||||
resp.platformCDNs = [`https://${myAddress}/`];
|
resp.platformCDNs = [`${myUrlBase}/`];
|
||||||
}
|
}
|
||||||
return resp;
|
return resp;
|
||||||
};
|
};
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
setAccountGotLoginRewardToday
|
setAccountGotLoginRewardToday
|
||||||
} from "@/src/services/loginRewardService";
|
} from "@/src/services/loginRewardService";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { config } from "@/src/services/configService";
|
||||||
|
|
||||||
export const loginRewardsController: RequestHandler = async (req, res) => {
|
export const loginRewardsController: RequestHandler = async (req, res) => {
|
||||||
const account = await getAccountForRequest(req);
|
const account = await getAccountForRequest(req);
|
||||||
@ -15,7 +16,7 @@ export const loginRewardsController: RequestHandler = async (req, res) => {
|
|||||||
const isMilestoneDay = account.LoginDays == 5 || account.LoginDays % 50 == 0;
|
const isMilestoneDay = account.LoginDays == 5 || account.LoginDays % 50 == 0;
|
||||||
const nextMilestoneDay = account.LoginDays < 5 ? 5 : (Math.trunc(account.LoginDays / 50) + 1) * 50;
|
const nextMilestoneDay = account.LoginDays < 5 ? 5 : (Math.trunc(account.LoginDays / 50) + 1) * 50;
|
||||||
|
|
||||||
if (today == account.LastLoginRewardDate) {
|
if (today == account.LastLoginRewardDate || config.disableDailyTribute) {
|
||||||
res.json({
|
res.json({
|
||||||
DailyTributeInfo: {
|
DailyTributeInfo: {
|
||||||
IsMilestoneDay: isMilestoneDay,
|
IsMilestoneDay: isMilestoneDay,
|
||||||
|
@ -26,7 +26,7 @@ export const loginRewardsSelectionController: RequestHandler = async (req, res)
|
|||||||
StoreItemType: body.ChosenReward
|
StoreItemType: body.ChosenReward
|
||||||
};
|
};
|
||||||
inventoryChanges = (await handleStoreItemAcquisition(body.ChosenReward, inventory)).InventoryChanges;
|
inventoryChanges = (await handleStoreItemAcquisition(body.ChosenReward, inventory)).InventoryChanges;
|
||||||
if (!evergreenRewards.find(x => x == body.ChosenReward)) {
|
if (evergreenRewards.indexOf(body.ChosenReward) == -1) {
|
||||||
inventory.LoginMilestoneRewards.push(body.ChosenReward);
|
inventory.LoginMilestoneRewards.push(body.ChosenReward);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { Account } from "@/src/models/loginModel";
|
import { Account } from "@/src/models/loginModel";
|
||||||
|
import { sendWsBroadcastTo } from "@/src/services/webService";
|
||||||
|
|
||||||
export const logoutController: RequestHandler = async (req, res) => {
|
export const logoutController: RequestHandler = async (req, res) => {
|
||||||
if (!req.query.accountId) {
|
if (!req.query.accountId) {
|
||||||
@ -10,7 +11,7 @@ export const logoutController: RequestHandler = async (req, res) => {
|
|||||||
throw new Error("Request is missing nonce parameter");
|
throw new Error("Request is missing nonce parameter");
|
||||||
}
|
}
|
||||||
|
|
||||||
await Account.updateOne(
|
const stat = await Account.updateOne(
|
||||||
{
|
{
|
||||||
_id: req.query.accountId,
|
_id: req.query.accountId,
|
||||||
Nonce: nonce
|
Nonce: nonce
|
||||||
@ -19,6 +20,10 @@ export const logoutController: RequestHandler = async (req, res) => {
|
|||||||
Nonce: 0
|
Nonce: 0
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
if (stat.modifiedCount) {
|
||||||
|
// Tell WebUI its nonce has been invalidated
|
||||||
|
sendWsBroadcastTo(req.query.accountId as string, { logged_out: true });
|
||||||
|
}
|
||||||
|
|
||||||
res.writeHead(200, {
|
res.writeHead(200, {
|
||||||
"Content-Type": "text/html",
|
"Content-Type": "text/html",
|
||||||
|
@ -7,6 +7,7 @@ import { generateRewardSeed, getInventory } from "@/src/services/inventoryServic
|
|||||||
import { getInventoryResponse } from "./inventoryController";
|
import { getInventoryResponse } from "./inventoryController";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { IMissionInventoryUpdateResponse } from "@/src/types/missionTypes";
|
import { IMissionInventoryUpdateResponse } from "@/src/types/missionTypes";
|
||||||
|
import { sendWsBroadcastTo } from "@/src/services/webService";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
**** INPUT ****
|
**** INPUT ****
|
||||||
@ -57,11 +58,15 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
|
|||||||
const firstCompletion = missionReport.SortieId
|
const firstCompletion = missionReport.SortieId
|
||||||
? inventory.CompletedSorties.indexOf(missionReport.SortieId) == -1
|
? inventory.CompletedSorties.indexOf(missionReport.SortieId) == -1
|
||||||
: false;
|
: false;
|
||||||
const inventoryUpdates = await addMissionInventoryUpdates(inventory, missionReport);
|
const inventoryUpdates = await addMissionInventoryUpdates(account, inventory, missionReport);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
missionReport.MissionStatus !== "GS_SUCCESS" &&
|
missionReport.MissionStatus !== "GS_SUCCESS" &&
|
||||||
!(missionReport.RewardInfo?.jobId || missionReport.RewardInfo?.challengeMissionId)
|
!(
|
||||||
|
missionReport.RewardInfo?.jobId ||
|
||||||
|
missionReport.RewardInfo?.challengeMissionId ||
|
||||||
|
missionReport.RewardInfo?.T
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
if (missionReport.EndOfMatchUpload) {
|
if (missionReport.EndOfMatchUpload) {
|
||||||
inventory.RewardSeed = generateRewardSeed();
|
inventory.RewardSeed = generateRewardSeed();
|
||||||
@ -72,6 +77,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
|
|||||||
InventoryJson: JSON.stringify(inventoryResponse),
|
InventoryJson: JSON.stringify(inventoryResponse),
|
||||||
MissionRewards: []
|
MissionRewards: []
|
||||||
});
|
});
|
||||||
|
sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +88,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
|
|||||||
AffiliationMods,
|
AffiliationMods,
|
||||||
SyndicateXPItemReward,
|
SyndicateXPItemReward,
|
||||||
ConquestCompletedMissionsCount
|
ConquestCompletedMissionsCount
|
||||||
} = await addMissionRewards(inventory, missionReport, firstCompletion);
|
} = await addMissionRewards(account, inventory, missionReport, firstCompletion);
|
||||||
|
|
||||||
if (missionReport.EndOfMatchUpload) {
|
if (missionReport.EndOfMatchUpload) {
|
||||||
inventory.RewardSeed = generateRewardSeed();
|
inventory.RewardSeed = generateRewardSeed();
|
||||||
@ -102,6 +108,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
|
|||||||
AffiliationMods,
|
AffiliationMods,
|
||||||
ConquestCompletedMissionsCount
|
ConquestCompletedMissionsCount
|
||||||
} satisfies IMissionInventoryUpdateResponse);
|
} satisfies IMissionInventoryUpdateResponse);
|
||||||
|
sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -2,7 +2,7 @@ import { RequestHandler } from "express";
|
|||||||
import { ExportWeapons } from "warframe-public-export-plus";
|
import { ExportWeapons } from "warframe-public-export-plus";
|
||||||
import { IMongoDate } from "@/src/types/commonTypes";
|
import { IMongoDate } from "@/src/types/commonTypes";
|
||||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||||
import { CRng } from "@/src/services/rngService";
|
import { SRng } from "@/src/services/rngService";
|
||||||
import { ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import {
|
import {
|
||||||
@ -140,7 +140,7 @@ const getModularWeaponSale = (
|
|||||||
partTypes: string[],
|
partTypes: string[],
|
||||||
getItemType: (parts: string[]) => string
|
getItemType: (parts: string[]) => string
|
||||||
): IModularWeaponSaleInfo => {
|
): IModularWeaponSaleInfo => {
|
||||||
const rng = new CRng(day);
|
const rng = new SRng(day);
|
||||||
const parts = partTypes.map(partType => rng.randomElement(partTypeToParts[partType])!);
|
const parts = partTypes.map(partType => rng.randomElement(partTypeToParts[partType])!);
|
||||||
let partsCost = 0;
|
let partsCost = 0;
|
||||||
for (const part of parts) {
|
for (const part of parts) {
|
||||||
|
@ -3,6 +3,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
|
|||||||
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { sendWsBroadcastTo } from "@/src/services/webService";
|
||||||
|
|
||||||
interface INameWeaponRequest {
|
interface INameWeaponRequest {
|
||||||
ItemName: string;
|
ItemName: string;
|
||||||
@ -27,4 +28,5 @@ export const nameWeaponController: RequestHandler = async (req, res) => {
|
|||||||
res.json({
|
res.json({
|
||||||
InventoryChanges: currencyChanges
|
InventoryChanges: currencyChanges
|
||||||
});
|
});
|
||||||
|
sendWsBroadcastTo(accountId, { update_inventory: true });
|
||||||
};
|
};
|
||||||
|
@ -1,18 +1,25 @@
|
|||||||
|
import { version_compare } from "@/src/helpers/inventoryHelpers";
|
||||||
import {
|
import {
|
||||||
|
antivirusMods,
|
||||||
consumeModCharge,
|
consumeModCharge,
|
||||||
|
decodeNemesisGuess,
|
||||||
encodeNemesisGuess,
|
encodeNemesisGuess,
|
||||||
getInfNodes,
|
getInfNodes,
|
||||||
getKnifeUpgrade,
|
getKnifeUpgrade,
|
||||||
|
getNemesisManifest,
|
||||||
getNemesisPasscode,
|
getNemesisPasscode,
|
||||||
getNemesisPasscodeModTypes,
|
GUESS_CORRECT,
|
||||||
getWeaponsForManifest,
|
GUESS_INCORRECT,
|
||||||
|
GUESS_NEUTRAL,
|
||||||
|
GUESS_NONE,
|
||||||
|
GUESS_WILDCARD,
|
||||||
IKnifeResponse,
|
IKnifeResponse,
|
||||||
showdownNodes
|
parseUpgrade
|
||||||
} from "@/src/helpers/nemesisHelpers";
|
} from "@/src/helpers/nemesisHelpers";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
|
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
|
||||||
import { freeUpSlot, getInventory } from "@/src/services/inventoryService";
|
import { freeUpSlot, getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountForRequest } from "@/src/services/loginService";
|
||||||
import { SRng } from "@/src/services/rngService";
|
import { SRng } from "@/src/services/rngService";
|
||||||
import { IMongoDate, IOid } from "@/src/types/commonTypes";
|
import { IMongoDate, IOid } from "@/src/types/commonTypes";
|
||||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
@ -24,16 +31,17 @@ import {
|
|||||||
IUpgradeClient,
|
IUpgradeClient,
|
||||||
IWeaponSkinClient,
|
IWeaponSkinClient,
|
||||||
LoadoutIndex,
|
LoadoutIndex,
|
||||||
TEquipmentKey
|
TEquipmentKey,
|
||||||
|
TNemesisFaction
|
||||||
} from "@/src/types/inventoryTypes/inventoryTypes";
|
} from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
export const nemesisController: RequestHandler = async (req, res) => {
|
export const nemesisController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const account = await getAccountForRequest(req);
|
||||||
if ((req.query.mode as string) == "f") {
|
if ((req.query.mode as string) == "f") {
|
||||||
const body = getJSONfromString<IValenceFusionRequest>(String(req.body));
|
const body = getJSONfromString<IValenceFusionRequest>(String(req.body));
|
||||||
const inventory = await getInventory(accountId, body.Category + " WeaponBin");
|
const inventory = await getInventory(account._id.toString(), body.Category + " WeaponBin");
|
||||||
const destWeapon = inventory[body.Category].id(body.DestWeapon.$oid)!;
|
const destWeapon = inventory[body.Category].id(body.DestWeapon.$oid)!;
|
||||||
const sourceWeapon = inventory[body.Category].id(body.SourceWeapon.$oid)!;
|
const sourceWeapon = inventory[body.Category].id(body.SourceWeapon.$oid)!;
|
||||||
const destFingerprint = JSON.parse(destWeapon.UpgradeFingerprint!) as IInnateDamageFingerprint;
|
const destFingerprint = JSON.parse(destWeapon.UpgradeFingerprint!) as IInnateDamageFingerprint;
|
||||||
@ -68,7 +76,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if ((req.query.mode as string) == "p") {
|
} else if ((req.query.mode as string) == "p") {
|
||||||
const inventory = await getInventory(accountId, "Nemesis");
|
const inventory = await getInventory(account._id.toString(), "Nemesis");
|
||||||
const body = getJSONfromString<INemesisPrespawnCheckRequest>(String(req.body));
|
const body = getJSONfromString<INemesisPrespawnCheckRequest>(String(req.body));
|
||||||
const passcode = getNemesisPasscode(inventory.Nemesis!);
|
const passcode = getNemesisPasscode(inventory.Nemesis!);
|
||||||
let guessResult = 0;
|
let guessResult = 0;
|
||||||
@ -81,7 +89,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0; i != 3; ++i) {
|
for (let i = 0; i != 3; ++i) {
|
||||||
if (body.guess[i] == passcode[i]) {
|
if (body.guess[i] == passcode[i] || body.guess[i] == GUESS_WILDCARD) {
|
||||||
++guessResult;
|
++guessResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -89,25 +97,36 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
|||||||
res.json({ GuessResult: guessResult });
|
res.json({ GuessResult: guessResult });
|
||||||
} else if (req.query.mode == "r") {
|
} else if (req.query.mode == "r") {
|
||||||
const inventory = await getInventory(
|
const inventory = await getInventory(
|
||||||
accountId,
|
account._id.toString(),
|
||||||
"Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades"
|
"Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades"
|
||||||
);
|
);
|
||||||
const body = getJSONfromString<INemesisRequiemRequest>(String(req.body));
|
const body = getJSONfromString<INemesisRequiemRequest>(String(req.body));
|
||||||
if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
|
if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
|
||||||
const guess: number[] = [body.guess & 0xf, (body.guess >> 4) & 0xf, (body.guess >> 8) & 0xf];
|
const guess: number[] = [body.guess & 0xf, (body.guess >> 4) & 0xf, (body.guess >> 8) & 0xf];
|
||||||
const passcode = getNemesisPasscode(inventory.Nemesis!)[0];
|
const passcode = getNemesisPasscode(inventory.Nemesis!)[0];
|
||||||
|
const result1 = passcode == guess[0] ? GUESS_CORRECT : GUESS_INCORRECT;
|
||||||
// Add to GuessHistory
|
const result2 = passcode == guess[1] ? GUESS_CORRECT : GUESS_INCORRECT;
|
||||||
const result1 = passcode == guess[0] ? 0 : 1;
|
const result3 = passcode == guess[2] ? GUESS_CORRECT : GUESS_INCORRECT;
|
||||||
const result2 = passcode == guess[1] ? 0 : 1;
|
|
||||||
const result3 = passcode == guess[2] ? 0 : 1;
|
|
||||||
inventory.Nemesis!.GuessHistory.push(
|
inventory.Nemesis!.GuessHistory.push(
|
||||||
encodeNemesisGuess(guess[0], result1, guess[1], result2, guess[2], result3)
|
encodeNemesisGuess([
|
||||||
|
{
|
||||||
|
symbol: guess[0],
|
||||||
|
result: result1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
symbol: guess[1],
|
||||||
|
result: result2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
symbol: guess[2],
|
||||||
|
result: result3
|
||||||
|
}
|
||||||
|
])
|
||||||
);
|
);
|
||||||
|
|
||||||
// Increase antivirus if correct antivirus mod is installed
|
// Increase antivirus if correct antivirus mod is installed
|
||||||
const response: IKnifeResponse = {};
|
const response: IKnifeResponse = {};
|
||||||
if (result1 == 0 || result2 == 0 || result3 == 0) {
|
if (result1 == GUESS_CORRECT || result2 == GUESS_CORRECT || result3 == GUESS_CORRECT) {
|
||||||
let antivirusGain = 5;
|
let antivirusGain = 5;
|
||||||
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!;
|
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!;
|
||||||
const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid);
|
const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid);
|
||||||
@ -116,71 +135,144 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
|||||||
for (const upgrade of body.knife!.AttachedUpgrades) {
|
for (const upgrade of body.knife!.AttachedUpgrades) {
|
||||||
switch (upgrade.ItemType) {
|
switch (upgrade.ItemType) {
|
||||||
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndSpeedOnUseMod":
|
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndSpeedOnUseMod":
|
||||||
antivirusGain += 10;
|
|
||||||
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
|
|
||||||
break;
|
|
||||||
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndWeaponDamageOnUseMod":
|
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndWeaponDamageOnUseMod":
|
||||||
|
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusSmallOnSingleUseMod":
|
||||||
antivirusGain += 10;
|
antivirusGain += 10;
|
||||||
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
|
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
|
||||||
break;
|
break;
|
||||||
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusLargeOnSingleUseMod": // Instant Secure
|
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
|
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusOnUseMod": // Immuno Shield
|
||||||
antivirusGain += 15;
|
antivirusGain += 15;
|
||||||
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
|
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
|
||||||
break;
|
break;
|
||||||
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusSmallOnSingleUseMod":
|
|
||||||
antivirusGain += 10;
|
|
||||||
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inventory.Nemesis!.HenchmenKilled += antivirusGain;
|
inventory.Nemesis!.HenchmenKilled += antivirusGain;
|
||||||
|
if (inventory.Nemesis!.HenchmenKilled >= 100) {
|
||||||
|
inventory.Nemesis!.HenchmenKilled = 100;
|
||||||
|
|
||||||
|
// Weaken nemesis now.
|
||||||
|
inventory.Nemesis!.InfNodes = [
|
||||||
|
{
|
||||||
|
Node: getNemesisManifest(inventory.Nemesis!.manifest).showdownNode,
|
||||||
|
Influence: 1
|
||||||
|
}
|
||||||
|
];
|
||||||
|
inventory.Nemesis!.Weakened = true;
|
||||||
|
const upgrade = getKnifeUpgrade(inventory, dataknifeUpgrades, antivirusMods[passcode]);
|
||||||
|
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inventory.Nemesis!.HenchmenKilled >= 100) {
|
if (inventory.Nemesis!.HenchmenKilled < 100) {
|
||||||
inventory.Nemesis!.HenchmenKilled = 100;
|
inventory.Nemesis!.InfNodes = getInfNodes(getNemesisManifest(inventory.Nemesis!.manifest), 0);
|
||||||
}
|
}
|
||||||
inventory.Nemesis!.InfNodes = getInfNodes("FC_INFESTATION", 0);
|
|
||||||
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json(response);
|
res.json(response);
|
||||||
} else {
|
} else {
|
||||||
const passcode = getNemesisPasscode(inventory.Nemesis!);
|
// For first guess, create a new entry.
|
||||||
if (passcode[body.position] != body.guess) {
|
if (body.position == 0) {
|
||||||
res.end();
|
inventory.Nemesis!.GuessHistory.push(
|
||||||
} else {
|
encodeNemesisGuess([
|
||||||
inventory.Nemesis!.Rank += 1;
|
{
|
||||||
inventory.Nemesis!.InfNodes = getInfNodes(inventory.Nemesis!.Faction, inventory.Nemesis!.Rank);
|
symbol: GUESS_NONE,
|
||||||
await inventory.save();
|
result: GUESS_NEUTRAL
|
||||||
res.json({ RankIncrease: 1 });
|
},
|
||||||
|
{
|
||||||
|
symbol: GUESS_NONE,
|
||||||
|
result: GUESS_NEUTRAL
|
||||||
|
},
|
||||||
|
{
|
||||||
|
symbol: GUESS_NONE,
|
||||||
|
result: GUESS_NEUTRAL
|
||||||
|
}
|
||||||
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Evaluate guess
|
||||||
|
const correct =
|
||||||
|
body.guess == GUESS_WILDCARD || getNemesisPasscode(inventory.Nemesis!)[body.position] == body.guess;
|
||||||
|
|
||||||
|
// Update entry
|
||||||
|
const guess = decodeNemesisGuess(
|
||||||
|
inventory.Nemesis!.GuessHistory[inventory.Nemesis!.GuessHistory.length - 1]
|
||||||
|
);
|
||||||
|
guess[body.position].symbol = body.guess;
|
||||||
|
guess[body.position].result = correct ? GUESS_CORRECT : GUESS_INCORRECT;
|
||||||
|
inventory.Nemesis!.GuessHistory[inventory.Nemesis!.GuessHistory.length - 1] = encodeNemesisGuess(guess);
|
||||||
|
|
||||||
|
const response: INemesisRequiemResponse = {};
|
||||||
|
if (correct) {
|
||||||
|
if (body.position == 2) {
|
||||||
|
// That was all 3 guesses correct, nemesis is now weakened.
|
||||||
|
inventory.Nemesis!.InfNodes = [
|
||||||
|
{
|
||||||
|
Node: getNemesisManifest(inventory.Nemesis!.manifest).showdownNode,
|
||||||
|
Influence: 1
|
||||||
|
}
|
||||||
|
];
|
||||||
|
inventory.Nemesis!.Weakened = true;
|
||||||
|
|
||||||
|
// Subtract a charge from all requiem mods installed on parazon
|
||||||
|
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!;
|
||||||
|
const dataknifeLoadout = loadout.DATAKNIFE.id(
|
||||||
|
inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid
|
||||||
|
);
|
||||||
|
const dataknifeConfigIndex = dataknifeLoadout?.s?.mod ?? 0;
|
||||||
|
const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex].Upgrades!;
|
||||||
|
for (let i = 3; i != 6; ++i) {
|
||||||
|
//logger.debug(`subtracting a charge from ${dataknifeUpgrades[i]}`);
|
||||||
|
const upgrade = parseUpgrade(inventory, dataknifeUpgrades[i]);
|
||||||
|
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Guess was incorrect, increase rank
|
||||||
|
response.RankIncrease = 1;
|
||||||
|
const manifest = getNemesisManifest(inventory.Nemesis!.manifest);
|
||||||
|
inventory.Nemesis!.Rank = Math.min(inventory.Nemesis!.Rank + 1, manifest.systemIndexes.length - 1);
|
||||||
|
inventory.Nemesis!.InfNodes = getInfNodes(manifest, inventory.Nemesis!.Rank);
|
||||||
|
}
|
||||||
|
await inventory.save();
|
||||||
|
res.json(response);
|
||||||
}
|
}
|
||||||
} else if ((req.query.mode as string) == "rs") {
|
} else if ((req.query.mode as string) == "rs") {
|
||||||
// report spawn; POST but no application data in body
|
// report spawn; POST but no application data in body
|
||||||
const inventory = await getInventory(accountId, "Nemesis");
|
const inventory = await getInventory(account._id.toString(), "Nemesis");
|
||||||
inventory.Nemesis!.LastEnc = inventory.Nemesis!.MissionCount;
|
inventory.Nemesis!.LastEnc = inventory.Nemesis!.MissionCount;
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({ LastEnc: inventory.Nemesis!.LastEnc });
|
res.json({ LastEnc: inventory.Nemesis!.LastEnc });
|
||||||
} else if ((req.query.mode as string) == "s") {
|
} else if ((req.query.mode as string) == "s") {
|
||||||
const inventory = await getInventory(accountId, "Nemesis");
|
const inventory = await getInventory(account._id.toString(), "Nemesis");
|
||||||
|
if (inventory.Nemesis) {
|
||||||
|
logger.warn(`overwriting an existing nemesis as a new one is being requested`);
|
||||||
|
}
|
||||||
const body = getJSONfromString<INemesisStartRequest>(String(req.body));
|
const body = getJSONfromString<INemesisStartRequest>(String(req.body));
|
||||||
body.target.fp = BigInt(body.target.fp);
|
body.target.fp = BigInt(body.target.fp);
|
||||||
|
|
||||||
|
const manifest = getNemesisManifest(body.target.manifest);
|
||||||
|
if (account.BuildLabel && version_compare(account.BuildLabel, manifest.minBuild) < 0) {
|
||||||
|
logger.warn(
|
||||||
|
`client on version ${account.BuildLabel} provided nemesis manifest ${body.target.manifest} which was expected to require ${manifest.minBuild} or above. please file a bug report.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let weaponIdx = -1;
|
let weaponIdx = -1;
|
||||||
if (body.target.Faction != "FC_INFESTATION") {
|
if (body.target.Faction != "FC_INFESTATION") {
|
||||||
const weapons = getWeaponsForManifest(body.target.manifest);
|
const weapons: readonly string[] = manifest.weapons;
|
||||||
const initialWeaponIdx = new SRng(body.target.fp).randomInt(0, weapons.length - 1);
|
const initialWeaponIdx = new SRng(body.target.fp).randomInt(0, weapons.length - 1);
|
||||||
weaponIdx = initialWeaponIdx;
|
weaponIdx = initialWeaponIdx;
|
||||||
do {
|
if (body.target.DisallowedWeapons) {
|
||||||
const weapon = weapons[weaponIdx];
|
do {
|
||||||
if (!body.target.DisallowedWeapons.find(x => x == weapon)) {
|
const weapon = weapons[weaponIdx];
|
||||||
break;
|
if (body.target.DisallowedWeapons.indexOf(weapon) == -1) {
|
||||||
}
|
break;
|
||||||
weaponIdx = (weaponIdx + 1) % weapons.length;
|
}
|
||||||
} while (weaponIdx != initialWeaponIdx);
|
weaponIdx = (weaponIdx + 1) % weapons.length;
|
||||||
|
} while (weaponIdx != initialWeaponIdx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inventory.Nemesis = {
|
inventory.Nemesis = {
|
||||||
@ -197,14 +289,14 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
|||||||
k: false,
|
k: false,
|
||||||
Traded: false,
|
Traded: false,
|
||||||
d: new Date(),
|
d: new Date(),
|
||||||
InfNodes: getInfNodes(body.target.Faction, 0),
|
InfNodes: getInfNodes(manifest, 0),
|
||||||
GuessHistory: [],
|
GuessHistory: [],
|
||||||
Hints: [],
|
Hints: [],
|
||||||
HintProgress: 0,
|
HintProgress: 0,
|
||||||
Weakened: body.target.Weakened,
|
Weakened: false,
|
||||||
PrevOwners: 0,
|
PrevOwners: 0,
|
||||||
HenchmenKilled: 0,
|
HenchmenKilled: 0,
|
||||||
SecondInCommand: body.target.SecondInCommand,
|
SecondInCommand: false,
|
||||||
MissionCount: 0,
|
MissionCount: 0,
|
||||||
LastEnc: 0
|
LastEnc: 0
|
||||||
};
|
};
|
||||||
@ -214,36 +306,15 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
|||||||
target: inventory.toJSON().Nemesis
|
target: inventory.toJSON().Nemesis
|
||||||
});
|
});
|
||||||
} else if ((req.query.mode as string) == "w") {
|
} else if ((req.query.mode as string) == "w") {
|
||||||
const inventory = await getInventory(
|
const inventory = await getInventory(account._id.toString(), "Nemesis");
|
||||||
accountId,
|
|
||||||
"Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades"
|
|
||||||
);
|
|
||||||
//const body = getJSONfromString<INemesisWeakenRequest>(String(req.body));
|
//const body = getJSONfromString<INemesisWeakenRequest>(String(req.body));
|
||||||
|
|
||||||
inventory.Nemesis!.InfNodes = [
|
// As of 38.6.0, this request is no longer sent, instead mode=r already weakens the nemesis if appropriate.
|
||||||
{
|
// We always weaken the nemesis in mode=r so simply giving the client back the nemesis.
|
||||||
Node: showdownNodes[inventory.Nemesis!.Faction],
|
|
||||||
Influence: 1
|
|
||||||
}
|
|
||||||
];
|
|
||||||
inventory.Nemesis!.Weakened = true;
|
|
||||||
|
|
||||||
const response: IKnifeResponse & { target: INemesisClient } = {
|
const response: INemesisWeakenResponse = {
|
||||||
target: inventory.toJSON<IInventoryClient>().Nemesis!
|
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);
|
res.json(response);
|
||||||
} else {
|
} else {
|
||||||
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
||||||
@ -265,11 +336,11 @@ interface INemesisStartRequest {
|
|||||||
KillingSuit: string;
|
KillingSuit: string;
|
||||||
killingDamageType: number;
|
killingDamageType: number;
|
||||||
ShoulderHelmet: string;
|
ShoulderHelmet: string;
|
||||||
DisallowedWeapons: string[];
|
DisallowedWeapons?: string[];
|
||||||
WeaponIdx: number;
|
WeaponIdx: number;
|
||||||
AgentIdx: number;
|
AgentIdx: number;
|
||||||
BirthNode: string;
|
BirthNode: string;
|
||||||
Faction: string;
|
Faction: TNemesisFaction;
|
||||||
Rank: number;
|
Rank: number;
|
||||||
k: boolean;
|
k: boolean;
|
||||||
Traded: boolean;
|
Traded: boolean;
|
||||||
@ -299,11 +370,19 @@ interface INemesisRequiemRequest {
|
|||||||
knife?: IKnife;
|
knife?: IKnife;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface INemesisRequiemResponse extends IKnifeResponse {
|
||||||
|
RankIncrease?: number;
|
||||||
|
}
|
||||||
|
|
||||||
// interface INemesisWeakenRequest {
|
// interface INemesisWeakenRequest {
|
||||||
// target: INemesisClient;
|
// target: INemesisClient;
|
||||||
// knife: IKnife;
|
// knife: IKnife;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
interface INemesisWeakenResponse extends IKnifeResponse {
|
||||||
|
target: INemesisClient;
|
||||||
|
}
|
||||||
|
|
||||||
interface IKnife {
|
interface IKnife {
|
||||||
Item: IEquipmentClient;
|
Item: IEquipmentClient;
|
||||||
Skins: IWeaponSkinClient[];
|
Skins: IWeaponSkinClient[];
|
||||||
|
@ -57,7 +57,11 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
|
|||||||
component.DecoCapacity -= meta.capacityCost;
|
component.DecoCapacity -= meta.capacityCost;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)![0];
|
const [itemType, meta] = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)!;
|
||||||
|
if (!itemType || meta.dojoCapacityCost === undefined) {
|
||||||
|
throw new Error(`unknown deco type: ${deco.Type}`);
|
||||||
|
}
|
||||||
|
component.DecoCapacity -= meta.dojoCapacityCost;
|
||||||
if (deco.Sockets !== undefined) {
|
if (deco.Sockets !== undefined) {
|
||||||
guild.VaultFusionTreasures!.find(x => x.ItemType == itemType && x.Sockets == deco.Sockets)!.ItemCount -=
|
guild.VaultFusionTreasures!.find(x => x.ItemType == itemType && x.Sockets == deco.Sockets)!.ItemCount -=
|
||||||
1;
|
1;
|
||||||
|
@ -2,13 +2,16 @@ import { RequestHandler } from "express";
|
|||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
||||||
import { ExportRelics, IRelic } from "warframe-public-export-plus";
|
import { ExportRelics, IRelic } from "warframe-public-export-plus";
|
||||||
|
import { config } from "@/src/services/configService";
|
||||||
|
|
||||||
export const projectionManagerController: RequestHandler = async (req, res) => {
|
export const projectionManagerController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
const request = JSON.parse(String(req.body)) as IProjectionUpgradeRequest;
|
const request = JSON.parse(String(req.body)) as IProjectionUpgradeRequest;
|
||||||
const [era, category, currentQuality] = parseProjection(request.projectionType);
|
const [era, category, currentQuality] = parseProjection(request.projectionType);
|
||||||
const upgradeCost = (request.qualityTag - qualityKeywordToNumber[currentQuality]) * 25;
|
const upgradeCost = config.dontSubtractVoidTraces
|
||||||
|
? 0
|
||||||
|
: (request.qualityTag - qualityKeywordToNumber[currentQuality]) * 25;
|
||||||
const newProjectionType = findProjection(era, category, qualityNumberToKeyword[request.qualityTag]);
|
const newProjectionType = findProjection(era, category, qualityNumberToKeyword[request.qualityTag]);
|
||||||
addMiscItems(inventory, [
|
addMiscItems(inventory, [
|
||||||
{
|
{
|
||||||
|
@ -3,6 +3,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
|
|||||||
import { IPurchaseRequest } from "@/src/types/purchaseTypes";
|
import { IPurchaseRequest } from "@/src/types/purchaseTypes";
|
||||||
import { handlePurchase } from "@/src/services/purchaseService";
|
import { handlePurchase } from "@/src/services/purchaseService";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { sendWsBroadcastTo } from "@/src/services/webService";
|
||||||
|
|
||||||
export const purchaseController: RequestHandler = async (req, res) => {
|
export const purchaseController: RequestHandler = async (req, res) => {
|
||||||
const purchaseRequest = JSON.parse(String(req.body)) as IPurchaseRequest;
|
const purchaseRequest = JSON.parse(String(req.body)) as IPurchaseRequest;
|
||||||
@ -10,5 +11,7 @@ export const purchaseController: RequestHandler = async (req, res) => {
|
|||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
const response = await handlePurchase(purchaseRequest, inventory);
|
const response = await handlePurchase(purchaseRequest, inventory);
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
|
//console.log(JSON.stringify(response, null, 2));
|
||||||
res.json(response);
|
res.json(response);
|
||||||
|
sendWsBroadcastTo(accountId, { update_inventory: true });
|
||||||
};
|
};
|
||||||
|
25
src/controllers/api/questControlController.ts
Normal file
25
src/controllers/api/questControlController.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
// Basic shim handling action=sync to login on U21
|
||||||
|
export const questControlController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
const quests: IQuestState[] = [];
|
||||||
|
for (const quest of inventory.QuestKeys) {
|
||||||
|
quests.push({
|
||||||
|
quest: quest.ItemType,
|
||||||
|
state: 3 // COMPLETE
|
||||||
|
});
|
||||||
|
}
|
||||||
|
res.json({
|
||||||
|
QuestState: quests
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IQuestState {
|
||||||
|
quest: string;
|
||||||
|
state: number;
|
||||||
|
task?: string;
|
||||||
|
}
|
99
src/controllers/api/removeFriendController.ts
Normal file
99
src/controllers/api/removeFriendController.ts
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { Friendship } from "@/src/models/friendModel";
|
||||||
|
import { Account } from "@/src/models/loginModel";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
|
import { parallelForeach } from "@/src/utils/async-utils";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { Types } from "mongoose";
|
||||||
|
|
||||||
|
export const removeFriendGetController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
if (req.query.all) {
|
||||||
|
const [internalFriendships, externalFriendships] = await Promise.all([
|
||||||
|
Friendship.find({ owner: accountId }, "friend"),
|
||||||
|
Friendship.find({ friend: accountId }, "owner")
|
||||||
|
]);
|
||||||
|
const promises: Promise<void>[] = [];
|
||||||
|
const friends: IOid[] = [];
|
||||||
|
for (const externalFriendship of externalFriendships) {
|
||||||
|
if (!internalFriendships.find(x => x.friend.equals(externalFriendship.owner))) {
|
||||||
|
promises.push(Friendship.deleteOne({ _id: externalFriendship._id }) as unknown as Promise<void>);
|
||||||
|
friends.push(toOid(externalFriendship.owner));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
|
res.json({
|
||||||
|
Friends: friends
|
||||||
|
} satisfies IRemoveFriendsResponse);
|
||||||
|
} else {
|
||||||
|
const friendId = req.query.friendId as string;
|
||||||
|
await Promise.all([
|
||||||
|
Friendship.deleteOne({ owner: accountId, friend: friendId }),
|
||||||
|
Friendship.deleteOne({ owner: friendId, friend: accountId })
|
||||||
|
]);
|
||||||
|
res.json({
|
||||||
|
Friends: [{ $oid: friendId }]
|
||||||
|
} satisfies IRemoveFriendsResponse);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeFriendPostController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const data = getJSONfromString<IBatchRemoveFriendsRequest>(String(req.body));
|
||||||
|
const friends = new Set((await Friendship.find({ owner: accountId }, "friend")).map(x => x.friend));
|
||||||
|
// TOVERIFY: Should pending friendships also be kept?
|
||||||
|
|
||||||
|
// Keep friends that have been online within threshold
|
||||||
|
await parallelForeach([...friends], async friend => {
|
||||||
|
const account = (await Account.findById(friend, "LastLogin"))!;
|
||||||
|
const daysLoggedOut = (Date.now() - account.LastLogin.getTime()) / 86400_000;
|
||||||
|
if (daysLoggedOut < data.DaysLoggedOut) {
|
||||||
|
friends.delete(friend);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.SkipClanmates) {
|
||||||
|
const inventory = await getInventory(accountId, "GuildId");
|
||||||
|
if (inventory.GuildId) {
|
||||||
|
await parallelForeach([...friends], async friend => {
|
||||||
|
const friendInventory = await getInventory(friend.toString(), "GuildId");
|
||||||
|
if (friendInventory.GuildId?.equals(inventory.GuildId)) {
|
||||||
|
friends.delete(friend);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all remaining friends that aren't in SkipFriendIds & give response.
|
||||||
|
const promises = [];
|
||||||
|
const response: IOid[] = [];
|
||||||
|
for (const friend of friends) {
|
||||||
|
if (!data.SkipFriendIds.find(skipFriendId => checkFriendId(skipFriendId, friend))) {
|
||||||
|
promises.push(Friendship.deleteOne({ owner: accountId, friend: friend }));
|
||||||
|
promises.push(Friendship.deleteOne({ owner: friend, friend: accountId }));
|
||||||
|
response.push(toOid(friend));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
|
res.json({
|
||||||
|
Friends: response
|
||||||
|
} satisfies IRemoveFriendsResponse);
|
||||||
|
};
|
||||||
|
|
||||||
|
// The friend ids format is a bit weird, e.g. when 6633b81e9dba0b714f28ff02 (A) is friends with 67cdac105ef1f4b49741c267 (B), A's friend id for B is 808000105ef1f40560ca079e and B's friend id for A is 8000b81e9dba0b06408a8075.
|
||||||
|
const checkFriendId = (friendId: string, b: Types.ObjectId): boolean => {
|
||||||
|
return friendId.substring(6, 6 + 8) == b.toString().substring(6, 6 + 8);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IBatchRemoveFriendsRequest {
|
||||||
|
DaysLoggedOut: number;
|
||||||
|
SkipClanmates: boolean;
|
||||||
|
SkipFriendIds: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IRemoveFriendsResponse {
|
||||||
|
Friends: IOid[];
|
||||||
|
}
|
32
src/controllers/api/renamePetController.ts
Normal file
32
src/controllers/api/renamePetController.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { sendWsBroadcastTo } from "@/src/services/webService";
|
||||||
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const renamePetController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "KubrowPets PremiumCredits PremiumCreditsFree");
|
||||||
|
const data = getJSONfromString<IRenamePetRequest>(String(req.body));
|
||||||
|
const details = inventory.KubrowPets.id(data.petId)!.Details!;
|
||||||
|
|
||||||
|
details.Name = data.name;
|
||||||
|
|
||||||
|
const inventoryChanges: IInventoryChanges = {};
|
||||||
|
if (!("webui" in req.query)) {
|
||||||
|
updateCurrency(inventory, 15, true, inventoryChanges);
|
||||||
|
}
|
||||||
|
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
...data,
|
||||||
|
inventoryChanges: inventoryChanges
|
||||||
|
});
|
||||||
|
sendWsBroadcastTo(accountId, { update_inventory: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IRenamePetRequest {
|
||||||
|
petId: string;
|
||||||
|
name: string;
|
||||||
|
}
|
@ -1,8 +1,7 @@
|
|||||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
|
||||||
import { config } from "@/src/services/configService";
|
import { config } from "@/src/services/configService";
|
||||||
import { addEmailItem, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
import { addEmailItem, getDialogue, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { ICompletedDialogue, IDialogueDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { ICompletedDialogue } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
@ -25,7 +24,7 @@ export const saveDialogueController: RequestHandler = async (req, res) => {
|
|||||||
inventory.DialogueHistory.Dialogues ??= [];
|
inventory.DialogueHistory.Dialogues ??= [];
|
||||||
const dialogue = getDialogue(inventory, request.DialogueName);
|
const dialogue = getDialogue(inventory, request.DialogueName);
|
||||||
dialogue.Rank = request.Rank;
|
dialogue.Rank = request.Rank;
|
||||||
dialogue.Chemistry = request.Chemistry;
|
dialogue.Chemistry += request.Chemistry;
|
||||||
dialogue.QueuedDialogues = request.QueuedDialogues;
|
dialogue.QueuedDialogues = request.QueuedDialogues;
|
||||||
for (const bool of request.Booleans) {
|
for (const bool of request.Booleans) {
|
||||||
dialogue.Booleans.push(bool);
|
dialogue.Booleans.push(bool);
|
||||||
@ -107,26 +106,3 @@ interface IOtherDialogueInfo {
|
|||||||
Tag: string;
|
Tag: string;
|
||||||
Value: number;
|
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;
|
|
||||||
};
|
|
||||||
|
@ -2,11 +2,12 @@ import { RequestHandler } from "express";
|
|||||||
import { ISaveLoadoutRequest } from "@/src/types/saveLoadoutTypes";
|
import { ISaveLoadoutRequest } from "@/src/types/saveLoadoutTypes";
|
||||||
import { handleInventoryItemConfigChange } from "@/src/services/saveLoadoutService";
|
import { handleInventoryItemConfigChange } from "@/src/services/saveLoadoutService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
|
||||||
export const saveLoadoutController: RequestHandler = async (req, res) => {
|
export const saveLoadoutController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
|
||||||
const body: ISaveLoadoutRequest = JSON.parse(req.body as string) as ISaveLoadoutRequest;
|
const body: ISaveLoadoutRequest = getJSONfromString<ISaveLoadoutRequest>(String(req.body));
|
||||||
// console.log(util.inspect(body, { showHidden: false, depth: null, colors: true }));
|
// console.log(util.inspect(body, { showHidden: false, depth: null, colors: true }));
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
@ -15,6 +15,7 @@ import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
|||||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
|
import { ExportDojoRecipes } from "warframe-public-export-plus";
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
|
import { sendWsBroadcastTo } from "@/src/services/webService";
|
||||||
|
|
||||||
export const sellController: RequestHandler = async (req, res) => {
|
export const sellController: RequestHandler = async (req, res) => {
|
||||||
const payload = JSON.parse(String(req.body)) as ISellRequest;
|
const payload = JSON.parse(String(req.body)) as ISellRequest;
|
||||||
@ -45,6 +46,9 @@ export const sellController: RequestHandler = async (req, res) => {
|
|||||||
if (payload.Items.SpaceGuns || payload.Items.SpaceMelee) {
|
if (payload.Items.SpaceGuns || payload.Items.SpaceMelee) {
|
||||||
requiredFields.add(InventorySlot.SPACEWEAPONS);
|
requiredFields.add(InventorySlot.SPACEWEAPONS);
|
||||||
}
|
}
|
||||||
|
if (payload.Items.MechSuits) {
|
||||||
|
requiredFields.add(InventorySlot.MECHSUITS);
|
||||||
|
}
|
||||||
if (payload.Items.Sentinels || payload.Items.SentinelWeapons || payload.Items.MoaPets) {
|
if (payload.Items.Sentinels || payload.Items.SentinelWeapons || payload.Items.MoaPets) {
|
||||||
requiredFields.add(InventorySlot.SENTINELS);
|
requiredFields.add(InventorySlot.SENTINELS);
|
||||||
}
|
}
|
||||||
@ -54,6 +58,9 @@ export const sellController: RequestHandler = async (req, res) => {
|
|||||||
if (payload.Items.Hoverboards) {
|
if (payload.Items.Hoverboards) {
|
||||||
requiredFields.add(InventorySlot.SPACESUITS);
|
requiredFields.add(InventorySlot.SPACESUITS);
|
||||||
}
|
}
|
||||||
|
if (payload.Items.CrewMembers) {
|
||||||
|
requiredFields.add(InventorySlot.CREWMEMBERS);
|
||||||
|
}
|
||||||
if (payload.Items.CrewShipWeapons || payload.Items.CrewShipWeaponSkins) {
|
if (payload.Items.CrewShipWeapons || payload.Items.CrewShipWeaponSkins) {
|
||||||
requiredFields.add(InventorySlot.RJ_COMPONENT_AND_ARMAMENTS);
|
requiredFields.add(InventorySlot.RJ_COMPONENT_AND_ARMAMENTS);
|
||||||
requiredFields.add("CrewShipRawSalvage");
|
requiredFields.add("CrewShipRawSalvage");
|
||||||
@ -136,6 +143,12 @@ export const sellController: RequestHandler = async (req, res) => {
|
|||||||
freeUpSlot(inventory, InventorySlot.SPACEWEAPONS);
|
freeUpSlot(inventory, InventorySlot.SPACEWEAPONS);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (payload.Items.MechSuits) {
|
||||||
|
payload.Items.MechSuits.forEach(sellItem => {
|
||||||
|
inventory.MechSuits.pull({ _id: sellItem.String });
|
||||||
|
freeUpSlot(inventory, InventorySlot.MECHSUITS);
|
||||||
|
});
|
||||||
|
}
|
||||||
if (payload.Items.Sentinels) {
|
if (payload.Items.Sentinels) {
|
||||||
payload.Items.Sentinels.forEach(sellItem => {
|
payload.Items.Sentinels.forEach(sellItem => {
|
||||||
inventory.Sentinels.pull({ _id: sellItem.String });
|
inventory.Sentinels.pull({ _id: sellItem.String });
|
||||||
@ -171,6 +184,12 @@ export const sellController: RequestHandler = async (req, res) => {
|
|||||||
inventory.Drones.pull({ _id: sellItem.String });
|
inventory.Drones.pull({ _id: sellItem.String });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (payload.Items.CrewMembers) {
|
||||||
|
payload.Items.CrewMembers.forEach(sellItem => {
|
||||||
|
inventory.CrewMembers.pull({ _id: sellItem.String });
|
||||||
|
freeUpSlot(inventory, InventorySlot.CREWMEMBERS);
|
||||||
|
});
|
||||||
|
}
|
||||||
if (payload.Items.CrewShipWeapons) {
|
if (payload.Items.CrewShipWeapons) {
|
||||||
payload.Items.CrewShipWeapons.forEach(sellItem => {
|
payload.Items.CrewShipWeapons.forEach(sellItem => {
|
||||||
if (sellItem.String[0] == "/") {
|
if (sellItem.String[0] == "/") {
|
||||||
@ -270,6 +289,7 @@ export const sellController: RequestHandler = async (req, res) => {
|
|||||||
res.json({
|
res.json({
|
||||||
inventoryChanges: inventoryChanges // "inventoryChanges" for this response instead of the usual "InventoryChanges"
|
inventoryChanges: inventoryChanges // "inventoryChanges" for this response instead of the usual "InventoryChanges"
|
||||||
});
|
});
|
||||||
|
sendWsBroadcastTo(accountId, { update_inventory: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ISellRequest {
|
interface ISellRequest {
|
||||||
@ -285,12 +305,14 @@ interface ISellRequest {
|
|||||||
SpaceSuits?: ISellItem[];
|
SpaceSuits?: ISellItem[];
|
||||||
SpaceGuns?: ISellItem[];
|
SpaceGuns?: ISellItem[];
|
||||||
SpaceMelee?: ISellItem[];
|
SpaceMelee?: ISellItem[];
|
||||||
|
MechSuits?: ISellItem[];
|
||||||
Sentinels?: ISellItem[];
|
Sentinels?: ISellItem[];
|
||||||
SentinelWeapons?: ISellItem[];
|
SentinelWeapons?: ISellItem[];
|
||||||
MoaPets?: ISellItem[];
|
MoaPets?: ISellItem[];
|
||||||
OperatorAmps?: ISellItem[];
|
OperatorAmps?: ISellItem[];
|
||||||
Hoverboards?: ISellItem[];
|
Hoverboards?: ISellItem[];
|
||||||
Drones?: ISellItem[];
|
Drones?: ISellItem[];
|
||||||
|
CrewMembers?: ISellItem[];
|
||||||
CrewShipWeapons?: ISellItem[];
|
CrewShipWeapons?: ISellItem[];
|
||||||
CrewShipWeaponSkins?: ISellItem[];
|
CrewShipWeaponSkins?: ISellItem[];
|
||||||
};
|
};
|
||||||
|
@ -13,7 +13,7 @@ export const setDojoComponentSettingsController: RequestHandler = async (req, re
|
|||||||
res.json({ DojoRequestStatus: -1 });
|
res.json({ DojoRequestStatus: -1 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const component = guild.DojoComponents.id(req.query.componentId)!;
|
const component = guild.DojoComponents.id(req.query.componentId as string)!;
|
||||||
const data = getJSONfromString<ISetDojoComponentSettingsRequest>(String(req.body));
|
const data = getJSONfromString<ISetDojoComponentSettingsRequest>(String(req.body));
|
||||||
component.Settings = data.Settings;
|
component.Settings = data.Settings;
|
||||||
await guild.save();
|
await guild.save();
|
||||||
|
30
src/controllers/api/setFriendNoteController.ts
Normal file
30
src/controllers/api/setFriendNoteController.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { Friendship } from "@/src/models/friendModel";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const setFriendNoteController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const payload = getJSONfromString<ISetFriendNoteRequest>(String(req.body));
|
||||||
|
const friendship = await Friendship.findOne({ owner: accountId, friend: payload.FriendId }, "Note Favorite");
|
||||||
|
if (friendship) {
|
||||||
|
if ("Note" in payload) {
|
||||||
|
friendship.Note = payload.Note;
|
||||||
|
} else {
|
||||||
|
friendship.Favorite = payload.Favorite;
|
||||||
|
}
|
||||||
|
await friendship.save();
|
||||||
|
}
|
||||||
|
res.json({
|
||||||
|
Id: payload.FriendId,
|
||||||
|
SetNote: "Note" in payload,
|
||||||
|
Note: friendship?.Note,
|
||||||
|
Favorite: friendship?.Favorite
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ISetFriendNoteRequest {
|
||||||
|
FriendId: string;
|
||||||
|
Note?: string;
|
||||||
|
Favorite?: boolean;
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
|
import { version_compare } from "@/src/helpers/inventoryHelpers";
|
||||||
import { Alliance, Guild, GuildMember } from "@/src/models/guildModel";
|
import { Alliance, Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
import { hasGuildPermissionEx } from "@/src/services/guildService";
|
import { hasGuildPermissionEx } from "@/src/services/guildService";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||||
import { version_compare } from "@/src/services/worldStateService";
|
|
||||||
import { GuildPermission, ILongMOTD } from "@/src/types/guildTypes";
|
import { GuildPermission, ILongMOTD } from "@/src/types/guildTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
22
src/controllers/api/setSuitInfectionController.ts
Normal file
22
src/controllers/api/setSuitInfectionController.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { fromMongoDate, fromOid } from "@/src/helpers/inventoryHelpers";
|
||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const setSuitInfectionController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "Suits");
|
||||||
|
const payload = getJSONfromString<ISetSuitInfectionRequest>(String(req.body));
|
||||||
|
for (const clientSuit of payload.Suits) {
|
||||||
|
const dbSuit = inventory.Suits.id(fromOid(clientSuit.ItemId))!;
|
||||||
|
dbSuit.InfestationDate = fromMongoDate(clientSuit.InfestationDate!);
|
||||||
|
}
|
||||||
|
await inventory.save();
|
||||||
|
res.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ISetSuitInfectionRequest {
|
||||||
|
Suits: IEquipmentClient[];
|
||||||
|
}
|
@ -11,7 +11,7 @@ import {
|
|||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
|
import { ExportDojoRecipes } from "warframe-public-export-plus";
|
||||||
import { config } from "@/src/services/configService";
|
import { config } from "@/src/services/configService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountForRequest } from "@/src/services/loginService";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
|
||||||
interface IStartDojoRecipeRequest {
|
interface IStartDojoRecipeRequest {
|
||||||
@ -20,10 +20,10 @@ interface IStartDojoRecipeRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const startDojoRecipeController: RequestHandler = async (req, res) => {
|
export const startDojoRecipeController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const account = await getAccountForRequest(req);
|
||||||
const inventory = await getInventory(accountId, "GuildId LevelKeys");
|
const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys");
|
||||||
const guild = await getGuildForRequestEx(req, inventory);
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Architect))) {
|
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, account._id, GuildPermission.Architect))) {
|
||||||
res.json({ DojoRequestStatus: -1 });
|
res.json({ DojoRequestStatus: -1 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -64,5 +64,5 @@ export const startDojoRecipeController: RequestHandler = async (req, res) => {
|
|||||||
setDojoRoomLogFunded(guild, component);
|
setDojoRoomLogFunded(guild, component);
|
||||||
}
|
}
|
||||||
await guild.save();
|
await guild.save();
|
||||||
res.json(await getDojoClient(guild, 0));
|
res.json(await getDojoClient(guild, 0, undefined, account.BuildLabel));
|
||||||
};
|
};
|
||||||
|
@ -3,12 +3,14 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getRecipe } from "@/src/services/itemDataService";
|
import { getRecipe } from "@/src/services/itemDataService";
|
||||||
import { addItem, freeUpSlot, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
import { addItem, addKubrowPet, freeUpSlot, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||||
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { InventorySlot, ISpectreLoadout } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { InventorySlot, ISpectreLoadout } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
import { fromOid, toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
import { ExportWeapons } from "warframe-public-export-plus";
|
import { ExportWeapons } from "warframe-public-export-plus";
|
||||||
|
import { getRandomElement } from "@/src/services/rngService";
|
||||||
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
|
||||||
interface IStartRecipeRequest {
|
interface IStartRecipeRequest {
|
||||||
RecipeName: string;
|
RecipeName: string;
|
||||||
@ -41,25 +43,40 @@ export const startRecipeController: RequestHandler = async (req, res) => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
for (let i = 0; i != recipe.ingredients.length; ++i) {
|
for (let i = 0; i != recipe.ingredients.length; ++i) {
|
||||||
if (startRecipeRequest.Ids[i]) {
|
if (startRecipeRequest.Ids[i] && startRecipeRequest.Ids[i][0] != "/") {
|
||||||
const category = ExportWeapons[recipe.ingredients[i].ItemType].productCategory;
|
if (recipe.ingredients[i].ItemType == "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem") {
|
||||||
if (category != "LongGuns" && category != "Pistols" && category != "Melee") {
|
const index = inventory.KubrowPetEggs.findIndex(x => x._id.equals(startRecipeRequest.Ids[i]));
|
||||||
throw new Error(`unexpected equipment ingredient type: ${category}`);
|
if (index != -1) {
|
||||||
|
inventory.KubrowPetEggs.splice(index, 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const category = ExportWeapons[recipe.ingredients[i].ItemType].productCategory;
|
||||||
|
if (category != "LongGuns" && category != "Pistols" && category != "Melee") {
|
||||||
|
throw new Error(`unexpected equipment ingredient type: ${category}`);
|
||||||
|
}
|
||||||
|
const equipmentIndex = inventory[category].findIndex(x => x._id.equals(startRecipeRequest.Ids[i]));
|
||||||
|
if (equipmentIndex == -1) {
|
||||||
|
throw new Error(`could not find equipment item to use for recipe`);
|
||||||
|
}
|
||||||
|
pr[category] ??= [];
|
||||||
|
pr[category].push(inventory[category][equipmentIndex]);
|
||||||
|
inventory[category].splice(equipmentIndex, 1);
|
||||||
|
freeUpSlot(inventory, InventorySlot.WEAPONS);
|
||||||
}
|
}
|
||||||
const equipmentIndex = inventory[category].findIndex(x => x._id.equals(startRecipeRequest.Ids[i]));
|
|
||||||
if (equipmentIndex == -1) {
|
|
||||||
throw new Error(`could not find equipment item to use for recipe`);
|
|
||||||
}
|
|
||||||
pr[category] ??= [];
|
|
||||||
pr[category].push(inventory[category][equipmentIndex]);
|
|
||||||
inventory[category].splice(equipmentIndex, 1);
|
|
||||||
freeUpSlot(inventory, InventorySlot.WEAPONS);
|
|
||||||
} else {
|
} else {
|
||||||
await addItem(inventory, recipe.ingredients[i].ItemType, recipe.ingredients[i].ItemCount * -1);
|
await addItem(inventory, recipe.ingredients[i].ItemType, recipe.ingredients[i].ItemCount * -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
|
let inventoryChanges: IInventoryChanges | undefined;
|
||||||
|
if (recipe.secretIngredientAction == "SIA_CREATE_KUBROW") {
|
||||||
|
inventoryChanges = addKubrowPet(inventory, getRandomElement(recipe.secretIngredients!)!.ItemType);
|
||||||
|
pr.KubrowPet = new Types.ObjectId(fromOid(inventoryChanges.KubrowPets![0].ItemId));
|
||||||
|
} else if (recipe.secretIngredientAction == "SIA_DISTILL_PRINT") {
|
||||||
|
pr.KubrowPet = new Types.ObjectId(startRecipeRequest.Ids[recipe.ingredients.length]);
|
||||||
|
const pet = inventory.KubrowPets.id(pr.KubrowPet)!;
|
||||||
|
pet.Details!.PrintsRemaining -= 1;
|
||||||
|
} else if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
|
||||||
const spectreLoadout: ISpectreLoadout = {
|
const spectreLoadout: ISpectreLoadout = {
|
||||||
ItemType: recipe.resultType,
|
ItemType: recipe.resultType,
|
||||||
Suits: "",
|
Suits: "",
|
||||||
@ -116,5 +133,5 @@ export const startRecipeController: RequestHandler = async (req, res) => {
|
|||||||
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
|
|
||||||
res.json({ RecipeId: toOid(pr._id) });
|
res.json({ RecipeId: toOid(pr._id), InventoryChanges: inventoryChanges });
|
||||||
};
|
};
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { ExportNightwave, ExportSyndicates, ISyndicateSacrifice } from "warframe-public-export-plus";
|
import { ExportSyndicates, ISyndicateSacrifice } from "warframe-public-export-plus";
|
||||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||||
import { addMiscItems, combineInventoryChanges, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
import { addMiscItem, combineInventoryChanges, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { isStoreItem, toStoreItem } from "@/src/services/itemDataService";
|
import { toStoreItem } from "@/src/services/itemDataService";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
|
|
||||||
const nightwaveCredsItemType = ExportNightwave.rewards[ExportNightwave.rewards.length - 1].uniqueName;
|
|
||||||
|
|
||||||
export const syndicateSacrificeController: RequestHandler = async (request, response) => {
|
export const syndicateSacrificeController: RequestHandler = async (request, response) => {
|
||||||
const accountId = await getAccountIdForRequest(request);
|
const accountId = await getAccountIdForRequest(request);
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
@ -20,74 +18,83 @@ export const syndicateSacrificeController: RequestHandler = async (request, resp
|
|||||||
syndicate = inventory.Affiliations[inventory.Affiliations.push({ Tag: data.AffiliationTag, Standing: 0 }) - 1];
|
syndicate = inventory.Affiliations[inventory.Affiliations.push({ Tag: data.AffiliationTag, Standing: 0 }) - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
const level = data.SacrificeLevel - (syndicate.Title ?? 0);
|
const oldLevel = syndicate.Title ?? 0;
|
||||||
|
const levelIncrease = data.SacrificeLevel - oldLevel;
|
||||||
|
if (levelIncrease < 0) {
|
||||||
|
throw new Error(`syndicate sacrifice can not decrease level`);
|
||||||
|
}
|
||||||
|
if (levelIncrease > 1 && !data.AllowMultiple) {
|
||||||
|
throw new Error(`desired syndicate level is an increase of ${levelIncrease}, max. allowed increase is 1`);
|
||||||
|
}
|
||||||
|
|
||||||
const res: ISyndicateSacrificeResponse = {
|
const res: ISyndicateSacrificeResponse = {
|
||||||
AffiliationTag: data.AffiliationTag,
|
AffiliationTag: data.AffiliationTag,
|
||||||
InventoryChanges: {},
|
InventoryChanges: {},
|
||||||
Level: data.SacrificeLevel,
|
Level: data.SacrificeLevel,
|
||||||
LevelIncrease: level <= 0 ? 1 : level,
|
LevelIncrease: data.SacrificeLevel < 0 ? 1 : levelIncrease,
|
||||||
NewEpisodeReward: false
|
NewEpisodeReward: false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Process sacrifices and rewards for every level we're reaching
|
||||||
const manifest = ExportSyndicates[data.AffiliationTag];
|
const manifest = ExportSyndicates[data.AffiliationTag];
|
||||||
let sacrifice: ISyndicateSacrifice | undefined;
|
for (let level = oldLevel + Math.min(levelIncrease, 1); level <= data.SacrificeLevel; ++level) {
|
||||||
let reward: string | undefined;
|
let sacrifice: ISyndicateSacrifice | undefined;
|
||||||
if (data.SacrificeLevel == 0) {
|
if (level == 0) {
|
||||||
sacrifice = manifest.initiationSacrifice;
|
sacrifice = manifest.initiationSacrifice;
|
||||||
reward = manifest.initiationReward;
|
if (manifest.initiationReward) {
|
||||||
syndicate.Initiated = true;
|
combineInventoryChanges(
|
||||||
} else {
|
res.InventoryChanges,
|
||||||
sacrifice = manifest.titles?.find(x => x.level == data.SacrificeLevel)?.sacrifice;
|
(await handleStoreItemAcquisition(manifest.initiationReward, inventory)).InventoryChanges
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
syndicate.Initiated = true;
|
||||||
|
} else {
|
||||||
|
sacrifice = manifest.titles?.find(x => x.level == level)?.sacrifice;
|
||||||
|
}
|
||||||
|
|
||||||
if (sacrifice) {
|
if (sacrifice) {
|
||||||
res.InventoryChanges = { ...updateCurrency(inventory, sacrifice.credits, false) };
|
updateCurrency(inventory, sacrifice.credits, false, res.InventoryChanges);
|
||||||
|
|
||||||
const miscItemChanges = sacrifice.items.map(x => ({
|
for (const item of sacrifice.items) {
|
||||||
ItemType: x.ItemType,
|
addMiscItem(inventory, item.ItemType, item.ItemCount * -1, res.InventoryChanges);
|
||||||
ItemCount: x.ItemCount * -1
|
}
|
||||||
}));
|
}
|
||||||
addMiscItems(inventory, miscItemChanges);
|
|
||||||
res.InventoryChanges.MiscItems = miscItemChanges;
|
|
||||||
}
|
|
||||||
|
|
||||||
syndicate.Title ??= 0;
|
// Quacks like a nightwave syndicate?
|
||||||
syndicate.Title += 1;
|
if (manifest.dailyChallenges) {
|
||||||
|
const title = manifest.titles!.find(x => x.level == level);
|
||||||
if (syndicate.Title > 0 && manifest.favours.find(x => x.rankUpReward && x.requiredLevel == syndicate.Title)) {
|
if (title) {
|
||||||
syndicate.FreeFavorsEarned ??= [];
|
res.NewEpisodeReward = true;
|
||||||
if (!syndicate.FreeFavorsEarned.includes(syndicate.Title)) {
|
let rewardType: string;
|
||||||
syndicate.FreeFavorsEarned.push(syndicate.Title);
|
let rewardCount: number;
|
||||||
}
|
if (title.storeItemReward) {
|
||||||
}
|
rewardType = title.storeItemReward;
|
||||||
|
rewardCount = 1;
|
||||||
if (reward) {
|
} else {
|
||||||
combineInventoryChanges(
|
rewardType = toStoreItem(title.reward!.ItemType);
|
||||||
res.InventoryChanges,
|
rewardCount = title.reward!.ItemCount;
|
||||||
(await handleStoreItemAcquisition(reward, inventory)).InventoryChanges
|
}
|
||||||
);
|
const rewardInventoryChanges = (await handleStoreItemAcquisition(rewardType, inventory, rewardCount))
|
||||||
}
|
.InventoryChanges;
|
||||||
|
if (Object.keys(rewardInventoryChanges).length == 0) {
|
||||||
if (data.AffiliationTag == ExportNightwave.affiliationTag) {
|
logger.debug(`nightwave rank up reward did not seem to get added, giving 50 creds instead`);
|
||||||
const index = syndicate.Title - 1;
|
const nightwaveCredsItemType = manifest.titles![0].reward!.ItemType;
|
||||||
if (index < ExportNightwave.rewards.length) {
|
addMiscItem(inventory, nightwaveCredsItemType, 50, rewardInventoryChanges);
|
||||||
res.NewEpisodeReward = true;
|
}
|
||||||
const reward = ExportNightwave.rewards[index];
|
combineInventoryChanges(res.InventoryChanges, rewardInventoryChanges);
|
||||||
let rewardType = reward.uniqueName;
|
}
|
||||||
if (!isStoreItem(rewardType)) {
|
} else {
|
||||||
rewardType = toStoreItem(rewardType);
|
if (level > 0 && manifest.favours.find(x => x.rankUpReward && x.requiredLevel == level)) {
|
||||||
}
|
syndicate.FreeFavorsEarned ??= [];
|
||||||
const rewardInventoryChanges = (await handleStoreItemAcquisition(rewardType, inventory, reward.itemCount))
|
if (!syndicate.FreeFavorsEarned.includes(level)) {
|
||||||
.InventoryChanges;
|
syndicate.FreeFavorsEarned.push(level);
|
||||||
if (Object.keys(rewardInventoryChanges).length == 0) {
|
}
|
||||||
logger.debug(`nightwave rank up reward did not seem to get added, giving 50 creds instead`);
|
}
|
||||||
rewardInventoryChanges.MiscItems = [{ ItemType: nightwaveCredsItemType, ItemCount: 50 }];
|
|
||||||
addMiscItems(inventory, rewardInventoryChanges.MiscItems);
|
|
||||||
}
|
|
||||||
combineInventoryChanges(res.InventoryChanges, rewardInventoryChanges);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Commit
|
||||||
|
syndicate.Title = data.SacrificeLevel < 0 ? data.SacrificeLevel + 1 : data.SacrificeLevel;
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
|
|
||||||
response.json(res);
|
response.json(res);
|
||||||
|
@ -5,7 +5,7 @@ import { IMiscItem, InventorySlot } from "@/src/types/inventoryTypes/inventoryTy
|
|||||||
import { IOid } from "@/src/types/commonTypes";
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
import { ExportSyndicates, ExportWeapons } from "warframe-public-export-plus";
|
import { ExportSyndicates, ExportWeapons } from "warframe-public-export-plus";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IAffiliationMods, IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
|
|
||||||
export const syndicateStandingBonusController: RequestHandler = async (req, res) => {
|
export const syndicateStandingBonusController: RequestHandler = async (req, res) => {
|
||||||
@ -54,13 +54,14 @@ export const syndicateStandingBonusController: RequestHandler = async (req, res)
|
|||||||
inventoryChanges[slotBin] = { count: -1, platinum: 0, Slots: 1 };
|
inventoryChanges[slotBin] = { count: -1, platinum: 0, Slots: 1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const affiliationMod = addStanding(inventory, request.Operation.AffiliationTag, gainedStanding, true);
|
const affiliationMods: IAffiliationMods[] = [];
|
||||||
|
addStanding(inventory, request.Operation.AffiliationTag, gainedStanding, affiliationMods, true);
|
||||||
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
InventoryChanges: inventoryChanges,
|
InventoryChanges: inventoryChanges,
|
||||||
AffiliationMods: [affiliationMod]
|
AffiliationMods: affiliationMods
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -35,6 +35,17 @@ const trainingResultController: RequestHandler = async (req, res): Promise<void>
|
|||||||
inventory.PlayerLevel += 1;
|
inventory.PlayerLevel += 1;
|
||||||
inventory.TradesRemaining += 1;
|
inventory.TradesRemaining += 1;
|
||||||
|
|
||||||
|
if (inventory.PlayerLevel == 2) {
|
||||||
|
await createMessage(accountId, [
|
||||||
|
{
|
||||||
|
sndr: "/Lotus/Language/Game/Maroo",
|
||||||
|
msg: "/Lotus/Language/Clan/MarooClanSearchDesc",
|
||||||
|
sub: "/Lotus/Language/Clan/MarooClanSearchTitle",
|
||||||
|
icon: "/Lotus/Interface/Icons/Npcs/Maroo.png"
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
await createMessage(accountId, [
|
await createMessage(accountId, [
|
||||||
{
|
{
|
||||||
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
|
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
|
||||||
|
27
src/controllers/api/umbraController.ts
Normal file
27
src/controllers/api/umbraController.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { fromMongoDate, fromOid } from "@/src/helpers/inventoryHelpers";
|
||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { addMiscItem, getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const umbraController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "Suits MiscItems");
|
||||||
|
const payload = getJSONfromString<IUmbraRequest>(String(req.body));
|
||||||
|
for (const clientSuit of payload.Suits) {
|
||||||
|
const dbSuit = inventory.Suits.id(fromOid(clientSuit.ItemId))!;
|
||||||
|
if (clientSuit.UmbraDate) {
|
||||||
|
addMiscItem(inventory, "/Lotus/Types/Items/MiscItems/UmbraEchoes", -1);
|
||||||
|
dbSuit.UmbraDate = fromMongoDate(clientSuit.UmbraDate);
|
||||||
|
} else {
|
||||||
|
dbSuit.UmbraDate = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await inventory.save();
|
||||||
|
res.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IUmbraRequest {
|
||||||
|
Suits: IEquipmentClient[];
|
||||||
|
}
|
@ -1,28 +1,62 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountForRequest } from "@/src/services/loginService";
|
||||||
import { addChallenges, getInventory } from "@/src/services/inventoryService";
|
import { addCalendarProgress, addChallenges, getInventory } from "@/src/services/inventoryService";
|
||||||
import { IChallengeProgress, ISeasonChallenge } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IChallengeProgress, ISeasonChallenge } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { IAffiliationMods } from "@/src/types/purchaseTypes";
|
import { IAffiliationMods } from "@/src/types/purchaseTypes";
|
||||||
|
import { getEntriesUnsafe } from "@/src/utils/ts-utils";
|
||||||
|
import { logger } from "@/src/utils/logger";
|
||||||
|
|
||||||
export const updateChallengeProgressController: RequestHandler = async (req, res) => {
|
export const updateChallengeProgressController: RequestHandler = async (req, res) => {
|
||||||
const challenges = getJSONfromString<IUpdateChallengeProgressRequest>(String(req.body));
|
const challenges = getJSONfromString<IUpdateChallengeProgressRequest>(String(req.body));
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const account = await getAccountForRequest(req);
|
||||||
|
|
||||||
const inventory = await getInventory(accountId, "ChallengeProgress SeasonChallengeHistory Affiliations");
|
const inventory = await getInventory(
|
||||||
|
account._id.toString(),
|
||||||
|
"ChallengesFixVersion ChallengeProgress SeasonChallengeHistory Affiliations CalendarProgress"
|
||||||
|
);
|
||||||
let affiliationMods: IAffiliationMods[] = [];
|
let affiliationMods: IAffiliationMods[] = [];
|
||||||
if (challenges.ChallengeProgress) {
|
if (challenges.ChallengeProgress) {
|
||||||
affiliationMods = addChallenges(inventory, challenges.ChallengeProgress, challenges.SeasonChallengeCompletions);
|
affiliationMods = addChallenges(
|
||||||
|
account,
|
||||||
|
inventory,
|
||||||
|
challenges.ChallengeProgress,
|
||||||
|
challenges.SeasonChallengeCompletions
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (challenges.SeasonChallengeHistory) {
|
for (const [key, value] of getEntriesUnsafe(challenges)) {
|
||||||
challenges.SeasonChallengeHistory.forEach(({ challenge, id }) => {
|
if (value === undefined) {
|
||||||
const itemIndex = inventory.SeasonChallengeHistory.findIndex(i => i.challenge === challenge);
|
logger.error(`Challenge progress update key ${key} has no value`);
|
||||||
if (itemIndex !== -1) {
|
continue;
|
||||||
inventory.SeasonChallengeHistory[itemIndex].id = id;
|
}
|
||||||
} else {
|
switch (key) {
|
||||||
inventory.SeasonChallengeHistory.push({ challenge, id });
|
case "ChallengesFixVersion":
|
||||||
}
|
inventory.ChallengesFixVersion = value;
|
||||||
});
|
break;
|
||||||
|
|
||||||
|
case "SeasonChallengeHistory":
|
||||||
|
value.forEach(({ challenge, id }) => {
|
||||||
|
const itemIndex = inventory.SeasonChallengeHistory.findIndex(i => i.challenge === challenge);
|
||||||
|
if (itemIndex !== -1) {
|
||||||
|
inventory.SeasonChallengeHistory[itemIndex].id = id;
|
||||||
|
} else {
|
||||||
|
inventory.SeasonChallengeHistory.push({ challenge, id });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "CalendarProgress":
|
||||||
|
addCalendarProgress(inventory, value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "ChallengeProgress":
|
||||||
|
case "SeasonChallengeCompletions":
|
||||||
|
case "ChallengePTS":
|
||||||
|
case "crossPlaySetting":
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger.warn(`unknown challenge progress entry`, { key, value });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
|
|
||||||
@ -32,7 +66,11 @@ export const updateChallengeProgressController: RequestHandler = async (req, res
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface IUpdateChallengeProgressRequest {
|
interface IUpdateChallengeProgressRequest {
|
||||||
|
ChallengePTS?: number;
|
||||||
|
ChallengesFixVersion?: number;
|
||||||
ChallengeProgress?: IChallengeProgress[];
|
ChallengeProgress?: IChallengeProgress[];
|
||||||
SeasonChallengeHistory?: ISeasonChallenge[];
|
SeasonChallengeHistory?: ISeasonChallenge[];
|
||||||
SeasonChallengeCompletions?: ISeasonChallenge[];
|
SeasonChallengeCompletions?: ISeasonChallenge[];
|
||||||
|
CalendarProgress?: { challenge: string }[];
|
||||||
|
crossPlaySetting?: string;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { getInventory, addRecipes } from "@/src/services/inventoryService";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { ExportRecipes } from "warframe-public-export-plus";
|
||||||
|
|
||||||
|
export const addMissingHelminthBlueprintsController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "Recipes");
|
||||||
|
const allHelminthRecipes = Object.keys(ExportRecipes).filter(
|
||||||
|
key => ExportRecipes[key].secretIngredientAction === "SIA_WARFRAME_ABILITY"
|
||||||
|
);
|
||||||
|
const inventoryHelminthRecipes = inventory.Recipes.filter(recipe =>
|
||||||
|
recipe.ItemType.startsWith("/Lotus/Types/Recipes/AbilityOverrides/")
|
||||||
|
).map(recipe => recipe.ItemType);
|
||||||
|
|
||||||
|
const missingHelminthRecipes = allHelminthRecipes
|
||||||
|
.filter(key => !inventoryHelminthRecipes.includes(key))
|
||||||
|
.map(ItemType => ({ ItemType, ItemCount: 1 }));
|
||||||
|
|
||||||
|
addRecipes(inventory, missingHelminthRecipes);
|
||||||
|
|
||||||
|
await inventory.save();
|
||||||
|
res.end();
|
||||||
|
};
|
@ -1,5 +1,6 @@
|
|||||||
import { applyClientEquipmentUpdates, getInventory } from "@/src/services/inventoryService";
|
import { applyClientEquipmentUpdates, getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
@ -11,7 +12,7 @@ export const addXpController: RequestHandler = async (req, res) => {
|
|||||||
const request = req.body as IAddXpRequest;
|
const request = req.body as IAddXpRequest;
|
||||||
for (const [category, gear] of Object.entries(request)) {
|
for (const [category, gear] of Object.entries(request)) {
|
||||||
for (const clientItem of gear) {
|
for (const clientItem of gear) {
|
||||||
const dbItem = inventory[category as TEquipmentKey].id(clientItem.ItemId.$oid);
|
const dbItem = inventory[category as TEquipmentKey].id((clientItem.ItemId as IOid).$oid);
|
||||||
if (dbItem) {
|
if (dbItem) {
|
||||||
if (dbItem.ItemType in ExportMisc.uniqueLevelCaps) {
|
if (dbItem.ItemType in ExportMisc.uniqueLevelCaps) {
|
||||||
if ((dbItem.Polarized ?? 0) < 5) {
|
if ((dbItem.Polarized ?? 0) < 5) {
|
||||||
|
40
src/controllers/custom/completeAllMissionsController.ts
Normal file
40
src/controllers/custom/completeAllMissionsController.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { addString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { addFixedLevelRewards } from "@/src/services/missionInventoryUpdateService";
|
||||||
|
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||||
|
import { IMissionReward } from "@/src/types/missionTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { ExportRegions } from "warframe-public-export-plus";
|
||||||
|
|
||||||
|
export const completeAllMissionsController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
|
const MissionRewards: IMissionReward[] = [];
|
||||||
|
for (const [tag, node] of Object.entries(ExportRegions)) {
|
||||||
|
let mission = inventory.Missions.find(x => x.Tag == tag);
|
||||||
|
if (!mission) {
|
||||||
|
mission =
|
||||||
|
inventory.Missions[
|
||||||
|
inventory.Missions.push({
|
||||||
|
Completes: 0,
|
||||||
|
Tier: 0,
|
||||||
|
Tag: tag
|
||||||
|
}) - 1
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (mission.Completes == 0) {
|
||||||
|
mission.Completes++;
|
||||||
|
if (node.missionReward) {
|
||||||
|
addFixedLevelRewards(node.missionReward, MissionRewards);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mission.Tier = 1;
|
||||||
|
}
|
||||||
|
for (const reward of MissionRewards) {
|
||||||
|
await handleStoreItemAcquisition(reward.StoreItem, inventory, reward.ItemCount, undefined, true);
|
||||||
|
}
|
||||||
|
addString(inventory.NodeIntrosCompleted, "TeshinHardModeUnlocked");
|
||||||
|
await inventory.save();
|
||||||
|
res.end();
|
||||||
|
};
|
44
src/controllers/custom/configController.ts
Normal file
44
src/controllers/custom/configController.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { config } from "@/src/services/configService";
|
||||||
|
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
|
||||||
|
import { saveConfig } from "@/src/services/configWatcherService";
|
||||||
|
import { sendWsBroadcastExcept } from "@/src/services/webService";
|
||||||
|
|
||||||
|
export const getConfigController: RequestHandler = async (req, res) => {
|
||||||
|
const account = await getAccountForRequest(req);
|
||||||
|
if (isAdministrator(account)) {
|
||||||
|
const responseData: Record<string, boolean | string | number | null> = {};
|
||||||
|
for (const id of req.body as string[]) {
|
||||||
|
const [obj, idx] = configIdToIndexable(id);
|
||||||
|
responseData[id] = obj[idx] ?? null;
|
||||||
|
}
|
||||||
|
res.json(responseData);
|
||||||
|
} else {
|
||||||
|
res.status(401).end();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setConfigController: RequestHandler = async (req, res) => {
|
||||||
|
const account = await getAccountForRequest(req);
|
||||||
|
if (isAdministrator(account)) {
|
||||||
|
for (const [id, value] of Object.entries(req.body as Record<string, boolean | string | number>)) {
|
||||||
|
const [obj, idx] = configIdToIndexable(id);
|
||||||
|
obj[idx] = value;
|
||||||
|
}
|
||||||
|
sendWsBroadcastExcept(parseInt(String(req.query.wsid)), { config_reloaded: true });
|
||||||
|
await saveConfig();
|
||||||
|
res.end();
|
||||||
|
} else {
|
||||||
|
res.status(401).end();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const configIdToIndexable = (id: string): [Record<string, boolean | string | number | undefined>, string] => {
|
||||||
|
let obj = config as unknown as Record<string, never>;
|
||||||
|
const arr = id.split(".");
|
||||||
|
while (arr.length > 1) {
|
||||||
|
obj = obj[arr[0]];
|
||||||
|
arr.splice(0, 1);
|
||||||
|
}
|
||||||
|
return [obj, arr[0]];
|
||||||
|
};
|
@ -10,6 +10,7 @@ import { Stats } from "@/src/models/statsModel";
|
|||||||
import { GuildMember } from "@/src/models/guildModel";
|
import { GuildMember } from "@/src/models/guildModel";
|
||||||
import { Leaderboard } from "@/src/models/leaderboardModel";
|
import { Leaderboard } from "@/src/models/leaderboardModel";
|
||||||
import { deleteGuild } from "@/src/services/guildService";
|
import { deleteGuild } from "@/src/services/guildService";
|
||||||
|
import { Friendship } from "@/src/models/friendModel";
|
||||||
|
|
||||||
export const deleteAccountController: RequestHandler = async (req, res) => {
|
export const deleteAccountController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
@ -22,6 +23,8 @@ export const deleteAccountController: RequestHandler = async (req, res) => {
|
|||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
Account.deleteOne({ _id: accountId }),
|
Account.deleteOne({ _id: accountId }),
|
||||||
|
Friendship.deleteMany({ owner: accountId }),
|
||||||
|
Friendship.deleteMany({ friend: accountId }),
|
||||||
GuildMember.deleteMany({ accountId: accountId }),
|
GuildMember.deleteMany({ accountId: accountId }),
|
||||||
Ignore.deleteMany({ ignorer: accountId }),
|
Ignore.deleteMany({ ignorer: accountId }),
|
||||||
Ignore.deleteMany({ ignoree: accountId }),
|
Ignore.deleteMany({ ignoree: accountId }),
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
import { AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
|
import { AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
|
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
export const getAccountInfoController: RequestHandler = async (req, res) => {
|
export const getAccountInfoController: RequestHandler = async (req, res) => {
|
||||||
const account = await getAccountForRequest(req);
|
const account = await getAccountForRequest(req);
|
||||||
|
const inventory = await getInventory(account._id.toString(), "QuestKeys");
|
||||||
const info: IAccountInfo = {
|
const info: IAccountInfo = {
|
||||||
DisplayName: account.DisplayName
|
DisplayName: account.DisplayName,
|
||||||
|
IsAdministrator: isAdministrator(account),
|
||||||
|
CompletedVorsPrize: !!inventory.QuestKeys.find(
|
||||||
|
x => x.ItemType == "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain"
|
||||||
|
)?.Completed
|
||||||
};
|
};
|
||||||
if (isAdministrator(account)) {
|
|
||||||
info.IsAdministrator = true;
|
|
||||||
}
|
|
||||||
const guildMember = await GuildMember.findOne({ accountId: account._id, status: 0 }, "guildId rank");
|
const guildMember = await GuildMember.findOne({ accountId: account._id, status: 0 }, "guildId rank");
|
||||||
if (guildMember) {
|
if (guildMember) {
|
||||||
const guild = (await Guild.findById(guildMember.guildId, "Ranks AllianceId"))!;
|
const guild = (await Guild.findById(guildMember.guildId, "Ranks AllianceId"))!;
|
||||||
@ -31,7 +34,8 @@ export const getAccountInfoController: RequestHandler = async (req, res) => {
|
|||||||
|
|
||||||
interface IAccountInfo {
|
interface IAccountInfo {
|
||||||
DisplayName: string;
|
DisplayName: string;
|
||||||
IsAdministrator?: boolean;
|
IsAdministrator: boolean;
|
||||||
|
CompletedVorsPrize: boolean;
|
||||||
GuildId?: string;
|
GuildId?: string;
|
||||||
GuildPermissions?: number;
|
GuildPermissions?: number;
|
||||||
GuildRank?: number;
|
GuildRank?: number;
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
import { RequestHandler } from "express";
|
|
||||||
import { config } from "@/src/services/configService";
|
|
||||||
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
|
|
||||||
|
|
||||||
const getConfigDataController: RequestHandler = async (req, res) => {
|
|
||||||
const account = await getAccountForRequest(req);
|
|
||||||
if (isAdministrator(account)) {
|
|
||||||
res.json(config);
|
|
||||||
} else {
|
|
||||||
res.status(401).end();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export { getConfigDataController };
|
|
@ -3,6 +3,7 @@ import { getDict, getItemName, getString } from "@/src/services/itemDataService"
|
|||||||
import {
|
import {
|
||||||
ExportArcanes,
|
ExportArcanes,
|
||||||
ExportAvionics,
|
ExportAvionics,
|
||||||
|
ExportBoosters,
|
||||||
ExportCustoms,
|
ExportCustoms,
|
||||||
ExportDrones,
|
ExportDrones,
|
||||||
ExportGear,
|
ExportGear,
|
||||||
@ -19,12 +20,12 @@ import {
|
|||||||
ExportWeapons,
|
ExportWeapons,
|
||||||
TRelicQuality
|
TRelicQuality
|
||||||
} from "warframe-public-export-plus";
|
} from "warframe-public-export-plus";
|
||||||
import archonCrystalUpgrades from "@/static/fixed_responses/webuiArchonCrystalUpgrades.json";
|
|
||||||
import allIncarnons from "@/static/fixed_responses/allIncarnonList.json";
|
import allIncarnons from "@/static/fixed_responses/allIncarnonList.json";
|
||||||
|
|
||||||
interface ListedItem {
|
interface ListedItem {
|
||||||
uniqueName: string;
|
uniqueName: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
subtype?: string;
|
||||||
fusionLimit?: number;
|
fusionLimit?: number;
|
||||||
exalted?: string[];
|
exalted?: string[];
|
||||||
badReason?: "starter" | "frivolous" | "notraw";
|
badReason?: "starter" | "frivolous" | "notraw";
|
||||||
@ -34,7 +35,6 @@ interface ListedItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ItemLists {
|
interface ItemLists {
|
||||||
archonCrystalUpgrades: Record<string, string>;
|
|
||||||
uniqueLevelCaps: Record<string, number>;
|
uniqueLevelCaps: Record<string, number>;
|
||||||
Suits: ListedItem[];
|
Suits: ListedItem[];
|
||||||
LongGuns: ListedItem[];
|
LongGuns: ListedItem[];
|
||||||
@ -54,6 +54,8 @@ interface ItemLists {
|
|||||||
KubrowPets: ListedItem[];
|
KubrowPets: ListedItem[];
|
||||||
EvolutionProgress: ListedItem[];
|
EvolutionProgress: ListedItem[];
|
||||||
mods: ListedItem[];
|
mods: ListedItem[];
|
||||||
|
Boosters: ListedItem[];
|
||||||
|
//circuitGameModes: ListedItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const relicQualitySuffixes: Record<TRelicQuality, string> = {
|
const relicQualitySuffixes: Record<TRelicQuality, string> = {
|
||||||
@ -63,10 +65,13 @@ const relicQualitySuffixes: Record<TRelicQuality, string> = {
|
|||||||
VPQ_PLATINUM: " [Exceptional]"
|
VPQ_PLATINUM: " [Exceptional]"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*const toTitleCase = (str: string): string => {
|
||||||
|
return str.replace(/[^\s-]+/g, word => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase());
|
||||||
|
};*/
|
||||||
|
|
||||||
const getItemListsController: RequestHandler = (req, response) => {
|
const getItemListsController: RequestHandler = (req, response) => {
|
||||||
const lang = getDict(typeof req.query.lang == "string" ? req.query.lang : "en");
|
const lang = getDict(typeof req.query.lang == "string" ? req.query.lang : "en");
|
||||||
const res: ItemLists = {
|
const res: ItemLists = {
|
||||||
archonCrystalUpgrades,
|
|
||||||
uniqueLevelCaps: ExportMisc.uniqueLevelCaps,
|
uniqueLevelCaps: ExportMisc.uniqueLevelCaps,
|
||||||
Suits: [],
|
Suits: [],
|
||||||
LongGuns: [],
|
LongGuns: [],
|
||||||
@ -85,7 +90,38 @@ const getItemListsController: RequestHandler = (req, response) => {
|
|||||||
QuestKeys: [],
|
QuestKeys: [],
|
||||||
KubrowPets: [],
|
KubrowPets: [],
|
||||||
EvolutionProgress: [],
|
EvolutionProgress: [],
|
||||||
mods: []
|
mods: [],
|
||||||
|
Boosters: []
|
||||||
|
/*circuitGameModes: [
|
||||||
|
{
|
||||||
|
uniqueName: "Survival",
|
||||||
|
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Survival", lang))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uniqueName: "VoidFlood",
|
||||||
|
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Corruption", lang))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uniqueName: "Excavation",
|
||||||
|
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Excavation", lang))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uniqueName: "Defense",
|
||||||
|
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Defense", lang))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uniqueName: "Exterminate",
|
||||||
|
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Exterminate", lang))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uniqueName: "Assassination",
|
||||||
|
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Assassination", lang))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uniqueName: "Alchemy",
|
||||||
|
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Alchemy", lang))
|
||||||
|
}
|
||||||
|
]*/
|
||||||
};
|
};
|
||||||
for (const [uniqueName, item] of Object.entries(ExportWarframes)) {
|
for (const [uniqueName, item] of Object.entries(ExportWarframes)) {
|
||||||
res[item.productCategory].push({
|
res[item.productCategory].push({
|
||||||
@ -175,7 +211,8 @@ const getItemListsController: RequestHandler = (req, response) => {
|
|||||||
) {
|
) {
|
||||||
res.miscitems.push({
|
res.miscitems.push({
|
||||||
uniqueName: uniqueName,
|
uniqueName: uniqueName,
|
||||||
name: name
|
name: name,
|
||||||
|
subtype: "Resource"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -193,7 +230,8 @@ const getItemListsController: RequestHandler = (req, response) => {
|
|||||||
for (const [uniqueName, item] of Object.entries(ExportGear)) {
|
for (const [uniqueName, item] of Object.entries(ExportGear)) {
|
||||||
res.miscitems.push({
|
res.miscitems.push({
|
||||||
uniqueName: uniqueName,
|
uniqueName: uniqueName,
|
||||||
name: getString(item.name, lang)
|
name: getString(item.name, lang),
|
||||||
|
subtype: "Gear"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const recipeNameTemplate = getString("/Lotus/Language/Items/BlueprintAndItem", lang);
|
const recipeNameTemplate = getString("/Lotus/Language/Items/BlueprintAndItem", lang);
|
||||||
@ -293,6 +331,13 @@ const getItemListsController: RequestHandler = (req, response) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const item of Object.values(ExportBoosters)) {
|
||||||
|
res.Boosters.push({
|
||||||
|
uniqueName: item.typeName,
|
||||||
|
name: getString(item.name, lang)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
response.json(res);
|
response.json(res);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
|
|||||||
await completeQuest(inventory, questKey.ItemType);
|
await completeQuest(inventory, questKey.ItemType);
|
||||||
} else {
|
} else {
|
||||||
const progress = {
|
const progress = {
|
||||||
c: questManifest.chainStages![currentStage].key ? -1 : 0,
|
c: 0,
|
||||||
i: false,
|
i: false,
|
||||||
m: false,
|
m: false,
|
||||||
b: []
|
b: []
|
||||||
|
@ -12,6 +12,7 @@ export const popArchonCrystalUpgradeController: RequestHandler = async (req, res
|
|||||||
);
|
);
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.end();
|
res.end();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
res.status(400).end();
|
res.status(400).end();
|
||||||
};
|
};
|
||||||
|
@ -15,6 +15,7 @@ export const pushArchonCrystalUpgradeController: RequestHandler = async (req, re
|
|||||||
}
|
}
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.end();
|
res.end();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res.status(400).end();
|
res.status(400).end();
|
||||||
|
45
src/controllers/custom/setBoosterController.ts
Normal file
45
src/controllers/custom/setBoosterController.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { ExportBoosters } from "warframe-public-export-plus";
|
||||||
|
|
||||||
|
const I32_MAX = 0x7fffffff;
|
||||||
|
|
||||||
|
export const setBoosterController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const requests = req.body as { ItemType: string; ExpiryDate: number }[];
|
||||||
|
const inventory = await getInventory(accountId, "Boosters");
|
||||||
|
const boosters = inventory.Boosters;
|
||||||
|
if (
|
||||||
|
requests.some(request => {
|
||||||
|
if (typeof request.ItemType !== "string") return true;
|
||||||
|
if (Object.entries(ExportBoosters).find(([_, item]) => item.typeName === request.ItemType) === undefined)
|
||||||
|
return true;
|
||||||
|
if (typeof request.ExpiryDate !== "number") return true;
|
||||||
|
if (request.ExpiryDate < 0 || request.ExpiryDate > I32_MAX) return true;
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
res.status(400).send("Invalid ItemType provided.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const now = Math.trunc(Date.now() / 1000);
|
||||||
|
for (const { ItemType, ExpiryDate } of requests) {
|
||||||
|
if (ExpiryDate <= now) {
|
||||||
|
// remove expired boosters
|
||||||
|
const index = boosters.findIndex(item => item.ItemType === ItemType);
|
||||||
|
if (index !== -1) {
|
||||||
|
boosters.splice(index, 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const boosterItem = boosters.find(item => item.ItemType === ItemType);
|
||||||
|
if (boosterItem) {
|
||||||
|
boosterItem.ExpiryDate = ExpiryDate;
|
||||||
|
} else {
|
||||||
|
boosters.push({ ItemType, ExpiryDate });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await inventory.save();
|
||||||
|
res.end();
|
||||||
|
};
|
@ -1,15 +0,0 @@
|
|||||||
import { RequestHandler } from "express";
|
|
||||||
import { updateConfig } from "@/src/services/configWatcherService";
|
|
||||||
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
|
|
||||||
|
|
||||||
const updateConfigDataController: RequestHandler = async (req, res) => {
|
|
||||||
const account = await getAccountForRequest(req);
|
|
||||||
if (isAdministrator(account)) {
|
|
||||||
await updateConfig(String(req.body));
|
|
||||||
res.end();
|
|
||||||
} else {
|
|
||||||
res.status(401).end();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export { updateConfigDataController };
|
|
39
src/controllers/custom/updateFingerprintController.ts
Normal file
39
src/controllers/custom/updateFingerprintController.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { WeaponTypeInternal } from "@/src/services/itemDataService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const updateFingerprintController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const request = req.body as IUpdateFingerPrintRequest;
|
||||||
|
const inventory = await getInventory(accountId, request.category);
|
||||||
|
const item = inventory[request.category].id(request.oid);
|
||||||
|
if (item) {
|
||||||
|
if (request.action == "set" && request.upgradeFingerprint.buffs[0].Tag) {
|
||||||
|
const newUpgradeFingerprint = request.upgradeFingerprint;
|
||||||
|
if (!newUpgradeFingerprint.compact) newUpgradeFingerprint.compact = item.ItemType;
|
||||||
|
|
||||||
|
item.UpgradeType = request.upgradeType;
|
||||||
|
item.UpgradeFingerprint = JSON.stringify(newUpgradeFingerprint);
|
||||||
|
} else if (request.action == "remove") {
|
||||||
|
item.UpgradeFingerprint = undefined;
|
||||||
|
item.UpgradeType = undefined;
|
||||||
|
}
|
||||||
|
await inventory.save();
|
||||||
|
}
|
||||||
|
res.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IUpdateFingerPrintRequest {
|
||||||
|
category: WeaponTypeInternal;
|
||||||
|
oid: string;
|
||||||
|
action: "set" | "remove";
|
||||||
|
upgradeType: string;
|
||||||
|
upgradeFingerprint: {
|
||||||
|
compact?: string;
|
||||||
|
buffs: {
|
||||||
|
Tag: string;
|
||||||
|
Value: number;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
}
|
10
src/controllers/custom/webuiFileChangeDetectedController.ts
Normal file
10
src/controllers/custom/webuiFileChangeDetectedController.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { args } from "@/src/helpers/commandLineArguments";
|
||||||
|
import { sendWsBroadcast } from "@/src/services/webService";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const webuiFileChangeDetectedController: RequestHandler = (req, res) => {
|
||||||
|
if (args.dev && args.secret && req.query.secret == args.secret) {
|
||||||
|
sendWsBroadcast({ reload: true });
|
||||||
|
}
|
||||||
|
res.end();
|
||||||
|
};
|
@ -18,64 +18,76 @@ import {
|
|||||||
ITypeXPItem
|
ITypeXPItem
|
||||||
} from "@/src/types/inventoryTypes/inventoryTypes";
|
} from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { catBreadHash } from "@/src/helpers/stringHelpers";
|
import { catBreadHash, getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { ExportCustoms, ExportDojoRecipes } from "warframe-public-export-plus";
|
import { ExportCustoms, ExportDojoRecipes } from "warframe-public-export-plus";
|
||||||
import { IStatsClient } from "@/src/types/statTypes";
|
import { IStatsClient } from "@/src/types/statTypes";
|
||||||
import { toStoreItem } from "@/src/services/itemDataService";
|
import { toStoreItem } from "@/src/services/itemDataService";
|
||||||
|
import { FlattenMaps } from "mongoose";
|
||||||
|
|
||||||
export const getProfileViewingDataController: RequestHandler = async (req, res) => {
|
const getProfileViewingDataByPlayerIdImpl = async (playerId: string): Promise<IProfileViewingData | undefined> => {
|
||||||
|
const account = await Account.findById(playerId, "DisplayName");
|
||||||
|
if (!account) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const inventory = (await Inventory.findOne({ accountOwnerId: account._id }))!;
|
||||||
|
|
||||||
|
const result: IPlayerProfileViewingDataResult = {
|
||||||
|
AccountId: toOid(account._id),
|
||||||
|
DisplayName: account.DisplayName,
|
||||||
|
PlayerLevel: inventory.PlayerLevel,
|
||||||
|
LoadOutInventory: {
|
||||||
|
WeaponSkins: [],
|
||||||
|
XPInfo: inventory.XPInfo
|
||||||
|
},
|
||||||
|
PlayerSkills: inventory.PlayerSkills,
|
||||||
|
ChallengeProgress: inventory.ChallengeProgress,
|
||||||
|
DeathMarks: inventory.DeathMarks,
|
||||||
|
Harvestable: inventory.Harvestable,
|
||||||
|
DeathSquadable: inventory.DeathSquadable,
|
||||||
|
Created: toMongoDate(inventory.Created),
|
||||||
|
MigratedToConsole: false,
|
||||||
|
Missions: inventory.Missions,
|
||||||
|
Affiliations: inventory.Affiliations,
|
||||||
|
DailyFocus: inventory.DailyFocus,
|
||||||
|
Wishlist: inventory.Wishlist,
|
||||||
|
Alignment: inventory.Alignment
|
||||||
|
};
|
||||||
|
await populateLoadout(inventory, result);
|
||||||
|
if (inventory.GuildId) {
|
||||||
|
const guild = (await Guild.findById(inventory.GuildId, "Name Tier XP Class Emblem"))!;
|
||||||
|
populateGuild(guild, result);
|
||||||
|
}
|
||||||
|
for (const key of allDailyAffiliationKeys) {
|
||||||
|
result[key] = inventory[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
const stats = (await Stats.findOne({ accountOwnerId: account._id }))!.toJSON<Partial<TStatsDatabaseDocument>>();
|
||||||
|
delete stats._id;
|
||||||
|
delete stats.__v;
|
||||||
|
delete stats.accountOwnerId;
|
||||||
|
|
||||||
|
return {
|
||||||
|
Results: [result],
|
||||||
|
TechProjects: [],
|
||||||
|
XpComponents: [],
|
||||||
|
//XpCacheExpiryDate, some IMongoDate in the future, no clue what it's for
|
||||||
|
Stats: stats
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getProfileViewingDataGetController: RequestHandler = async (req, res) => {
|
||||||
if (req.query.playerId) {
|
if (req.query.playerId) {
|
||||||
const account = await Account.findById(req.query.playerId as string, "DisplayName");
|
const data = await getProfileViewingDataByPlayerIdImpl(req.query.playerId as string);
|
||||||
if (!account) {
|
if (data) {
|
||||||
|
res.json(data);
|
||||||
|
} else {
|
||||||
res.status(409).send("Could not find requested account");
|
res.status(409).send("Could not find requested account");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
const inventory = (await Inventory.findOne({ accountOwnerId: account._id }))!;
|
|
||||||
|
|
||||||
const result: IPlayerProfileViewingDataResult = {
|
|
||||||
AccountId: toOid(account._id),
|
|
||||||
DisplayName: account.DisplayName,
|
|
||||||
PlayerLevel: inventory.PlayerLevel,
|
|
||||||
LoadOutInventory: {
|
|
||||||
WeaponSkins: [],
|
|
||||||
XPInfo: inventory.XPInfo
|
|
||||||
},
|
|
||||||
PlayerSkills: inventory.PlayerSkills,
|
|
||||||
ChallengeProgress: inventory.ChallengeProgress,
|
|
||||||
DeathMarks: inventory.DeathMarks,
|
|
||||||
Harvestable: inventory.Harvestable,
|
|
||||||
DeathSquadable: inventory.DeathSquadable,
|
|
||||||
Created: toMongoDate(inventory.Created),
|
|
||||||
MigratedToConsole: false,
|
|
||||||
Missions: inventory.Missions,
|
|
||||||
Affiliations: inventory.Affiliations,
|
|
||||||
DailyFocus: inventory.DailyFocus,
|
|
||||||
Wishlist: inventory.Wishlist,
|
|
||||||
Alignment: inventory.Alignment
|
|
||||||
};
|
|
||||||
await populateLoadout(inventory, result);
|
|
||||||
if (inventory.GuildId) {
|
|
||||||
const guild = (await Guild.findById(inventory.GuildId, "Name Tier XP Class Emblem"))!;
|
|
||||||
populateGuild(guild, result);
|
|
||||||
}
|
|
||||||
for (const key of allDailyAffiliationKeys) {
|
|
||||||
result[key] = inventory[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
const stats = (await Stats.findOne({ accountOwnerId: account._id }))!.toJSON<Partial<TStatsDatabaseDocument>>();
|
|
||||||
delete stats._id;
|
|
||||||
delete stats.__v;
|
|
||||||
delete stats.accountOwnerId;
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
Results: [result],
|
|
||||||
TechProjects: [],
|
|
||||||
XpComponents: [],
|
|
||||||
//XpCacheExpiryDate, some IMongoDate in the future, no clue what it's for
|
|
||||||
Stats: stats
|
|
||||||
});
|
|
||||||
} else if (req.query.guildId) {
|
} else if (req.query.guildId) {
|
||||||
const guild = await Guild.findById(req.query.guildId, "Name Tier XP Class Emblem TechProjects ClaimedXP");
|
const guild = await Guild.findById(
|
||||||
|
req.query.guildId as string,
|
||||||
|
"Name Tier XP Class Emblem TechProjects ClaimedXP"
|
||||||
|
);
|
||||||
if (!guild) {
|
if (!guild) {
|
||||||
res.status(409).send("Could not find guild");
|
res.status(409).send("Could not find guild");
|
||||||
return;
|
return;
|
||||||
@ -170,6 +182,28 @@ export const getProfileViewingDataController: RequestHandler = async (req, res)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// For old versions, this was an authenticated POST request.
|
||||||
|
interface IGetProfileViewingDataRequest {
|
||||||
|
AccountId: string;
|
||||||
|
}
|
||||||
|
export const getProfileViewingDataPostController: RequestHandler = async (req, res) => {
|
||||||
|
const payload = getJSONfromString<IGetProfileViewingDataRequest>(String(req.body));
|
||||||
|
const data = await getProfileViewingDataByPlayerIdImpl(payload.AccountId);
|
||||||
|
if (data) {
|
||||||
|
res.json(data);
|
||||||
|
} else {
|
||||||
|
res.status(409).send("Could not find requested account");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IProfileViewingData {
|
||||||
|
Results: IPlayerProfileViewingDataResult[];
|
||||||
|
TechProjects: [];
|
||||||
|
XpComponents: [];
|
||||||
|
//XpCacheExpiryDate, some IMongoDate in the future, no clue what it's for
|
||||||
|
Stats: FlattenMaps<Partial<TStatsDatabaseDocument>>;
|
||||||
|
}
|
||||||
|
|
||||||
interface IPlayerProfileViewingDataResult extends Partial<IDailyAffiliations> {
|
interface IPlayerProfileViewingDataResult extends Partial<IDailyAffiliations> {
|
||||||
AccountId: IOid;
|
AccountId: IOid;
|
||||||
DisplayName: string;
|
DisplayName: string;
|
||||||
|
@ -1,6 +1,19 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getWorldState } from "@/src/services/worldStateService";
|
import { getWorldState, populateDailyDeal, populateFissures } from "@/src/services/worldStateService";
|
||||||
|
import { version_compare } from "@/src/helpers/inventoryHelpers";
|
||||||
|
|
||||||
export const worldStateController: RequestHandler = (req, res) => {
|
export const worldStateController: RequestHandler = async (req, res) => {
|
||||||
res.json(getWorldState(req.query.buildLabel as string | undefined));
|
const buildLabel = req.query.buildLabel as string | undefined;
|
||||||
|
const worldState = getWorldState(buildLabel);
|
||||||
|
|
||||||
|
const populatePromises = [populateDailyDeal(worldState)];
|
||||||
|
|
||||||
|
// Omitting void fissures for versions prior to Dante Unbound to avoid script errors.
|
||||||
|
if (!buildLabel || version_compare(buildLabel, "2024.03.24.20.00") >= 0) {
|
||||||
|
populatePromises.push(populateFissures(worldState));
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(populatePromises);
|
||||||
|
|
||||||
|
res.json(worldState);
|
||||||
};
|
};
|
||||||
|
23
src/helpers/commandLineArguments.ts
Normal file
23
src/helpers/commandLineArguments.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
interface IArguments {
|
||||||
|
configPath?: string;
|
||||||
|
dev?: boolean;
|
||||||
|
secret?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const args: IArguments = {};
|
||||||
|
|
||||||
|
for (let i = 2; i < process.argv.length; ) {
|
||||||
|
switch (process.argv[i++]) {
|
||||||
|
case "--configPath":
|
||||||
|
args.configPath = process.argv[i++];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "--dev":
|
||||||
|
args.dev = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "--secret":
|
||||||
|
args.secret = process.argv[i++];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
@ -48,7 +48,8 @@ const toDatabaseAccount = (createAccount: IAccountCreation): IDatabaseAccountReq
|
|||||||
CrossPlatformAllowed: true,
|
CrossPlatformAllowed: true,
|
||||||
ForceLogoutVersion: 0,
|
ForceLogoutVersion: 0,
|
||||||
TrackedSettings: [],
|
TrackedSettings: [],
|
||||||
Nonce: 0
|
Nonce: 0,
|
||||||
|
LastLogin: new Date()
|
||||||
} satisfies IDatabaseAccountRequiredFields;
|
} satisfies IDatabaseAccountRequiredFields;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,9 +1,46 @@
|
|||||||
import { IMongoDate, IOid } from "@/src/types/commonTypes";
|
import { IMongoDate, IOid, IOidWithLegacySupport } from "@/src/types/commonTypes";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { TRarity } from "warframe-public-export-plus";
|
import { TRarity } from "warframe-public-export-plus";
|
||||||
|
|
||||||
|
export const version_compare = (a: string, b: string): number => {
|
||||||
|
const a_digits = a
|
||||||
|
.split("/")[0]
|
||||||
|
.split(".")
|
||||||
|
.map(x => parseInt(x));
|
||||||
|
const b_digits = b
|
||||||
|
.split("/")[0]
|
||||||
|
.split(".")
|
||||||
|
.map(x => parseInt(x));
|
||||||
|
for (let i = 0; i != a_digits.length; ++i) {
|
||||||
|
if (a_digits[i] != b_digits[i]) {
|
||||||
|
return a_digits[i] > b_digits[i] ? 1 : -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
export const toOid = (objectId: Types.ObjectId): IOid => {
|
export const toOid = (objectId: Types.ObjectId): IOid => {
|
||||||
return { $oid: objectId.toString() } satisfies IOid;
|
return { $oid: objectId.toString() };
|
||||||
|
};
|
||||||
|
|
||||||
|
export function toOid2(objectId: Types.ObjectId, buildLabel: undefined): IOid;
|
||||||
|
export function toOid2(objectId: Types.ObjectId, buildLabel: string | undefined): IOidWithLegacySupport;
|
||||||
|
export function toOid2(objectId: Types.ObjectId, buildLabel: string | undefined): IOidWithLegacySupport {
|
||||||
|
if (buildLabel && version_compare(buildLabel, "2016.12.21.19.13") <= 0) {
|
||||||
|
return { $id: objectId.toString() };
|
||||||
|
}
|
||||||
|
return { $oid: objectId.toString() };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const toLegacyOid = (oid: IOidWithLegacySupport): void => {
|
||||||
|
if (!("$id" in oid)) {
|
||||||
|
oid.$id = oid.$oid;
|
||||||
|
delete oid.$oid;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fromOid = (oid: IOidWithLegacySupport): string => {
|
||||||
|
return (oid.$oid ?? oid.$id)!;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toMongoDate = (date: Date): IMongoDate => {
|
export const toMongoDate = (date: Date): IMongoDate => {
|
||||||
|
@ -1,18 +1,203 @@
|
|||||||
import { ExportRegions, ExportWarframes } from "warframe-public-export-plus";
|
import { ExportRegions, ExportWarframes } from "warframe-public-export-plus";
|
||||||
import { IInfNode, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IInfNode, TNemesisFaction } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { getRewardAtPercentage, SRng } from "@/src/services/rngService";
|
import { getRewardAtPercentage, SRng } from "@/src/services/rngService";
|
||||||
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
|
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
import { IOid } from "../types/commonTypes";
|
import { IOid } from "../types/commonTypes";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { addMods, generateRewardSeed } from "../services/inventoryService";
|
import { addMods, generateRewardSeed } from "../services/inventoryService";
|
||||||
import { isArchwingMission, version_compare } from "../services/worldStateService";
|
import { isArchwingMission } from "../services/worldStateService";
|
||||||
import { fromStoreItem, toStoreItem } from "../services/itemDataService";
|
|
||||||
import { createMessage } from "../services/inboxService";
|
|
||||||
|
|
||||||
export const getInfNodes = (faction: string, rank: number): IInfNode[] => {
|
type TInnateDamageTag =
|
||||||
|
| "InnateElectricityDamage"
|
||||||
|
| "InnateHeatDamage"
|
||||||
|
| "InnateFreezeDamage"
|
||||||
|
| "InnateToxinDamage"
|
||||||
|
| "InnateMagDamage"
|
||||||
|
| "InnateRadDamage"
|
||||||
|
| "InnateImpactDamage";
|
||||||
|
|
||||||
|
export interface INemesisManifest {
|
||||||
|
weapons: readonly string[];
|
||||||
|
systemIndexes: readonly number[];
|
||||||
|
showdownNode: string;
|
||||||
|
ephemeraChance: number;
|
||||||
|
ephemeraTypes?: Record<TInnateDamageTag, string>;
|
||||||
|
firstKillReward: string;
|
||||||
|
firstConvertReward: string;
|
||||||
|
messageTitle: string;
|
||||||
|
messageBody: string;
|
||||||
|
minBuild: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class KuvaLichManifest implements INemesisManifest {
|
||||||
|
weapons = [
|
||||||
|
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Drakgoon/KuvaDrakgoon",
|
||||||
|
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Karak/KuvaKarak",
|
||||||
|
"/Lotus/Weapons/Grineer/Melee/GrnKuvaLichScythe/GrnKuvaLichScytheWeapon",
|
||||||
|
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Kohm/KuvaKohm",
|
||||||
|
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Ogris/KuvaOgris",
|
||||||
|
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Quartakk/KuvaQuartakk",
|
||||||
|
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Tonkor/KuvaTonkor",
|
||||||
|
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Brakk/KuvaBrakk",
|
||||||
|
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Kraken/KuvaKraken",
|
||||||
|
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Seer/KuvaSeer",
|
||||||
|
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Stubba/KuvaStubba",
|
||||||
|
"/Lotus/Weapons/Grineer/HeavyWeapons/GrnHeavyGrenadeLauncher",
|
||||||
|
"/Lotus/Weapons/Grineer/LongGuns/GrnKuvaLichRifle/GrnKuvaLichRifleWeapon"
|
||||||
|
];
|
||||||
|
systemIndexes = [2, 3, 9, 11, 18];
|
||||||
|
showdownNode = "CrewBattleNode557";
|
||||||
|
ephemeraChance = 0.05;
|
||||||
|
ephemeraTypes = {
|
||||||
|
InnateElectricityDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaLightningEphemera",
|
||||||
|
InnateHeatDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaFireEphemera",
|
||||||
|
InnateFreezeDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaIceEphemera",
|
||||||
|
InnateToxinDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaToxinEphemera",
|
||||||
|
InnateMagDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaMagneticEphemera",
|
||||||
|
InnateRadDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaTricksterEphemera",
|
||||||
|
InnateImpactDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaImpactEphemera"
|
||||||
|
};
|
||||||
|
firstKillReward = "/Lotus/StoreItems/Upgrades/Skins/Clan/LichKillerBadgeItem";
|
||||||
|
firstConvertReward = "/Lotus/StoreItems/Upgrades/Skins/Sigils/KuvaLichSigil";
|
||||||
|
messageTitle = "/Lotus/Language/Inbox/VanquishKuvaMsgTitle";
|
||||||
|
messageBody = "/Lotus/Language/Inbox/VanquishLichMsgBody";
|
||||||
|
minBuild = "2019.10.31.22.42"; // 26.0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
class KuvaLichManifestVersionTwo extends KuvaLichManifest {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.ephemeraChance = 0.1;
|
||||||
|
this.minBuild = "2020.03.05.16.06"; // Unsure about this one, so using the same value as in version three.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class KuvaLichManifestVersionThree extends KuvaLichManifestVersionTwo {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.weapons.push("/Lotus/Weapons/Grineer/Bows/GrnBow/GrnBowWeapon");
|
||||||
|
this.weapons.push("/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hind/KuvaHind");
|
||||||
|
this.weapons.push("/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Nukor/KuvaNukor");
|
||||||
|
this.ephemeraChance = 0.2;
|
||||||
|
this.minBuild = "2020.03.05.16.06"; // This is 27.2.0, tho 27.1.0 should also recognise this.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class KuvaLichManifestVersionFour extends KuvaLichManifestVersionThree {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.minBuild = "2021.07.05.17.03"; // Unsure about this one, so using the same value as in version five.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class KuvaLichManifestVersionFive extends KuvaLichManifestVersionFour {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.weapons.push("/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hek/KuvaHekWeapon");
|
||||||
|
this.weapons.push("/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Zarr/KuvaZarr");
|
||||||
|
this.weapons.push("/Lotus/Weapons/Grineer/KuvaLich/HeavyWeapons/Grattler/KuvaGrattler");
|
||||||
|
this.minBuild = "2021.07.05.17.03"; // 30.5.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class KuvaLichManifestVersionSix extends KuvaLichManifestVersionFive {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.weapons.push("/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Sobek/KuvaSobek");
|
||||||
|
this.minBuild = "2024.05.15.11.07"; // 35.6.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LawyerManifest implements INemesisManifest {
|
||||||
|
weapons = [
|
||||||
|
"/Lotus/Weapons/Corpus/LongGuns/CrpBriefcaseLauncher/CrpBriefcaseLauncher",
|
||||||
|
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEArcaPlasmor/CrpBEArcaPlasmor",
|
||||||
|
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEFluxRifle/CrpBEFluxRifle",
|
||||||
|
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBETetra/CrpBETetra",
|
||||||
|
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBECycron/CrpBECycron",
|
||||||
|
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEDetron/CrpBEDetron",
|
||||||
|
"/Lotus/Weapons/Corpus/Pistols/CrpIgniterPistol/CrpIgniterPistol",
|
||||||
|
"/Lotus/Weapons/Corpus/Pistols/CrpBriefcaseAkimbo/CrpBriefcaseAkimboPistol"
|
||||||
|
];
|
||||||
|
systemIndexes = [1, 15, 4, 7, 8];
|
||||||
|
showdownNode = "CrewBattleNode558";
|
||||||
|
ephemeraChance = 0.2;
|
||||||
|
ephemeraTypes = {
|
||||||
|
InnateElectricityDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraA",
|
||||||
|
InnateHeatDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraB",
|
||||||
|
InnateFreezeDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraC",
|
||||||
|
InnateToxinDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraD",
|
||||||
|
InnateMagDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraE",
|
||||||
|
InnateRadDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraF",
|
||||||
|
InnateImpactDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraG"
|
||||||
|
};
|
||||||
|
firstKillReward = "/Lotus/StoreItems/Upgrades/Skins/Clan/CorpusLichBadgeItem";
|
||||||
|
firstConvertReward = "/Lotus/StoreItems/Upgrades/Skins/Sigils/CorpusLichSigil";
|
||||||
|
messageTitle = "/Lotus/Language/Inbox/VanquishLawyerMsgTitle";
|
||||||
|
messageBody = "/Lotus/Language/Inbox/VanquishLichMsgBody";
|
||||||
|
minBuild = "2021.07.05.17.03"; // 30.5.0
|
||||||
|
}
|
||||||
|
|
||||||
|
class LawyerManifestVersionTwo extends LawyerManifest {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.weapons.push("/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEPlinx/CrpBEPlinxWeapon");
|
||||||
|
this.minBuild = "2022.11.30.08.13"; // 32.2.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LawyerManifestVersionThree extends LawyerManifestVersionTwo {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.weapons.push("/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEGlaxion/CrpBEGlaxion");
|
||||||
|
this.minBuild = "2024.05.15.11.07"; // 35.6.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LawyerManifestVersionFour extends LawyerManifestVersionThree {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.minBuild = "2024.10.01.11.03"; // 37.0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InfestedLichManfest implements INemesisManifest {
|
||||||
|
weapons = [];
|
||||||
|
systemIndexes = [23];
|
||||||
|
showdownNode = "CrewBattleNode559";
|
||||||
|
ephemeraChance = 0;
|
||||||
|
firstKillReward = "/Lotus/StoreItems/Upgrades/Skins/Sigils/InfLichVanquishedSigil";
|
||||||
|
firstConvertReward = "/Lotus/StoreItems/Upgrades/Skins/Sigils/InfLichConvertedSigil";
|
||||||
|
messageTitle = "/Lotus/Language/Inbox/VanquishBandMsgTitle";
|
||||||
|
messageBody = "/Lotus/Language/Inbox/VanquishBandMsgBody";
|
||||||
|
minBuild = "2025.03.18.09.51"; // 38.5.0
|
||||||
|
}
|
||||||
|
|
||||||
|
const nemesisManifests: Record<string, INemesisManifest> = {
|
||||||
|
"/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifest": new KuvaLichManifest(),
|
||||||
|
"/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionTwo": new KuvaLichManifestVersionTwo(),
|
||||||
|
"/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionThree": new KuvaLichManifestVersionThree(),
|
||||||
|
"/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionFour": new KuvaLichManifestVersionFour(),
|
||||||
|
"/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionFive": new KuvaLichManifestVersionFive(),
|
||||||
|
"/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionSix": new KuvaLichManifestVersionSix(),
|
||||||
|
"/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifest": new LawyerManifest(),
|
||||||
|
"/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionTwo": new LawyerManifestVersionTwo(),
|
||||||
|
"/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionThree": new LawyerManifestVersionThree(),
|
||||||
|
"/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionFour": new LawyerManifestVersionFour(),
|
||||||
|
"/Lotus/Types/Enemies/InfestedLich/InfestedLichManifest": new InfestedLichManfest()
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getNemesisManifest = (manifest: string): INemesisManifest => {
|
||||||
|
if (manifest in nemesisManifests) {
|
||||||
|
return nemesisManifests[manifest];
|
||||||
|
}
|
||||||
|
throw new Error(`unknown nemesis manifest: ${manifest}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getInfNodes = (manifest: INemesisManifest, rank: number): IInfNode[] => {
|
||||||
const infNodes = [];
|
const infNodes = [];
|
||||||
const systemIndex = systemIndexes[faction][rank];
|
const systemIndex = manifest.systemIndexes[rank];
|
||||||
for (const [key, value] of Object.entries(ExportRegions)) {
|
for (const [key, value] of Object.entries(ExportRegions)) {
|
||||||
if (
|
if (
|
||||||
value.systemIndex === systemIndex &&
|
value.systemIndex === systemIndex &&
|
||||||
@ -34,20 +219,8 @@ export const getInfNodes = (faction: string, rank: number): IInfNode[] => {
|
|||||||
return infNodes;
|
return infNodes;
|
||||||
};
|
};
|
||||||
|
|
||||||
const systemIndexes: Record<string, number[]> = {
|
|
||||||
FC_GRINEER: [2, 3, 9, 11, 18],
|
|
||||||
FC_CORPUS: [1, 15, 4, 7, 8],
|
|
||||||
FC_INFESTATION: [23]
|
|
||||||
};
|
|
||||||
|
|
||||||
export const showdownNodes: Record<string, string> = {
|
|
||||||
FC_GRINEER: "CrewBattleNode557",
|
|
||||||
FC_CORPUS: "CrewBattleNode558",
|
|
||||||
FC_INFESTATION: "CrewBattleNode559"
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get a parazon 'passcode' based on the nemesis fingerprint so it's always the same for the same nemesis.
|
// Get a parazon 'passcode' based on the nemesis fingerprint so it's always the same for the same nemesis.
|
||||||
export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: string }): number[] => {
|
export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: TNemesisFaction }): number[] => {
|
||||||
const rng = new SRng(nemesis.fp);
|
const rng = new SRng(nemesis.fp);
|
||||||
const choices = [0, 1, 2, 3, 5, 6, 7];
|
const choices = [0, 1, 2, 3, 5, 6, 7];
|
||||||
let choiceIndex = rng.randomInt(0, choices.length - 1);
|
let choiceIndex = rng.randomInt(0, choices.length - 1);
|
||||||
@ -64,7 +237,7 @@ export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: string }): nu
|
|||||||
return passcode;
|
return passcode;
|
||||||
};
|
};
|
||||||
|
|
||||||
const reqiuemMods: readonly string[] = [
|
/*const requiemMods: readonly string[] = [
|
||||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod",
|
"/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod",
|
||||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalTwoMod",
|
"/Lotus/Upgrades/Mods/Immortal/ImmortalTwoMod",
|
||||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalThreeMod",
|
"/Lotus/Upgrades/Mods/Immortal/ImmortalThreeMod",
|
||||||
@ -73,9 +246,9 @@ const reqiuemMods: readonly string[] = [
|
|||||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalSixMod",
|
"/Lotus/Upgrades/Mods/Immortal/ImmortalSixMod",
|
||||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalSevenMod",
|
"/Lotus/Upgrades/Mods/Immortal/ImmortalSevenMod",
|
||||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalEightMod"
|
"/Lotus/Upgrades/Mods/Immortal/ImmortalEightMod"
|
||||||
];
|
];*/
|
||||||
|
|
||||||
const antivirusMods: readonly string[] = [
|
export const antivirusMods: readonly string[] = [
|
||||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusOneMod",
|
"/Lotus/Upgrades/Mods/Immortal/AntivirusOneMod",
|
||||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusTwoMod",
|
"/Lotus/Upgrades/Mods/Immortal/AntivirusTwoMod",
|
||||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusThreeMod",
|
"/Lotus/Upgrades/Mods/Immortal/AntivirusThreeMod",
|
||||||
@ -86,33 +259,55 @@ const antivirusMods: readonly string[] = [
|
|||||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusEightMod"
|
"/Lotus/Upgrades/Mods/Immortal/AntivirusEightMod"
|
||||||
];
|
];
|
||||||
|
|
||||||
export const getNemesisPasscodeModTypes = (nemesis: { fp: bigint; Faction: string }): string[] => {
|
/*export const getNemesisPasscodeModTypes = (nemesis: { fp: bigint; Faction: TNemesisFaction }): string[] => {
|
||||||
const passcode = getNemesisPasscode(nemesis);
|
const passcode = getNemesisPasscode(nemesis);
|
||||||
return nemesis.Faction == "FC_INFESTATION"
|
return nemesis.Faction == "FC_INFESTATION"
|
||||||
? passcode.map(i => antivirusMods[i])
|
? passcode.map(i => antivirusMods[i])
|
||||||
: passcode.map(i => reqiuemMods[i]);
|
: passcode.map(i => requiemMods[i]);
|
||||||
};
|
};*/
|
||||||
|
|
||||||
export const encodeNemesisGuess = (
|
// Symbols; 0-7 are the normal requiem mods.
|
||||||
symbol1: number,
|
export const GUESS_NONE = 8;
|
||||||
result1: number,
|
export const GUESS_WILDCARD = 9;
|
||||||
symbol2: number,
|
|
||||||
result2: number,
|
// Results; there are 3, 4, 5 as well which are more muted versions but unused afaik.
|
||||||
symbol3: number,
|
export const GUESS_NEUTRAL = 0;
|
||||||
result3: number
|
export const GUESS_INCORRECT = 1;
|
||||||
): number => {
|
export const GUESS_CORRECT = 2;
|
||||||
|
|
||||||
|
interface NemesisPositionGuess {
|
||||||
|
symbol: number;
|
||||||
|
result: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NemesisGuess = [NemesisPositionGuess, NemesisPositionGuess, NemesisPositionGuess];
|
||||||
|
|
||||||
|
export const encodeNemesisGuess = (guess: NemesisGuess): number => {
|
||||||
return (
|
return (
|
||||||
(symbol1 & 0xf) |
|
(guess[0].symbol & 0xf) |
|
||||||
((result1 & 3) << 12) |
|
((guess[0].result & 3) << 12) |
|
||||||
((symbol2 << 4) & 0xff) |
|
((guess[1].symbol << 4) & 0xff) |
|
||||||
((result2 << 14) & 0xffff) |
|
((guess[1].result << 14) & 0xffff) |
|
||||||
((symbol3 & 0xf) << 8) |
|
((guess[2].symbol & 0xf) << 8) |
|
||||||
((result3 & 3) << 16)
|
((guess[2].result & 3) << 16)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const decodeNemesisGuess = (val: number): number[] => {
|
export const decodeNemesisGuess = (val: number): NemesisGuess => {
|
||||||
return [val & 0xf, (val >> 12) & 3, (val & 0xff) >> 4, (val & 0xffff) >> 14, (val >> 8) & 0xf, (val >> 16) & 3];
|
return [
|
||||||
|
{
|
||||||
|
symbol: val & 0xf,
|
||||||
|
result: (val >> 12) & 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
symbol: (val & 0xff) >> 4,
|
||||||
|
result: (val & 0xffff) >> 14
|
||||||
|
},
|
||||||
|
{
|
||||||
|
symbol: (val >> 8) & 0xf,
|
||||||
|
result: (val >> 16) & 3
|
||||||
|
}
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IKnifeResponse {
|
export interface IKnifeResponse {
|
||||||
@ -148,6 +343,27 @@ export const getKnifeUpgrade = (
|
|||||||
throw new Error(`${type} does not seem to be installed on parazon?!`);
|
throw new Error(`${type} does not seem to be installed on parazon?!`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const parseUpgrade = (
|
||||||
|
inventory: TInventoryDatabaseDocument,
|
||||||
|
str: string
|
||||||
|
): { ItemId: IOid; ItemType: string } => {
|
||||||
|
if (str.length == 24) {
|
||||||
|
const upgrade = inventory.Upgrades.id(str);
|
||||||
|
if (upgrade) {
|
||||||
|
return {
|
||||||
|
ItemId: { $oid: str },
|
||||||
|
ItemType: upgrade.ItemType
|
||||||
|
};
|
||||||
|
}
|
||||||
|
throw new Error(`Could not resolve oid ${str}`);
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
ItemId: { $oid: "000000000000000000000000" },
|
||||||
|
ItemType: str
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const consumeModCharge = (
|
export const consumeModCharge = (
|
||||||
response: IKnifeResponse,
|
response: IKnifeResponse,
|
||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
@ -199,92 +415,31 @@ export const consumeModCharge = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const kuvaLichVersionSixWeapons = [
|
export const getInnateDamageTag = (KillingSuit: string): TInnateDamageTag => {
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Drakgoon/KuvaDrakgoon",
|
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Karak/KuvaKarak",
|
|
||||||
"/Lotus/Weapons/Grineer/Melee/GrnKuvaLichScythe/GrnKuvaLichScytheWeapon",
|
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Kohm/KuvaKohm",
|
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Ogris/KuvaOgris",
|
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Quartakk/KuvaQuartakk",
|
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Tonkor/KuvaTonkor",
|
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Brakk/KuvaBrakk",
|
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Kraken/KuvaKraken",
|
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Seer/KuvaSeer",
|
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Stubba/KuvaStubba",
|
|
||||||
"/Lotus/Weapons/Grineer/HeavyWeapons/GrnHeavyGrenadeLauncher",
|
|
||||||
"/Lotus/Weapons/Grineer/LongGuns/GrnKuvaLichRifle/GrnKuvaLichRifleWeapon",
|
|
||||||
"/Lotus/Weapons/Grineer/Bows/GrnBow/GrnBowWeapon",
|
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hind/KuvaHind",
|
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Nukor/KuvaNukor",
|
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hek/KuvaHekWeapon",
|
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Zarr/KuvaZarr",
|
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/HeavyWeapons/Grattler/KuvaGrattler",
|
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Sobek/KuvaSobek"
|
|
||||||
];
|
|
||||||
|
|
||||||
const corpusVersionThreeWeapons = [
|
|
||||||
"/Lotus/Weapons/Corpus/LongGuns/CrpBriefcaseLauncher/CrpBriefcaseLauncher",
|
|
||||||
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEArcaPlasmor/CrpBEArcaPlasmor",
|
|
||||||
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEFluxRifle/CrpBEFluxRifle",
|
|
||||||
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBETetra/CrpBETetra",
|
|
||||||
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBECycron/CrpBECycron",
|
|
||||||
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEDetron/CrpBEDetron",
|
|
||||||
"/Lotus/Weapons/Corpus/Pistols/CrpIgniterPistol/CrpIgniterPistol",
|
|
||||||
"/Lotus/Weapons/Corpus/Pistols/CrpBriefcaseAkimbo/CrpBriefcaseAkimboPistol",
|
|
||||||
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEPlinx/CrpBEPlinxWeapon",
|
|
||||||
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEGlaxion/CrpBEGlaxion"
|
|
||||||
];
|
|
||||||
|
|
||||||
export const getWeaponsForManifest = (manifest: string): readonly string[] => {
|
|
||||||
switch (manifest) {
|
|
||||||
case "/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionSix": // >= 35.6.0
|
|
||||||
return kuvaLichVersionSixWeapons;
|
|
||||||
case "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionThree": // >= 35.6.0
|
|
||||||
case "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionFour": // >= 37.0.0
|
|
||||||
return corpusVersionThreeWeapons;
|
|
||||||
}
|
|
||||||
throw new Error(`unknown nemesis manifest: ${manifest}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isNemesisCompatibleWithVersion = (
|
|
||||||
nemesis: { manifest: string; Faction: string },
|
|
||||||
buildLabel: string
|
|
||||||
): boolean => {
|
|
||||||
// Anything below 35.6.0 is not going to be okay given our set of supported manifests.
|
|
||||||
if (version_compare(buildLabel, "2024.05.15.11.07") < 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nemesis.Faction == "FC_INFESTATION") {
|
|
||||||
// Anything below 38.5.0 isn't gonna like an infested lich.
|
|
||||||
if (version_compare(buildLabel, "2025.03.18.16.07") < 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (nemesis.manifest == "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionFour") {
|
|
||||||
// Anything below 37.0.0 isn't gonna know version 4, but version 3 is identical in terms of weapon choices, so we can spoof it to that.
|
|
||||||
if (version_compare(buildLabel, "2024.10.01.11.03") < 0) {
|
|
||||||
nemesis.manifest = "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionThree";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getInnateDamageTag = (
|
|
||||||
KillingSuit: string
|
|
||||||
):
|
|
||||||
| "InnateElectricityDamage"
|
|
||||||
| "InnateFreezeDamage"
|
|
||||||
| "InnateHeatDamage"
|
|
||||||
| "InnateImpactDamage"
|
|
||||||
| "InnateMagDamage"
|
|
||||||
| "InnateRadDamage"
|
|
||||||
| "InnateToxinDamage" => {
|
|
||||||
return ExportWarframes[KillingSuit].nemesisUpgradeTag!;
|
return ExportWarframes[KillingSuit].nemesisUpgradeTag!;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: For -1399275245665749231n, the value should be 75306944, but we're off by 59 with 75307003.
|
const petHeads = [
|
||||||
export const getInnateDamageValue = (fp: bigint): number => {
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA",
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB",
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC"
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export interface INemesisProfile {
|
||||||
|
innateDamageTag: TInnateDamageTag;
|
||||||
|
innateDamageValue: number;
|
||||||
|
ephemera?: string;
|
||||||
|
petHead?: (typeof petHeads)[number];
|
||||||
|
petBody?: string;
|
||||||
|
petLegs?: string;
|
||||||
|
petTail?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const generateNemesisProfile = (
|
||||||
|
fp: bigint = generateRewardSeed(),
|
||||||
|
manifest: INemesisManifest = new LawyerManifest(),
|
||||||
|
killingSuit: string = "/Lotus/Powersuits/Ember/Ember"
|
||||||
|
): INemesisProfile => {
|
||||||
const rng = new SRng(fp);
|
const rng = new SRng(fp);
|
||||||
rng.randomFloat(); // used for the weapon index
|
rng.randomFloat(); // used for the weapon index
|
||||||
const WeaponUpgradeValueAttenuationExponent = 2.25;
|
const WeaponUpgradeValueAttenuationExponent = 2.25;
|
||||||
@ -292,7 +447,33 @@ export const getInnateDamageValue = (fp: bigint): number => {
|
|||||||
if (value >= 0.941428) {
|
if (value >= 0.941428) {
|
||||||
value = 1;
|
value = 1;
|
||||||
}
|
}
|
||||||
return Math.trunc(value * 0x40000000);
|
const profile: INemesisProfile = {
|
||||||
|
innateDamageTag: getInnateDamageTag(killingSuit),
|
||||||
|
innateDamageValue: Math.trunc(value * 0x40000000) // TODO: For -1399275245665749231n, the value should be 75306944, but we're off by 59 with 75307003.
|
||||||
|
};
|
||||||
|
if (rng.randomFloat() <= manifest.ephemeraChance && manifest.ephemeraTypes) {
|
||||||
|
profile.ephemera = manifest.ephemeraTypes[profile.innateDamageTag];
|
||||||
|
}
|
||||||
|
rng.randomFloat(); // something related to sentinel agent maybe
|
||||||
|
if (manifest instanceof LawyerManifest) {
|
||||||
|
profile.petHead = rng.randomElement(petHeads)!;
|
||||||
|
profile.petBody = rng.randomElement([
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyA",
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyB",
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyC"
|
||||||
|
])!;
|
||||||
|
profile.petLegs = rng.randomElement([
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartLegsA",
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartLegsB",
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartLegsC"
|
||||||
|
])!;
|
||||||
|
profile.petTail = rng.randomElement([
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartTailA",
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartTailB",
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartTailC"
|
||||||
|
])!;
|
||||||
|
}
|
||||||
|
return profile;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getKillTokenRewardCount = (fp: bigint): number => {
|
export const getKillTokenRewardCount = (fp: bigint): number => {
|
||||||
@ -351,52 +532,3 @@ export const getInfestedLichItemRewards = (fp: bigint): string[] => {
|
|||||||
const rotBReward = getRewardAtPercentage(infestedLichRotB, rng.randomFloat())!.type;
|
const rotBReward = getRewardAtPercentage(infestedLichRotB, rng.randomFloat())!.type;
|
||||||
return [rotAReward, rotBReward];
|
return [rotAReward, rotBReward];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sendCodaFinishedMessage = async (
|
|
||||||
inventory: TInventoryDatabaseDocument,
|
|
||||||
fp: bigint = generateRewardSeed(),
|
|
||||||
name: string = "ZEKE_BEATWOMAN_TM.1999",
|
|
||||||
killed: boolean = true
|
|
||||||
): Promise<void> => {
|
|
||||||
const att: string[] = [];
|
|
||||||
|
|
||||||
// First vanquish/convert gives a sigil
|
|
||||||
const sigil = killed
|
|
||||||
? "/Lotus/Upgrades/Skins/Sigils/InfLichVanquishedSigil"
|
|
||||||
: "/Lotus/Upgrades/Skins/Sigils/InfLichConvertedSigil";
|
|
||||||
if (!inventory.WeaponSkins.find(x => x.ItemType == sigil)) {
|
|
||||||
att.push(toStoreItem(sigil));
|
|
||||||
}
|
|
||||||
|
|
||||||
const [rotAReward, rotBReward] = getInfestedLichItemRewards(fp);
|
|
||||||
att.push(fromStoreItem(rotAReward));
|
|
||||||
att.push(fromStoreItem(rotBReward));
|
|
||||||
|
|
||||||
let countedAtt: ITypeCount[] | undefined;
|
|
||||||
if (killed) {
|
|
||||||
countedAtt = [
|
|
||||||
{
|
|
||||||
ItemType: "/Lotus/Types/Items/MiscItems/CodaWeaponBucks",
|
|
||||||
ItemCount: getKillTokenRewardCount(fp)
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
await createMessage(inventory.accountOwnerId, [
|
|
||||||
{
|
|
||||||
sndr: "/Lotus/Language/Bosses/Ordis",
|
|
||||||
msg: "/Lotus/Language/Inbox/VanquishBandMsgBody",
|
|
||||||
arg: [
|
|
||||||
{
|
|
||||||
Key: "LICH_NAME",
|
|
||||||
Tag: name
|
|
||||||
}
|
|
||||||
],
|
|
||||||
att: att,
|
|
||||||
countedAtt: countedAtt,
|
|
||||||
sub: "/Lotus/Language/Inbox/VanquishBandMsgTitle",
|
|
||||||
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
|
|
||||||
highPriority: true
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
export const rootDir = path.join(__dirname, "../..");
|
export const rootDir = path.join(__dirname, "../..");
|
||||||
export const repoDir = path.basename(rootDir) == "build" ? path.join(rootDir, "..") : rootDir;
|
export const repoDir = path.basename(rootDir) != "build" ? rootDir : path.join(rootDir, "..");
|
||||||
|
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