forked from OpenWF/SpaceNinjaServer
Compare commits
333 Commits
Author | SHA1 | Date | |
---|---|---|---|
9cc0c76ef5 | |||
2a4488d1dd | |||
2e1326cde8 | |||
70be467cbf | |||
fac3ec01c6 | |||
ebdca760e6 | |||
51c0ddda38 | |||
9129bdb5fc | |||
a4922d4c35 | |||
679752633a | |||
67b5890f39 | |||
5d54e79e5d | |||
4606f28a58 | |||
a2d383ee3c | |||
834b7a8196 | |||
4a2d863c9c | |||
9f0cd91105 | |||
ebfef52fb1 | |||
dd7bacd22e | |||
c00967931e | |||
b15a635e11 | |||
7e618539fa | |||
a29398fae6 | |||
601091f1c0 | |||
f561884f2c | |||
6e1cb0c9f9 | |||
9286627668 | |||
f94f2005d3 | |||
9901b7af54 | |||
2fa846f465 | |||
541ec3d702 | |||
0a28eab65d | |||
8e639a16bd | |||
522924a823 | |||
48e3f324e2 | |||
8f77c722cb | |||
e7287933b5 | |||
b21bca7a6d | |||
d30d450311 | |||
b62e326920 | |||
8b4bc114f6 | |||
564aa06762 | |||
2e84f71af8 | |||
ddfa98e0b2 | |||
bb3c3e01b0 | |||
695dcf98e0 | |||
509f7f0d9b | |||
aada031a80 | |||
a2a441ecb0 | |||
c0a0463a68 | |||
2307a40833 | |||
304af514e2 | |||
ddf3cd49b5 | |||
41e3f0136f | |||
c0ca9d9398 | |||
0f6b55beed | |||
f8550e9afe | |||
b53c4d9125 | |||
922b65cfab | |||
2f642df20a | |||
62314e89c7 | |||
56aa3e3331 | |||
c3f486488f | |||
49c353d895 | |||
90ab560620 | |||
b0e80fcfa8 | |||
2c62fb3c3c | |||
5b215733aa | |||
39866b9a2b | |||
fad1ee9314 | |||
64b43fcccf | |||
e407262cf8 | |||
00e57c43df | |||
2ab9f39507 | |||
b60723ef54 | |||
b3bf291d10 | |||
db86e2d265 | |||
f6cb8414c1 | |||
ba3df4bdbc | |||
8feb3a5b3c | |||
66f3d65d77 | |||
b18f06087b | |||
987b5b98ff | |||
fbbd9076cf | |||
838818543c | |||
a16e2716f1 | |||
f4c7ce582b | |||
c0187f9446 | |||
f796f9a851 | |||
e18b8e09ea | |||
0d8044b87c | |||
a109ea6c5d | |||
7eb95c995c | |||
dc8f32d4d8 | |||
ba70ba88dd | |||
08d4a03c50 | |||
45feff682b | |||
65be1083ce | |||
07e7c9e897 | |||
dcb26471c9 | |||
5a75d88385 | |||
a35572e306 | |||
c46c43f143 | |||
98ed2b5ee4 | |||
7aa1b12306 | |||
b410f6b554 | |||
1dffcf979f | |||
c86bba017b | |||
2c499cec3d | |||
d6145561fd | |||
1545cdb8ce | |||
80b5e2df7f | |||
76e61129bf | |||
ea3e299861 | |||
3d8c1d036a | |||
773f96ebbc | |||
2a80307c26 | |||
a40ff27fea | |||
c9a4359714 | |||
280ed8bef1 | |||
9c89e907b1 | |||
b54fd96098 | |||
c7c7fd4ea0 | |||
a75e6d6b95 | |||
5089f67146 | |||
0416221d15 | |||
26729ce21a | |||
ee4adc7d55 | |||
29aadf4e78 | |||
0b32bc21be | |||
e09e5ebec2 | |||
b2de8608c6 | |||
2b23db1433 | |||
a45bacc388 | |||
46d37d3688 | |||
41686aea88 | |||
61ac2f8b72 | |||
d2ab894c01 | |||
8c85cdcd1d | |||
aa6191f033 | |||
e26d2635fb | |||
dd6ae8898f | |||
499ca23ffb | |||
d3102acb7c | |||
363028c9ce | |||
1d60745f18 | |||
a9b3b16d31 | |||
fd1d72a1cf | |||
75832afdbe | |||
aa916d2820 | |||
5a5f6106a3 | |||
24d9dc27e2 | |||
5e05a15743 | |||
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 |
36
.eslintrc
36
.eslintrc
@ -1,36 +1,46 @@
|
||||
{
|
||||
"plugins": ["@typescript-eslint", "prettier", "import"],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:@typescript-eslint/recommended-requiring-type-checking"
|
||||
"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
||||
"plugin:import/recommended",
|
||||
"plugin:import/typescript"
|
||||
],
|
||||
"plugins": ["@typescript-eslint", "prettier"],
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"rules": {
|
||||
"@typescript-eslint/explicit-function-return-type": "warn",
|
||||
"@typescript-eslint/restrict-template-expressions": "warn",
|
||||
"@typescript-eslint/restrict-plus-operands": "warn",
|
||||
"@typescript-eslint/no-unsafe-member-access": "warn",
|
||||
"@typescript-eslint/explicit-function-return-type": "error",
|
||||
"@typescript-eslint/restrict-template-expressions": "error",
|
||||
"@typescript-eslint/restrict-plus-operands": "error",
|
||||
"@typescript-eslint/no-unsafe-member-access": "error",
|
||||
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "caughtErrors": "none" }],
|
||||
"@typescript-eslint/no-unsafe-argument": "error",
|
||||
"@typescript-eslint/no-unsafe-call": "warn",
|
||||
"@typescript-eslint/no-unsafe-assignment": "warn",
|
||||
"@typescript-eslint/no-explicit-any": "warn",
|
||||
"no-loss-of-precision": "warn",
|
||||
"@typescript-eslint/no-unnecessary-condition": "warn",
|
||||
"@typescript-eslint/no-unsafe-call": "error",
|
||||
"@typescript-eslint/no-unsafe-assignment": "error",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"no-loss-of-precision": "error",
|
||||
"@typescript-eslint/no-unnecessary-condition": "error",
|
||||
"@typescript-eslint/no-base-to-string": "off",
|
||||
"no-case-declarations": "error",
|
||||
"prettier/prettier": "error",
|
||||
"no-mixed-spaces-and-tabs": "error",
|
||||
"require-await": "off",
|
||||
"@typescript-eslint/require-await": "error"
|
||||
"@typescript-eslint/require-await": "error",
|
||||
"import/no-named-as-default-member": "off",
|
||||
"import/no-cycle": "warn"
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"settings": {
|
||||
"import/extensions": [ ".ts" ],
|
||||
"import/resolver": {
|
||||
"typescript": true,
|
||||
"node": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@ -1,6 +1,7 @@
|
||||
name: Build
|
||||
on:
|
||||
push: {}
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request: {}
|
||||
jobs:
|
||||
build:
|
||||
@ -13,11 +14,12 @@ jobs:
|
||||
with:
|
||||
node-version: ">=20.6.0"
|
||||
- run: npm ci
|
||||
- run: cp config.json.example config.json
|
||||
- run: cp config-vanilla.json config.json
|
||||
- run: npm run verify
|
||||
- run: npm run lint:ci
|
||||
- run: npm run prettier
|
||||
- run: npm run update-translations
|
||||
- run: npm run fix-imports
|
||||
- name: Fail if there are uncommitted changes
|
||||
run: |
|
||||
if [[ -n "$(git status --porcelain)" ]]; then
|
||||
|
25
.github/workflows/docker.yml
vendored
25
.github/workflows/docker.yml
vendored
@ -4,9 +4,9 @@ on:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
docker:
|
||||
docker-amd64:
|
||||
if: github.repository == 'OpenWF/SpaceNinjaServer'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: amd64
|
||||
steps:
|
||||
- name: Set up Docker buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
@ -18,8 +18,27 @@ jobs:
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: |
|
||||
openwf/spaceninjaserver:latest
|
||||
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
|
||||
|
@ -2,3 +2,4 @@ src/routes/api.ts
|
||||
static/webui/libs/
|
||||
*.html
|
||||
*.md
|
||||
config-vanilla.json
|
||||
|
3
.vscode/launch.json
vendored
3
.vscode/launch.json
vendored
@ -8,8 +8,7 @@
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Debug and Watch",
|
||||
"runtimeArgs": ["-r", "tsconfig-paths/register", "-r", "ts-node/register", "--watch-path", "src"],
|
||||
"args": ["${workspaceFolder}/src/index.ts"],
|
||||
"args": ["${workspaceFolder}/scripts/dev.js"],
|
||||
"console": "integratedTerminal"
|
||||
}
|
||||
]
|
||||
|
17
AGENTS.md
Normal file
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.
|
53
Dockerfile
53
Dockerfile
@ -1,53 +1,12 @@
|
||||
FROM node:18-alpine3.19
|
||||
FROM node:24-alpine3.21
|
||||
|
||||
ENV APP_MONGODB_URL=mongodb://mongodb:27017/openWF
|
||||
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_CLAIMING_BLUEPRINT_REFUNDS_INGREDIENTS=false
|
||||
ENV APP_DONT_SUBTRACT_VOIDTRACES=false
|
||||
ENV APP_DONT_SUBTRACT_CONSUMABLES=false
|
||||
ENV APP_UNLOCK_ALL_SHIP_FEATURES=false
|
||||
ENV APP_UNLOCK_ALL_SHIP_DECORATIONS=false
|
||||
ENV APP_UNLOCK_ALL_FLAVOUR_ITEMS=false
|
||||
ENV APP_UNLOCK_ALL_SKINS=false
|
||||
ENV APP_UNLOCK_ALL_CAPTURA_SCENES=false
|
||||
ENV APP_UNIVERSAL_POLARITY_EVERYWHERE=false
|
||||
ENV APP_UNLOCK_DOUBLE_CAPACITY_POTATOES_EVERYWHERE=false
|
||||
ENV APP_UNLOCK_EXILUS_EVERYWHERE=false
|
||||
ENV APP_UNLOCK_ARCANES_EVERYWHERE=false
|
||||
ENV APP_NO_DAILY_FOCUS_LIMIT=false
|
||||
ENV APP_NO_ARGON_CRYSTAL_DECAY=false
|
||||
ENV APP_NO_MASTERY_RANK_UP_COOLDOWN=false
|
||||
ENV APP_NO_VENDOR_PURCHASE_LIMITS=true
|
||||
ENV APP_NO_DEATH_MARKS=false
|
||||
ENV APP_NO_KIM_COOLDOWNS=false
|
||||
ENV APP_SYNDICATE_MISSIONS_REPEATABLE=false
|
||||
ENV APP_INSTANT_FINISH_RIVEN_CHALLENGE=false
|
||||
ENV APP_INSTANT_RESOURCE_EXTRACTOR_DRONES=false
|
||||
ENV APP_NO_RESOURCE_EXTRACTOR_DRONES_DAMAGE=false
|
||||
ENV APP_SKIP_CLAN_KEY_CRAFTING=false
|
||||
ENV APP_NO_DOJO_ROOM_BUILD_STAGE=false
|
||||
ENV APP_NO_DECO_BUILD_STAGE=false
|
||||
ENV APP_FAST_DOJO_ROOM_DESTRUCTION=false
|
||||
ENV APP_NO_DOJO_RESEARCH_COSTS=false
|
||||
ENV APP_NO_DOJO_RESEARCH_TIME=false
|
||||
ENV APP_FAST_CLAN_ASCENSION=false
|
||||
ENV APP_SPOOF_MASTERY_RANK=-1
|
||||
|
||||
RUN apk add --no-cache bash sed wget jq
|
||||
RUN apk add --no-cache bash jq
|
||||
|
||||
COPY . /app
|
||||
WORKDIR /app
|
||||
|
||||
RUN npm i --omit=dev
|
||||
RUN npm run build
|
||||
RUN date '+%d %B %Y' > BUILD_DATE
|
||||
|
||||
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
||||
|
25
README.md
25
README.md
@ -10,8 +10,29 @@ To get an idea of what functionality you can expect to be missing [have a look t
|
||||
|
||||
## config.json
|
||||
|
||||
SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [config.json.example](config.json.example), which has most cheats disabled.
|
||||
SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [config-vanilla.json](config-vanilla.json), which has most cheats disabled.
|
||||
|
||||
- `logger.level` can be `fatal`, `error`, `warn`, `info`, `http`, `debug`, or `trace`.
|
||||
- `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`.
|
||||
- `worldState.lockTime` will lock the time provided in worldState if nonzero, e.g. `1743202800` for night in POE.
|
||||
- `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...
|
||||
git fetch --prune
|
||||
git stash
|
||||
git reset --hard origin/main
|
||||
git checkout -f origin/main
|
||||
|
||||
if exist static\data\0\ (
|
||||
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,13 +13,16 @@
|
||||
"skipTutorial": false,
|
||||
"skipAllDialogue": false,
|
||||
"unlockAllScans": false,
|
||||
"unlockAllMissions": false,
|
||||
"infiniteCredits": false,
|
||||
"infinitePlatinum": false,
|
||||
"infiniteEndo": false,
|
||||
"infiniteRegalAya": false,
|
||||
"infiniteHelminthMaterials": false,
|
||||
"claimingBlueprintRefundsIngredients": false,
|
||||
"dontSubtractPurchaseCreditCost": false,
|
||||
"dontSubtractPurchasePlatinumCost": false,
|
||||
"dontSubtractPurchaseItemCost": false,
|
||||
"dontSubtractPurchaseStandingCost": false,
|
||||
"dontSubtractVoidTraces": false,
|
||||
"dontSubtractConsumables": false,
|
||||
"unlockAllShipFeatures": false,
|
||||
@ -35,10 +38,14 @@
|
||||
"noDailyFocusLimit": false,
|
||||
"noArgonCrystalDecay": false,
|
||||
"noMasteryRankUpCooldown": false,
|
||||
"noVendorPurchaseLimits": true,
|
||||
"noVendorPurchaseLimits": false,
|
||||
"noDeathMarks": false,
|
||||
"noKimCooldowns": false,
|
||||
"fullyStockedVendors": false,
|
||||
"baroAlwaysAvailable": false,
|
||||
"baroFullyStocked": false,
|
||||
"syndicateMissionsRepeatable": false,
|
||||
"unlockAllProfitTakerStages": false,
|
||||
"instantFinishRivenChallenge": false,
|
||||
"instantResourceExtractorDrones": false,
|
||||
"noResourceExtractorDronesDamage": false,
|
||||
@ -49,12 +56,36 @@
|
||||
"noDojoResearchCosts": false,
|
||||
"noDojoResearchTime": false,
|
||||
"fastClanAscension": false,
|
||||
"missionsCanGiveAllRelics": false,
|
||||
"unlockAllSimarisResearchEntries": false,
|
||||
"disableDailyTribute": false,
|
||||
"spoofMasteryRank": -1,
|
||||
"relicRewardItemCountMultiplier": 1,
|
||||
"nightwaveStandingMultiplier": 1,
|
||||
"unfaithfulBugFixes": {
|
||||
"ignore1999LastRegionPlayed": false,
|
||||
"fixXtraCheeseTimer": false
|
||||
},
|
||||
"worldState": {
|
||||
"creditBoost": false,
|
||||
"affinityBoost": false,
|
||||
"resourceBoost": false,
|
||||
"starDays": true,
|
||||
"lockTime": 0
|
||||
"tennoLiveRelay": false,
|
||||
"galleonOfGhouls": 0,
|
||||
"ghoulEmergenceOverride": null,
|
||||
"plagueStarOverride": null,
|
||||
"starDaysOverride": null,
|
||||
"eidolonOverride": "",
|
||||
"vallisOverride": "",
|
||||
"duviriOverride": "",
|
||||
"nightwaveOverride": "",
|
||||
"allTheFissures": "",
|
||||
"circuitGameModes": null,
|
||||
"darvoStockMultiplier": 1,
|
||||
"varziaOverride": "",
|
||||
"varziaFullyStocked": false
|
||||
},
|
||||
"dev": {
|
||||
"keepVendorsExpired": false
|
||||
}
|
||||
}
|
@ -1,62 +1,20 @@
|
||||
services:
|
||||
spaceninjaserver:
|
||||
# build: .
|
||||
# The image to use. If you have an ARM CPU, replace 'latest' with 'latest-arm64'.
|
||||
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_CLAIMING_BLUEPRINT_REFUNDS_INGREDIENTS: false
|
||||
# APP_DONT_SUBTRACT_VOIDTRACES: false
|
||||
# APP_DONT_SUBTRACT_CONSUMABLES: false
|
||||
# APP_UNLOCK_ALL_SHIP_FEATURES: false
|
||||
# APP_UNLOCK_ALL_SHIP_DECORATIONS: false
|
||||
# APP_UNLOCK_ALL_FLAVOUR_ITEMS: false
|
||||
# APP_UNLOCK_ALL_SKINS: false
|
||||
# APP_UNLOCK_ALL_CAPTURA_SCENES: false
|
||||
# APP_UNIVERSAL_POLARITY_EVERYWHERE: false
|
||||
# APP_UNLOCK_DOUBLE_CAPACITY_POTATOES_EVERYWHERE: false
|
||||
# APP_UNLOCK_EXILUS_EVERYWHERE: false
|
||||
# APP_UNLOCK_ARCANES_EVERYWHERE: false
|
||||
# APP_NO_DAILY_FOCUS_LIMIT: false
|
||||
# APP_NO_ARGON_CRYSTAL_DECAY: false
|
||||
# APP_NO_MASTERY_RANK_UP_COOLDOWN: false
|
||||
# APP_NO_VENDOR_PURCHASE_LIMITS: true
|
||||
# APP_NO_DEATH_MARKS: false
|
||||
# APP_NO_KIM_COOLDOWNS: false
|
||||
# APP_SYNDICATE_MISSIONS_REPEATABLE: false
|
||||
# APP_INSTANT_FINISH_RIVEN_CHALLENGE: false
|
||||
# APP_INSTANT_RESOURCE_EXTRACTOR_DRONES: false
|
||||
# APP_NO_RESOURCE_EXTRACTOR_DRONES_DAMAGE: false
|
||||
# APP_SKIP_CLAN_KEY_CRAFTING: false
|
||||
# APP_NO_DOJO_ROOM_BUILD_STAGE: false
|
||||
# APP_NO_DECO_BUILD_STAGE: false
|
||||
# APP_FAST_DOJO_ROOM_DESTRUCTION: false
|
||||
# APP_NO_DOJO_RESEARCH_COSTS: false
|
||||
# APP_NO_DOJO_RESEARCH_TIME: false
|
||||
# APP_FAST_CLAN_ASCENSION: false
|
||||
# APP_SPOOF_MASTERY_RANK: -1
|
||||
volumes:
|
||||
- ./docker-data/static:/app/static/data
|
||||
- ./docker-data/conf:/app/conf
|
||||
- ./docker-data/static-data:/app/static/data
|
||||
- ./docker-data/logs:/app/logs
|
||||
ports:
|
||||
- 80:80
|
||||
- 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:
|
||||
- mongodb
|
||||
mongodb:
|
||||
@ -66,3 +24,4 @@ services:
|
||||
MONGO_INITDB_ROOT_PASSWORD: spaceninjaserver
|
||||
volumes:
|
||||
- ./docker-data/database:/data/db
|
||||
command: mongod --quiet --logpath /dev/null
|
||||
|
@ -1,24 +1,8 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Set up the configuration file using environment variables.
|
||||
echo '{
|
||||
"logger": {
|
||||
"files": true,
|
||||
"level": "trace",
|
||||
"__valid_levels": "fatal, error, warn, info, http, debug, trace"
|
||||
}
|
||||
}
|
||||
' > config.json
|
||||
if [ ! -f conf/config.json ]; then
|
||||
jq --arg value "mongodb://openwfagent:spaceninjaserver@mongodb:27017/" '.mongodbUrl = $value' /app/config-vanilla.json > /app/conf/config.json
|
||||
fi
|
||||
|
||||
for config in $(env | grep "APP_")
|
||||
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
|
||||
exec npm run start -- --configPath conf/config.json
|
||||
|
3005
package-lock.json
generated
3005
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
32
package.json
32
package.json
@ -5,19 +5,33 @@
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"start": "node --enable-source-maps --import ./build/src/pathman.js build/src/index.js",
|
||||
"dev": "ts-node-dev --openssl-legacy-provider -r tsconfig-paths/register src/index.ts ",
|
||||
"build": "tsc --incremental --sourceMap && ncp static/webui build/static/webui",
|
||||
"build": "tsgo --sourceMap && ncp static/webui build/static/webui",
|
||||
"build:tsc": "tsc --incremental --sourceMap && ncp static/webui build/static/webui",
|
||||
"build:dev": "tsgo --sourceMap",
|
||||
"build:dev:tsc": "tsc --incremental --sourceMap",
|
||||
"build-and-start": "npm run build && npm run start",
|
||||
"build-and-start:bun": "npm run verify && npm run bun-run",
|
||||
"dev": "node scripts/dev.js",
|
||||
"dev:bun": "bun scripts/dev.js",
|
||||
"verify": "tsgo --noEmit",
|
||||
"verify:tsc": "tsc --noEmit",
|
||||
"bun-run": "bun src/index.ts",
|
||||
"lint": "eslint --ext .ts .",
|
||||
"lint:ci": "eslint --ext .ts --rule \"prettier/prettier: off\" .",
|
||||
"lint:fix": "eslint --fix --ext .ts .",
|
||||
"prettier": "prettier --write .",
|
||||
"update-translations": "cd scripts && node update-translations.js"
|
||||
"update-translations": "cd scripts && node update-translations.js",
|
||||
"fix-imports": "cd scripts && node fix-imports.js",
|
||||
"fix": "npm run update-translations && npm run fix-imports && npm run prettier"
|
||||
},
|
||||
"license": "GNU",
|
||||
"dependencies": {
|
||||
"@types/express": "^5",
|
||||
"@types/morgan": "^1.9.9",
|
||||
"@types/websocket": "^1.0.10",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@typescript/native-preview": "^7.0.0-dev.20250625.1",
|
||||
"chokidar": "^4.0.3",
|
||||
"crc-32": "^1.2.2",
|
||||
"express": "^5",
|
||||
"json-with-bigint": "^3.4.4",
|
||||
@ -25,19 +39,21 @@
|
||||
"morgan": "^1.10.0",
|
||||
"ncp": "^2.0.0",
|
||||
"typescript": "^5.5",
|
||||
"warframe-public-export-plus": "^0.5.64",
|
||||
"undici": "^7.10.0",
|
||||
"warframe-public-export-plus": "^0.5.80",
|
||||
"warframe-riven-info": "^0.1.2",
|
||||
"winston": "^3.17.0",
|
||||
"winston-daily-rotate-file": "^5.0.0"
|
||||
"winston-daily-rotate-file": "^5.0.0",
|
||||
"ws": "^8.18.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^8.28.0",
|
||||
"@typescript-eslint/parser": "^8.28.0",
|
||||
"@typescript/native-preview": "^7.0.0-dev.20250523.1",
|
||||
"eslint": "^8",
|
||||
"eslint-import-resolver-typescript": "^4.4.4",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-prettier": "^5.2.5",
|
||||
"prettier": "^3.5.3",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"tsconfig-paths": "^4.2.0"
|
||||
"tree-kill": "^1.2.2"
|
||||
}
|
||||
}
|
||||
|
58
scripts/dev.js
Normal file
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) {}
|
||||
});
|
46
scripts/fix-imports.js
Normal file
46
scripts/fix-imports.js
Normal file
@ -0,0 +1,46 @@
|
||||
/* eslint-disable */
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const root = path.join(process.cwd(), "..");
|
||||
|
||||
function listFiles(dir) {
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
let results = [];
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
results = results.concat(listFiles(fullPath));
|
||||
} else {
|
||||
results.push(fullPath);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
const files = listFiles(path.join(root, "src"));
|
||||
|
||||
for (const file of files) {
|
||||
let content;
|
||||
try {
|
||||
content = fs.readFileSync(file, "utf8");
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
const dir = path.dirname(file);
|
||||
const fixedContent = content.replaceAll(/} from "([^"]+)";/g, (sub, importPath) => {
|
||||
if (!importPath.startsWith("@/")) {
|
||||
const fullImportPath = path.resolve(dir, importPath);
|
||||
if (fs.existsSync(fullImportPath + ".ts")) {
|
||||
const relative = path.relative(root, fullImportPath).replace(/\\/g, "/");
|
||||
const fixedPath = "@/" + relative;
|
||||
console.log(`${importPath} -> ${fixedPath}`);
|
||||
return sub.split(importPath).join(fixedPath);
|
||||
}
|
||||
}
|
||||
return sub;
|
||||
});
|
||||
if (content != fixedContent) {
|
||||
fs.writeFileSync(file, fixedContent, "utf8");
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
// Based on https://onlyg.it/OpenWF/Translations/src/branch/main/update.php
|
||||
// Converted via ChatGPT-4o
|
||||
|
||||
/* eslint-disable */
|
||||
const fs = require("fs");
|
||||
|
||||
function extractStrings(content) {
|
||||
@ -30,7 +31,7 @@ fs.readdirSync("../static/webui/translations").forEach(file => {
|
||||
const strings = extractStrings(line);
|
||||
if (Object.keys(strings).length > 0) {
|
||||
Object.entries(strings).forEach(([key, value]) => {
|
||||
if (targetStrings.hasOwnProperty(key)) {
|
||||
if (targetStrings.hasOwnProperty(key) && !targetStrings[key].startsWith("[UNTRANSLATED] ")) {
|
||||
fs.writeSync(fileHandle, ` ${key}: \`${targetStrings[key]}\`,\n`);
|
||||
} else {
|
||||
fs.writeSync(fileHandle, ` ${key}: \`[UNTRANSLATED] ${value}\`,\n`);
|
||||
|
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;
|
||||
}
|
22
src/controllers/api/apartmentController.ts
Normal file
22
src/controllers/api/apartmentController.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getPersonalRooms } from "@/src/services/personalRoomsService";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const apartmentController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const personalRooms = await getPersonalRooms(accountId, "Apartment");
|
||||
const response: IApartmentResponse = {};
|
||||
if (req.query.backdrop !== undefined) {
|
||||
response.NewBackdropItem = personalRooms.Apartment.VideoWallBackdrop = req.query.backdrop as string;
|
||||
}
|
||||
if (req.query.soundscape !== undefined) {
|
||||
response.NewSoundscapeItem = personalRooms.Apartment.Soundscape = req.query.soundscape as string;
|
||||
}
|
||||
await personalRooms.save();
|
||||
res.json(response);
|
||||
};
|
||||
|
||||
interface IApartmentResponse {
|
||||
NewBackdropItem?: string;
|
||||
NewSoundscapeItem?: string;
|
||||
}
|
@ -24,7 +24,6 @@ export const artifactsController: RequestHandler = async (req, res) => {
|
||||
|
||||
if (itemIndex !== -1) {
|
||||
Upgrades[itemIndex].UpgradeFingerprint = stringifiedUpgradeFingerprint;
|
||||
inventory.markModified(`Upgrades.${itemIndex}.UpgradeFingerprint`);
|
||||
} else {
|
||||
itemIndex =
|
||||
Upgrades.push({
|
||||
|
@ -1,16 +1,12 @@
|
||||
import { getAccountForRequest } from "@/src/services/loginService";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
const checkDailyMissionBonusController: RequestHandler = (_req, res) => {
|
||||
const data = Buffer.from([
|
||||
0x44, 0x61, 0x69, 0x6c, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x6f, 0x6e, 0x75, 0x73, 0x3a,
|
||||
0x31, 0x2d, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x50, 0x56, 0x50, 0x57, 0x69, 0x6e, 0x42, 0x6f, 0x6e, 0x75, 0x73,
|
||||
0x3a, 0x31, 0x0a
|
||||
]);
|
||||
res.writeHead(200, {
|
||||
"Content-Type": "text/html",
|
||||
"Content-Length": data.length
|
||||
});
|
||||
res.end(data);
|
||||
export const checkDailyMissionBonusController: RequestHandler = async (req, res) => {
|
||||
const account = await getAccountForRequest(req);
|
||||
const today = Math.trunc(Date.now() / 86400000) * 86400;
|
||||
if (account.DailyFirstWinDate != today) {
|
||||
res.send("DailyMissionBonus:1-DailyPVPWinBonus:1\n");
|
||||
} else {
|
||||
res.send("DailyMissionBonus:0-DailyPVPWinBonus:1\n");
|
||||
}
|
||||
};
|
||||
|
||||
export { checkDailyMissionBonusController };
|
||||
|
@ -13,15 +13,18 @@ import {
|
||||
addItem,
|
||||
addRecipes,
|
||||
occupySlot,
|
||||
combineInventoryChanges
|
||||
combineInventoryChanges,
|
||||
addKubrowPetPrint,
|
||||
addPowerSuit,
|
||||
addEquipment
|
||||
} from "@/src/services/inventoryService";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { InventorySlot, IPendingRecipeDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { toOid2 } from "@/src/helpers/inventoryHelpers";
|
||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||
import { IRecipe } from "warframe-public-export-plus";
|
||||
import { config } from "@/src/services/configService";
|
||||
import { EquipmentFeatures, IEquipmentClient, Status } from "@/src/types/equipmentTypes";
|
||||
|
||||
interface IClaimCompletedRecipeRequest {
|
||||
RecipeIds: IOid[];
|
||||
@ -105,20 +108,145 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
|
||||
...updateCurrency(inventory, cost, true)
|
||||
};
|
||||
}
|
||||
if (recipe.secretIngredientAction != "SIA_UNBRAND") {
|
||||
InventoryChanges = {
|
||||
...InventoryChanges,
|
||||
...(await addItem(
|
||||
|
||||
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") {
|
||||
if (recipe.resultType == "/Lotus/Powersuits/Excalibur/ExcaliburUmbra") {
|
||||
// Quite the special case here...
|
||||
// We don't just get Umbra, but also Skiajati and Umbra Mods. Both items are max rank, potatoed, and with the mods are pre-installed.
|
||||
// Source: https://wiki.warframe.com/w/The_Sacrifice, https://wiki.warframe.com/w/Excalibur/Umbra, https://wiki.warframe.com/w/Skiajati
|
||||
|
||||
const umbraModA = (
|
||||
await addItem(
|
||||
inventory,
|
||||
"/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModA",
|
||||
1,
|
||||
false,
|
||||
undefined,
|
||||
`{"lvl":5}`
|
||||
)
|
||||
).Upgrades![0];
|
||||
const umbraModB = (
|
||||
await addItem(
|
||||
inventory,
|
||||
"/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModB",
|
||||
1,
|
||||
false,
|
||||
undefined,
|
||||
`{"lvl":5}`
|
||||
)
|
||||
).Upgrades![0];
|
||||
const umbraModC = (
|
||||
await addItem(
|
||||
inventory,
|
||||
"/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModC",
|
||||
1,
|
||||
false,
|
||||
undefined,
|
||||
`{"lvl":5}`
|
||||
)
|
||||
).Upgrades![0];
|
||||
const sacrificeModA = (
|
||||
await addItem(
|
||||
inventory,
|
||||
"/Lotus/Upgrades/Mods/Sets/Sacrifice/MeleeSacrificeModA",
|
||||
1,
|
||||
false,
|
||||
undefined,
|
||||
`{"lvl":5}`
|
||||
)
|
||||
).Upgrades![0];
|
||||
const sacrificeModB = (
|
||||
await addItem(
|
||||
inventory,
|
||||
"/Lotus/Upgrades/Mods/Sets/Sacrifice/MeleeSacrificeModB",
|
||||
1,
|
||||
false,
|
||||
undefined,
|
||||
`{"lvl":5}`
|
||||
)
|
||||
).Upgrades![0];
|
||||
InventoryChanges.Upgrades ??= [];
|
||||
InventoryChanges.Upgrades.push(umbraModA, umbraModB, umbraModC, sacrificeModA, sacrificeModB);
|
||||
|
||||
await addPowerSuit(
|
||||
inventory,
|
||||
recipe.resultType,
|
||||
recipe.num,
|
||||
false,
|
||||
undefined,
|
||||
pendingRecipe.TargetFingerprint
|
||||
))
|
||||
};
|
||||
"/Lotus/Powersuits/Excalibur/ExcaliburUmbra",
|
||||
{
|
||||
Configs: [
|
||||
{
|
||||
Upgrades: [
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
umbraModA.ItemId.$oid,
|
||||
umbraModB.ItemId.$oid,
|
||||
umbraModC.ItemId.$oid
|
||||
]
|
||||
}
|
||||
],
|
||||
XP: 900_000,
|
||||
Features: EquipmentFeatures.DOUBLE_CAPACITY
|
||||
},
|
||||
InventoryChanges
|
||||
);
|
||||
inventory.XPInfo.push({
|
||||
ItemType: "/Lotus/Powersuits/Excalibur/ExcaliburUmbra",
|
||||
XP: 900_000
|
||||
});
|
||||
|
||||
addEquipment(
|
||||
inventory,
|
||||
"Melee",
|
||||
"/Lotus/Weapons/Tenno/Melee/Swords/UmbraKatana/UmbraKatana",
|
||||
{
|
||||
Configs: [
|
||||
{ Upgrades: ["", "", "", "", "", "", sacrificeModA.ItemId.$oid, sacrificeModB.ItemId.$oid] }
|
||||
],
|
||||
XP: 450_000,
|
||||
Features: EquipmentFeatures.DOUBLE_CAPACITY
|
||||
},
|
||||
InventoryChanges
|
||||
);
|
||||
inventory.XPInfo.push({
|
||||
ItemType: "/Lotus/Weapons/Tenno/Melee/Swords/UmbraKatana/UmbraKatana",
|
||||
XP: 450_000
|
||||
});
|
||||
} else {
|
||||
InventoryChanges = {
|
||||
...InventoryChanges,
|
||||
...(await addItem(
|
||||
inventory,
|
||||
recipe.resultType,
|
||||
recipe.num,
|
||||
false,
|
||||
undefined,
|
||||
pendingRecipe.TargetFingerprint
|
||||
))
|
||||
};
|
||||
}
|
||||
}
|
||||
if (config.claimingBlueprintRefundsIngredients) {
|
||||
if (
|
||||
config.claimingBlueprintRefundsIngredients &&
|
||||
recipe.secretIngredientAction != "SIA_CREATE_KUBROW" // Can't refund the egg
|
||||
) {
|
||||
await refundRecipeIngredients(inventory, InventoryChanges, recipe, pendingRecipe);
|
||||
}
|
||||
await inventory.save();
|
||||
|
@ -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 { checkCalendarAutoAdvance, getCalendarProgress, getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||
import { getWorldState } from "@/src/services/worldStateService";
|
||||
@ -12,27 +12,23 @@ export const completeCalendarEventController: RequestHandler = async (req, res)
|
||||
const calendarProgress = getCalendarProgress(inventory);
|
||||
const currentSeason = getWorldState().KnownCalendarSeasons[0];
|
||||
let inventoryChanges: IInventoryChanges = {};
|
||||
let dayIndex = 0;
|
||||
for (const day of currentSeason.Days) {
|
||||
if (day.events.length == 0 || day.events[0].type != "CET_CHALLENGE") {
|
||||
if (dayIndex == calendarProgress.SeasonProgress.LastCompletedDayIdx) {
|
||||
if (day.events.length != 0) {
|
||||
const selection = day.events[parseInt(req.query.CompletedEventIdx as string)];
|
||||
if (selection.type == "CET_REWARD") {
|
||||
inventoryChanges = (await handleStoreItemAcquisition(selection.reward!, inventory))
|
||||
.InventoryChanges;
|
||||
} else if (selection.type == "CET_UPGRADE") {
|
||||
calendarProgress.YearProgress.Upgrades.push(selection.upgrade!);
|
||||
} else if (selection.type != "CET_PLOT") {
|
||||
throw new Error(`unexpected selection type: ${selection.type}`);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
++dayIndex;
|
||||
const dayIndex = calendarProgress.SeasonProgress.LastCompletedDayIdx + 1;
|
||||
const day = currentSeason.Days[dayIndex];
|
||||
if (day.events.length != 0) {
|
||||
if (day.events[0].type == "CET_CHALLENGE") {
|
||||
throw new Error(`completeCalendarEvent should not be used for challenges`);
|
||||
}
|
||||
const selection = day.events[parseInt(req.query.CompletedEventIdx as string)];
|
||||
if (selection.type == "CET_REWARD") {
|
||||
inventoryChanges = (await handleStoreItemAcquisition(selection.reward!, inventory)).InventoryChanges;
|
||||
} else if (selection.type == "CET_UPGRADE") {
|
||||
calendarProgress.YearProgress.Upgrades.push(selection.upgrade!);
|
||||
} else if (selection.type != "CET_PLOT") {
|
||||
throw new Error(`unexpected selection type: ${selection.type}`);
|
||||
}
|
||||
}
|
||||
calendarProgress.SeasonProgress.LastCompletedDayIdx++;
|
||||
calendarProgress.SeasonProgress.LastCompletedDayIdx = dayIndex;
|
||||
checkCalendarAutoAdvance(inventory, currentSeason);
|
||||
await inventory.save();
|
||||
res.json({
|
||||
InventoryChanges: inventoryChanges,
|
||||
|
@ -4,8 +4,7 @@ import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inven
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { createUnveiledRivenFingerprint } from "@/src/helpers/rivenHelper";
|
||||
import { ExportUpgrades } from "warframe-public-export-plus";
|
||||
import { IVeiledRivenFingerprint } from "@/src/helpers/rivenHelper";
|
||||
|
||||
export const completeRandomModChallengeController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
@ -27,10 +26,11 @@ export const completeRandomModChallengeController: RequestHandler = async (req,
|
||||
inventoryChanges.MiscItems = miscItemChanges;
|
||||
}
|
||||
|
||||
// Update riven fingerprint to a randomised unveiled state
|
||||
// Complete the riven challenge
|
||||
const upgrade = inventory.Upgrades.id(request.ItemId)!;
|
||||
const meta = ExportUpgrades[upgrade.ItemType];
|
||||
upgrade.UpgradeFingerprint = JSON.stringify(createUnveiledRivenFingerprint(meta));
|
||||
const fp = JSON.parse(upgrade.UpgradeFingerprint!) as IVeiledRivenFingerprint;
|
||||
fp.challenge.Progress = fp.challenge.Required;
|
||||
upgrade.UpgradeFingerprint = JSON.stringify(fp);
|
||||
|
||||
await inventory.save();
|
||||
|
||||
|
@ -21,7 +21,8 @@ import {
|
||||
updateCurrency
|
||||
} from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { IFusionTreasure, IMiscItem, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { ITypeCount } from "@/src/types/commonTypes";
|
||||
import { IFusionTreasure, IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const contributeToVaultController: RequestHandler = async (req, res) => {
|
||||
|
@ -4,9 +4,15 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
|
||||
export const creditsController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
|
||||
const inventory = await getInventory(accountId, "RegularCredits TradesRemaining PremiumCreditsFree PremiumCredits");
|
||||
const inventory = (
|
||||
await Promise.all([
|
||||
getAccountIdForRequest(req),
|
||||
getInventory(
|
||||
req.query.accountId as string,
|
||||
"RegularCredits TradesRemaining PremiumCreditsFree PremiumCredits"
|
||||
)
|
||||
])
|
||||
)[1];
|
||||
|
||||
const response = {
|
||||
RegularCredits: inventory.RegularCredits,
|
||||
|
106
src/controllers/api/crewShipFusionController.ts
Normal file
106
src/controllers/api/crewShipFusionController.ts
Normal file
@ -0,0 +1,106 @@
|
||||
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);
|
||||
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];
|
@ -12,7 +12,7 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { getRandomInt } from "@/src/services/rngService";
|
||||
import { IFingerprintStat } from "@/src/helpers/rivenHelper";
|
||||
import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { IEquipmentDatabase } from "@/src/types/equipmentTypes";
|
||||
|
||||
export const crewShipIdentifySalvageController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
|
@ -1,12 +1,15 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { Guild } from "@/src/models/guildModel";
|
||||
import { getAccountForRequest } from "@/src/services/loginService";
|
||||
import { hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountForRequest, getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { GuildPermission } from "@/src/types/guildTypes";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const customObstacleCourseLeaderboardController: RequestHandler = async (req, res) => {
|
||||
const data = getJSONfromString<ICustomObstacleCourseLeaderboardRequest>(String(req.body));
|
||||
const guild = (await Guild.findById(data.g, "DojoComponents"))!;
|
||||
const guild = (await Guild.findById(data.g, "DojoComponents Ranks"))!;
|
||||
const component = guild.DojoComponents.id(data.c)!;
|
||||
if (req.query.act == "f") {
|
||||
res.json({
|
||||
@ -34,6 +37,19 @@ export const customObstacleCourseLeaderboardController: RequestHandler = async (
|
||||
entry.r = ++r;
|
||||
}
|
||||
await guild.save();
|
||||
res.status(200).end();
|
||||
} else if (req.query.act == "c") {
|
||||
// TOVERIFY: What clan permission is actually needed for this?
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "GuildId LevelKeys");
|
||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Decorator))) {
|
||||
res.status(400).end();
|
||||
return;
|
||||
}
|
||||
|
||||
component.Leaderboard = undefined;
|
||||
await guild.save();
|
||||
|
||||
res.status(200).end();
|
||||
} else {
|
||||
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
||||
|
@ -3,11 +3,13 @@ import {
|
||||
getGuildForRequestEx,
|
||||
hasAccessToDojo,
|
||||
hasGuildPermission,
|
||||
refundDojoDeco,
|
||||
removeDojoDeco
|
||||
} from "@/src/services/guildService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { GuildPermission } from "@/src/types/guildTypes";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const destroyDojoDecoController: RequestHandler = async (req, res) => {
|
||||
@ -18,9 +20,20 @@ export const destroyDojoDecoController: RequestHandler = async (req, res) => {
|
||||
res.json({ DojoRequestStatus: -1 });
|
||||
return;
|
||||
}
|
||||
const request = JSON.parse(String(req.body)) as IDestroyDojoDecoRequest;
|
||||
|
||||
removeDojoDeco(guild, request.ComponentId, request.DecoId);
|
||||
const request = JSON.parse(String(req.body)) as IDestroyDojoDecoRequest | IClearObstacleCourseRequest;
|
||||
if ("DecoType" in request) {
|
||||
removeDojoDeco(guild, request.ComponentId, request.DecoId);
|
||||
} else if (request.Act == "cObst") {
|
||||
const component = guild.DojoComponents.id(request.ComponentId)!;
|
||||
if (component.Decos) {
|
||||
for (const deco of component.Decos) {
|
||||
refundDojoDeco(guild, component, deco);
|
||||
}
|
||||
component.Decos.splice(0, component.Decos.length);
|
||||
}
|
||||
} else {
|
||||
logger.error(`unhandled destroyDojoDeco request`, request);
|
||||
}
|
||||
|
||||
await guild.save();
|
||||
res.json(await getDojoClient(guild, 0, request.ComponentId));
|
||||
@ -31,3 +44,8 @@ interface IDestroyDojoDecoRequest {
|
||||
ComponentId: string;
|
||||
DecoId: string;
|
||||
}
|
||||
|
||||
interface IClearObstacleCourseRequest {
|
||||
ComponentId: string;
|
||||
Act: "cObst" | "maybesomethingelsewedontknowabout";
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getRecipe, WeaponTypeInternal } from "@/src/services/itemDataService";
|
||||
import { EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { EquipmentFeatures } from "@/src/types/equipmentTypes";
|
||||
|
||||
export const evolveWeaponController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
|
@ -30,15 +30,14 @@ export const fishmongerController: RequestHandler = async (req, res) => {
|
||||
miscItemChanges.push({ ItemType: fish.ItemType, ItemCount: fish.ItemCount * -1 });
|
||||
}
|
||||
addMiscItems(inventory, miscItemChanges);
|
||||
let affiliationMod;
|
||||
if (gainedStanding && syndicateTag) affiliationMod = addStanding(inventory, syndicateTag, gainedStanding);
|
||||
if (gainedStanding && syndicateTag) addStanding(inventory, syndicateTag, gainedStanding);
|
||||
await inventory.save();
|
||||
res.json({
|
||||
InventoryChanges: {
|
||||
MiscItems: miscItemChanges
|
||||
},
|
||||
SyndicateTag: syndicateTag,
|
||||
StandingChange: affiliationMod?.Standing || 0
|
||||
StandingChange: gainedStanding
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -4,7 +4,6 @@ import { getInventory, addMiscItems, addEquipment, occupySlot } from "@/src/serv
|
||||
import { IMiscItem, TFocusPolarity, TEquipmentKey, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { ExportFocusUpgrades } from "warframe-public-export-plus";
|
||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
||||
|
||||
export const focusController: RequestHandler = async (req, res) => {
|
||||
@ -43,7 +42,7 @@ export const focusController: RequestHandler = async (req, res) => {
|
||||
inventory.FocusAbility ??= focusType;
|
||||
inventory.FocusUpgrades.push({ ItemType: focusType });
|
||||
if (inventory.FocusXP) {
|
||||
inventory.FocusXP[focusPolarity] -= cost;
|
||||
inventory.FocusXP[focusPolarity]! -= cost;
|
||||
}
|
||||
await inventory.save();
|
||||
res.json({
|
||||
@ -78,7 +77,7 @@ export const focusController: RequestHandler = async (req, res) => {
|
||||
cost += ExportFocusUpgrades[focusType].baseFocusPointCost;
|
||||
inventory.FocusUpgrades.push({ ItemType: focusType, Level: 0 });
|
||||
}
|
||||
inventory.FocusXP![focusPolarity] -= cost;
|
||||
inventory.FocusXP![focusPolarity]! -= cost;
|
||||
await inventory.save();
|
||||
res.json({
|
||||
FocusTypes: request.FocusTypes,
|
||||
@ -96,7 +95,7 @@ export const focusController: RequestHandler = async (req, res) => {
|
||||
const focusUpgradeDb = inventory.FocusUpgrades.find(entry => entry.ItemType == focusUpgrade.ItemType)!;
|
||||
focusUpgradeDb.Level = focusUpgrade.Level;
|
||||
}
|
||||
inventory.FocusXP![focusPolarity] -= cost;
|
||||
inventory.FocusXP![focusPolarity]! -= cost;
|
||||
await inventory.save();
|
||||
res.json({
|
||||
FocusInfos: request.FocusInfos,
|
||||
@ -116,14 +115,14 @@ export const focusController: RequestHandler = async (req, res) => {
|
||||
});
|
||||
occupySlot(inventory, InventorySlot.AMPS, false);
|
||||
await inventory.save();
|
||||
res.json((inventoryChanges.OperatorAmps as IEquipmentClient[])[0]);
|
||||
res.json(inventoryChanges.OperatorAmps![0]);
|
||||
break;
|
||||
}
|
||||
case FocusOperation.UnbindUpgrade: {
|
||||
const request = JSON.parse(String(req.body)) as IUnbindUpgradeRequest;
|
||||
const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]);
|
||||
const inventory = await getInventory(accountId);
|
||||
inventory.FocusXP![focusPolarity] -= 750_000 * request.FocusTypes.length;
|
||||
inventory.FocusXP![focusPolarity]! -= 750_000 * request.FocusTypes.length;
|
||||
addMiscItems(inventory, [
|
||||
{
|
||||
ItemType: "/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantItem",
|
||||
@ -168,8 +167,10 @@ export const focusController: RequestHandler = async (req, res) => {
|
||||
shard.ItemCount *= -1;
|
||||
}
|
||||
const inventory = await getInventory(accountId);
|
||||
inventory.FocusXP ??= { AP_POWER: 0, AP_TACTIC: 0, AP_DEFENSE: 0, AP_ATTACK: 0, AP_WARD: 0 };
|
||||
inventory.FocusXP[request.Polarity] += xp;
|
||||
const polarity = request.Polarity;
|
||||
inventory.FocusXP ??= {};
|
||||
inventory.FocusXP[polarity] ??= 0;
|
||||
inventory.FocusXP[polarity] += xp;
|
||||
addMiscItems(inventory, request.Shards);
|
||||
await inventory.save();
|
||||
break;
|
||||
|
@ -2,22 +2,14 @@ import { RequestHandler } from "express";
|
||||
import { ExportResources } from "warframe-public-export-plus";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { addFusionTreasures, addMiscItems, getInventory } from "@/src/services/inventoryService";
|
||||
import { IFusionTreasure, IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { parseFusionTreasure } from "@/src/helpers/inventoryHelpers";
|
||||
|
||||
interface IFusionTreasureRequest {
|
||||
oldTreasureName: string;
|
||||
newTreasureName: string;
|
||||
}
|
||||
|
||||
const parseFusionTreasure = (name: string, count: number): IFusionTreasure => {
|
||||
const arr = name.split("_");
|
||||
return {
|
||||
ItemType: arr[0],
|
||||
Sockets: parseInt(arr[1], 16),
|
||||
ItemCount: count
|
||||
};
|
||||
};
|
||||
|
||||
export const fusionTreasuresController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId);
|
||||
|
@ -6,9 +6,8 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
|
||||
import { IMongoDate } from "@/src/types/commonTypes";
|
||||
import { IMissionReward } from "@/src/types/missionTypes";
|
||||
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
|
||||
import { IGardeningClient, IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { IGardeningClient } from "@/src/types/shipTypes";
|
||||
import { RequestHandler } from "express";
|
||||
import { dict_en, ExportResources } from "warframe-public-export-plus";
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { DailyDeal } from "@/src/models/worldStateModel";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const getDailyDealStockLevelsController: RequestHandler = (req, res) => {
|
||||
export const getDailyDealStockLevelsController: RequestHandler = async (req, res) => {
|
||||
const dailyDeal = (await DailyDeal.findOne({ StoreItem: req.query.productName }, "AmountSold"))!;
|
||||
res.json({
|
||||
StoreItem: req.query.productName,
|
||||
AmountSold: 0
|
||||
AmountSold: dailyDeal.AmountSold
|
||||
});
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
||||
import { generateRewardSeed } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { generateRewardSeed } from "@/src/services/rngService";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const getNewRewardSeedController: RequestHandler = async (req, res) => {
|
||||
|
62
src/controllers/api/getPastWeeklyChallengesController.ts
Normal file
62
src/controllers/api/getPastWeeklyChallengesController.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { EPOCH, getSeasonChallengePools, getWorldState, pushWeeklyActs } from "@/src/services/worldStateService";
|
||||
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
||||
import { ISeasonChallenge } from "@/src/types/worldStateTypes";
|
||||
import { ExportChallenges } from "warframe-public-export-plus";
|
||||
|
||||
export const getPastWeeklyChallengesController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "SeasonChallengeHistory ChallengeProgress");
|
||||
const worldState = getWorldState(undefined);
|
||||
|
||||
if (worldState.SeasonInfo) {
|
||||
const pools = getSeasonChallengePools(worldState.SeasonInfo.AffiliationTag);
|
||||
const nightwaveStartTimestamp = Number(worldState.SeasonInfo.Activation.$date.$numberLong);
|
||||
const nightwaveSeason = worldState.SeasonInfo.Season;
|
||||
const timeMs = worldState.Time * 1000;
|
||||
const completedChallengesIds = new Set<string>();
|
||||
|
||||
inventory.SeasonChallengeHistory.forEach(challengeHistory => {
|
||||
const entryNightwaveSeason = parseInt(challengeHistory.id.slice(0, 4), 10) - 1;
|
||||
if (nightwaveSeason == entryNightwaveSeason) {
|
||||
const meta = Object.entries(ExportChallenges).find(
|
||||
([key]) => key.split("/").pop() === challengeHistory.challenge
|
||||
);
|
||||
if (meta) {
|
||||
const [, challengeMeta] = meta;
|
||||
const challengeProgress = inventory.ChallengeProgress.find(
|
||||
c => c.Name === challengeHistory.challenge
|
||||
);
|
||||
|
||||
if (challengeProgress && challengeProgress.Progress >= (challengeMeta.requiredCount ?? 1)) {
|
||||
completedChallengesIds.add(challengeHistory.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const PastWeeklyChallenges: ISeasonChallenge[] = [];
|
||||
|
||||
let week = Math.trunc((timeMs - EPOCH) / unixTimesInMs.week) - 1;
|
||||
|
||||
while (EPOCH + week * unixTimesInMs.week >= nightwaveStartTimestamp && PastWeeklyChallenges.length < 3) {
|
||||
const tempActs: ISeasonChallenge[] = [];
|
||||
pushWeeklyActs(tempActs, pools, week, nightwaveStartTimestamp, nightwaveSeason);
|
||||
|
||||
for (const act of tempActs) {
|
||||
if (!completedChallengesIds.has(act._id.$oid) && PastWeeklyChallenges.length < 3) {
|
||||
if (act.Challenge.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanent")) {
|
||||
act.Permanent = true;
|
||||
}
|
||||
PastWeeklyChallenges.push(act);
|
||||
}
|
||||
}
|
||||
|
||||
week--;
|
||||
}
|
||||
|
||||
res.json({ PastWeeklyChallenges: PastWeeklyChallenges });
|
||||
}
|
||||
};
|
@ -3,10 +3,9 @@ import { config } from "@/src/services/configService";
|
||||
import allShipFeatures from "@/static/fixed_responses/allShipFeatures.json";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
|
||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||
import { IGetShipResponse } from "@/src/types/shipTypes";
|
||||
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
|
||||
import { IGetShipResponse, IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
|
||||
import { getLoadout } from "@/src/services/loadoutService";
|
||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||
|
||||
export const getShipController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
@ -26,15 +25,7 @@ export const getShipController: RequestHandler = async (req, res) => {
|
||||
LoadOutInventory: { LoadOutPresets: loadout.toJSON() },
|
||||
Ship: {
|
||||
...personalRooms.Ship,
|
||||
ShipId: toOid(personalRoomsDb.activeShipId),
|
||||
ShipInterior: {
|
||||
Colors: personalRooms.ShipInteriorColors,
|
||||
ShipAttachments: { HOOD_ORNAMENT: "" },
|
||||
SkinFlavourItem: ""
|
||||
},
|
||||
FavouriteLoadoutId: personalRooms.Ship.FavouriteLoadoutId
|
||||
? toOid(personalRooms.Ship.FavouriteLoadoutId)
|
||||
: undefined
|
||||
ShipId: toOid(personalRoomsDb.activeShipId)
|
||||
},
|
||||
Apartment: personalRooms.Apartment,
|
||||
TailorShop: personalRooms.TailorShop
|
||||
|
@ -2,6 +2,7 @@ import { RequestHandler } from "express";
|
||||
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 = async (req, res) => {
|
||||
let manifest = getVendorManifestByTypeName(req.query.vendor as string);
|
||||
@ -14,6 +15,14 @@ export const getVendorInfoController: RequestHandler = async (req, res) => {
|
||||
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);
|
||||
|
@ -9,15 +9,26 @@ import {
|
||||
updateCurrency
|
||||
} from "@/src/services/inventoryService";
|
||||
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||
import { handleDailyDealPurchase, handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||
import { IOid } from "@/src/types/commonTypes";
|
||||
import { IInventoryChanges, IPurchaseParams } from "@/src/types/purchaseTypes";
|
||||
import { IPurchaseParams, IPurchaseResponse, PurchaseSource } from "@/src/types/purchaseTypes";
|
||||
import { RequestHandler } from "express";
|
||||
import { ExportBundles, ExportFlavour } from "warframe-public-export-plus";
|
||||
|
||||
const checkPurchaseParams = (params: IPurchaseParams): boolean => {
|
||||
switch (params.Source) {
|
||||
case PurchaseSource.Market:
|
||||
return params.UsePremium;
|
||||
|
||||
case PurchaseSource.DailyDeal:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const giftingController: RequestHandler = async (req, res) => {
|
||||
const data = getJSONfromString<IGiftingRequest>(String(req.body));
|
||||
if (data.PurchaseParams.Source != 0 || !data.PurchaseParams.UsePremium) {
|
||||
if (!checkPurchaseParams(data.PurchaseParams)) {
|
||||
throw new Error(`unexpected purchase params in gifting request: ${String(req.body)}`);
|
||||
}
|
||||
|
||||
@ -58,16 +69,19 @@ export const giftingController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
senderInventory.GiftsRemaining -= 1;
|
||||
|
||||
const inventoryChanges: IInventoryChanges = updateCurrency(
|
||||
senderInventory,
|
||||
data.PurchaseParams.ExpectedPrice,
|
||||
true
|
||||
);
|
||||
const response: IPurchaseResponse = {
|
||||
InventoryChanges: {}
|
||||
};
|
||||
if (data.PurchaseParams.Source == PurchaseSource.DailyDeal) {
|
||||
await handleDailyDealPurchase(senderInventory, data.PurchaseParams, response);
|
||||
} else {
|
||||
updateCurrency(senderInventory, data.PurchaseParams.ExpectedPrice, true, response.InventoryChanges);
|
||||
}
|
||||
if (data.PurchaseParams.StoreItem in ExportBundles) {
|
||||
const bundle = ExportBundles[data.PurchaseParams.StoreItem];
|
||||
if (bundle.giftingBonus) {
|
||||
combineInventoryChanges(
|
||||
inventoryChanges,
|
||||
response.InventoryChanges,
|
||||
(await handleStoreItemAcquisition(bundle.giftingBonus, senderInventory)).InventoryChanges
|
||||
);
|
||||
}
|
||||
@ -99,9 +113,7 @@ export const giftingController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
]);
|
||||
|
||||
res.json({
|
||||
InventoryChanges: inventoryChanges
|
||||
});
|
||||
res.json(response);
|
||||
};
|
||||
|
||||
interface IGiftingRequest {
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { sendWsBroadcastTo } from "@/src/services/wsService";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
||||
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { ArtifactPolarity, EquipmentFeatures, IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { ArtifactPolarity } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { ExportRecipes } from "warframe-public-export-plus";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { EquipmentFeatures, IEquipmentClient } from "@/src/types/equipmentTypes";
|
||||
|
||||
interface IGildWeaponRequest {
|
||||
ItemName: string;
|
||||
@ -72,4 +74,5 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
|
||||
InventoryChanges: inventoryChanges,
|
||||
AffiliationMods: affiliationMods
|
||||
});
|
||||
sendWsBroadcastTo(accountId, { update_inventory: true });
|
||||
};
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { addLoreFragmentScans, addShipDecorations, getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { ILoreFragmentScan, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { ITypeCount } from "@/src/types/commonTypes";
|
||||
import { ILoreFragmentScan } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const giveShipDecoAndLoreFragmentController: RequestHandler = async (req, res) => {
|
||||
|
@ -5,13 +5,14 @@ import {
|
||||
getGuildVault,
|
||||
hasAccessToDojo,
|
||||
hasGuildPermission,
|
||||
processCompletedGuildTechProject,
|
||||
processFundedGuildTechProject,
|
||||
processGuildTechProjectContributionsUpdate,
|
||||
removePigmentsFromGuildMembers,
|
||||
scaleRequiredCount,
|
||||
setGuildTechLogState
|
||||
} 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 {
|
||||
addCrewShipWeaponSkin,
|
||||
@ -51,8 +52,12 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
||||
};
|
||||
if (project.CompletionDate) {
|
||||
techProject.CompletionDate = toMongoDate(project.CompletionDate);
|
||||
if (Date.now() >= project.CompletionDate.getTime()) {
|
||||
needSave ||= setGuildTechLogState(guild, project.ItemType, 4, project.CompletionDate);
|
||||
if (
|
||||
Date.now() >= project.CompletionDate.getTime() &&
|
||||
setGuildTechLogState(guild, project.ItemType, 4, project.CompletionDate)
|
||||
) {
|
||||
processCompletedGuildTechProject(guild, project.ItemType);
|
||||
needSave = true;
|
||||
}
|
||||
}
|
||||
techProjects.push(techProject);
|
||||
@ -442,6 +447,7 @@ const finishComponentRepair = (
|
||||
...(category == "CrewShipWeaponSkins"
|
||||
? addCrewShipWeaponSkin(inventory, salvageItem.ItemType, salvageItem.UpgradeFingerprint)
|
||||
: addEquipment(inventory, category, salvageItem.ItemType, {
|
||||
UpgradeType: ExportRailjackWeapons[salvageItem.ItemType].defaultUpgrades?.[0].ItemType,
|
||||
UpgradeFingerprint: salvageItem.UpgradeFingerprint
|
||||
})),
|
||||
...occupySlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS, false)
|
||||
|
@ -30,8 +30,9 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
||||
const request = getJSONfromString<IShardInstallRequest>(String(req.body));
|
||||
const inventory = await getInventory(account._id.toString());
|
||||
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] = {
|
||||
UpgradeType: request.UpgradeType,
|
||||
@ -92,7 +93,8 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
|
||||
// remove from suit
|
||||
suit.ArchonCrystalUpgrades![request.Slot] = {};
|
||||
suit.ArchonCrystalUpgrades![request.Slot].UpgradeType = undefined;
|
||||
suit.ArchonCrystalUpgrades![request.Slot].Color = undefined;
|
||||
|
||||
await inventory.save();
|
||||
|
||||
|
@ -5,30 +5,41 @@ import { config } from "@/src/services/configService";
|
||||
import allDialogue from "@/static/fixed_responses/allDialogue.json";
|
||||
import { ILoadoutDatabase } from "@/src/types/saveLoadoutTypes";
|
||||
import { IInventoryClient, IShipInventory, equipmentKeys } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { IPolarity, ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { IPolarity, ArtifactPolarity } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import {
|
||||
eFaction,
|
||||
ExportCustoms,
|
||||
ExportFlavour,
|
||||
ExportRegions,
|
||||
ExportResources,
|
||||
ExportVirtuals
|
||||
ExportVirtuals,
|
||||
ICountedItem
|
||||
} from "warframe-public-export-plus";
|
||||
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "@/src/services/infestedFoundryService";
|
||||
import {
|
||||
addEmailItem,
|
||||
addItem,
|
||||
addMiscItems,
|
||||
allDailyAffiliationKeys,
|
||||
checkCalendarAutoAdvance,
|
||||
cleanupInventory,
|
||||
createLibraryDailyTask,
|
||||
generateRewardSeed
|
||||
getCalendarProgress
|
||||
} from "@/src/services/inventoryService";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { catBreadHash } from "@/src/helpers/stringHelpers";
|
||||
import { addString, catBreadHash } from "@/src/helpers/stringHelpers";
|
||||
import { Types } from "mongoose";
|
||||
import { getNemesisManifest } from "@/src/helpers/nemesisHelpers";
|
||||
import { getPersonalRooms } from "@/src/services/personalRoomsService";
|
||||
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
|
||||
import { Ship } from "@/src/models/shipModel";
|
||||
import { toLegacyOid, version_compare } from "@/src/helpers/inventoryHelpers";
|
||||
import { toLegacyOid, toOid, version_compare } from "@/src/helpers/inventoryHelpers";
|
||||
import { Inbox } from "@/src/models/inboxModel";
|
||||
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
||||
import { DailyDeal } from "@/src/models/worldStateModel";
|
||||
import { EquipmentFeatures } from "@/src/types/equipmentTypes";
|
||||
import { generateRewardSeed } from "@/src/services/rngService";
|
||||
import { getInvasionByOid, getWorldState } from "@/src/services/worldStateService";
|
||||
import { createMessage } from "@/src/services/inboxService";
|
||||
|
||||
export const inventoryController: RequestHandler = async (request, response) => {
|
||||
const account = await getAccountForRequest(request);
|
||||
@ -42,6 +53,8 @@ export const inventoryController: RequestHandler = async (request, response) =>
|
||||
|
||||
// Handle daily reset
|
||||
if (!inventory.NextRefill || Date.now() >= inventory.NextRefill.getTime()) {
|
||||
const today = Math.trunc(Date.now() / 86400000);
|
||||
|
||||
for (const key of allDailyAffiliationKeys) {
|
||||
inventory[key] = 16000 + inventory.PlayerLevel * 500;
|
||||
}
|
||||
@ -52,12 +65,12 @@ export const inventoryController: RequestHandler = async (request, response) =>
|
||||
inventory.LibraryAvailableDailyTaskInfo = createLibraryDailyTask();
|
||||
|
||||
if (inventory.NextRefill) {
|
||||
const lastLoginDay = Math.trunc(inventory.NextRefill.getTime() / 86400000) - 1;
|
||||
const daysPassed = today - lastLoginDay;
|
||||
|
||||
if (config.noArgonCrystalDecay) {
|
||||
inventory.FoundToday = undefined;
|
||||
} else {
|
||||
const lastLoginDay = Math.trunc(inventory.NextRefill.getTime() / 86400000) - 1;
|
||||
const today = Math.trunc(Date.now() / 86400000);
|
||||
const daysPassed = today - lastLoginDay;
|
||||
for (let i = 0; i != daysPassed; ++i) {
|
||||
const numArgonCrystals =
|
||||
inventory.MiscItems.find(x => x.ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal")
|
||||
@ -89,11 +102,87 @@ export const inventoryController: RequestHandler = async (request, response) =>
|
||||
inventory.FoundToday = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (inventory.UsedDailyDeals.length != 0) {
|
||||
if (daysPassed == 1) {
|
||||
const todayAt0Utc = today * 86400000;
|
||||
const darvoIndex = Math.trunc((todayAt0Utc - 25200000) / (26 * unixTimesInMs.hour));
|
||||
const darvoStart = darvoIndex * (26 * unixTimesInMs.hour) + 25200000;
|
||||
const darvoOid =
|
||||
((darvoStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "adc51a72f7324d95";
|
||||
const deal = await DailyDeal.findById(darvoOid);
|
||||
if (deal) {
|
||||
inventory.UsedDailyDeals = inventory.UsedDailyDeals.filter(x => x == deal.StoreItem); // keep only the deal that came into this new day with us
|
||||
} else {
|
||||
inventory.UsedDailyDeals = [];
|
||||
}
|
||||
} else {
|
||||
inventory.UsedDailyDeals = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Setup CalendarProgress as part of 1999 mission completion?
|
||||
|
||||
const previousYearIteration = inventory.CalendarProgress?.Iteration;
|
||||
|
||||
// We need to do the following to ensure the in-game calendar does not break:
|
||||
getCalendarProgress(inventory); // Keep the CalendarProgress up-to-date (at least for the current year iteration) (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2364)
|
||||
checkCalendarAutoAdvance(inventory, getWorldState().KnownCalendarSeasons[0]); // Skip birthday events for characters if we do not have them unlocked yet (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2424)
|
||||
|
||||
// also handle sending of kiss cinematic at year rollover
|
||||
if (
|
||||
inventory.CalendarProgress!.Iteration != previousYearIteration &&
|
||||
inventory.DialogueHistory &&
|
||||
inventory.DialogueHistory.Dialogues
|
||||
) {
|
||||
let kalymos = false;
|
||||
for (const { dialogueName, kissEmail } of [
|
||||
{
|
||||
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/ArthurDialogue_rom.dialogue",
|
||||
kissEmail: "/Lotus/Types/Items/EmailItems/ArthurKissEmailItem"
|
||||
},
|
||||
{
|
||||
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/EleanorDialogue_rom.dialogue",
|
||||
kissEmail: "/Lotus/Types/Items/EmailItems/EleanorKissEmailItem"
|
||||
},
|
||||
{
|
||||
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/LettieDialogue_rom.dialogue",
|
||||
kissEmail: "/Lotus/Types/Items/EmailItems/LettieKissEmailItem"
|
||||
},
|
||||
{
|
||||
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/JabirDialogue_rom.dialogue",
|
||||
kissEmail: "/Lotus/Types/Items/EmailItems/AmirKissEmailItem"
|
||||
},
|
||||
{
|
||||
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/AoiDialogue_rom.dialogue",
|
||||
kissEmail: "/Lotus/Types/Items/EmailItems/AoiKissEmailItem"
|
||||
},
|
||||
{
|
||||
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/QuincyDialogue_rom.dialogue",
|
||||
kissEmail: "/Lotus/Types/Items/EmailItems/QuincyKissEmailItem"
|
||||
}
|
||||
]) {
|
||||
const dialogue = inventory.DialogueHistory.Dialogues.find(x => x.DialogueName == dialogueName);
|
||||
if (dialogue) {
|
||||
if (dialogue.Rank == 7) {
|
||||
await addEmailItem(inventory, kissEmail);
|
||||
kalymos = false;
|
||||
break;
|
||||
}
|
||||
if (dialogue.Rank == 6) {
|
||||
kalymos = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (kalymos) {
|
||||
await addEmailItem(inventory, "/Lotus/Types/Items/EmailItems/KalymosKissEmailItem");
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@ -106,6 +195,63 @@ export const inventoryController: RequestHandler = async (request, response) =>
|
||||
//await inventory.save();
|
||||
}
|
||||
|
||||
for (let i = 0; i != inventory.QualifyingInvasions.length; ) {
|
||||
const qi = inventory.QualifyingInvasions[i];
|
||||
const invasion = getInvasionByOid(qi.invasionId.toString());
|
||||
if (!invasion) {
|
||||
logger.debug(`removing QualifyingInvasions entry for unknown invasion: ${qi.invasionId.toString()}`);
|
||||
inventory.QualifyingInvasions.splice(i, 1);
|
||||
continue;
|
||||
}
|
||||
if (invasion.Completed) {
|
||||
let factionSidedWith: string | undefined;
|
||||
let battlePay: ICountedItem[] | undefined;
|
||||
if (qi.AttackerScore >= 3) {
|
||||
factionSidedWith = invasion.Faction;
|
||||
battlePay = invasion.AttackerReward.countedItems;
|
||||
logger.debug(`invasion pay from ${factionSidedWith}`, { battlePay });
|
||||
} else if (qi.DefenderScore >= 3) {
|
||||
factionSidedWith = invasion.DefenderFaction;
|
||||
battlePay = invasion.DefenderReward.countedItems;
|
||||
logger.debug(`invasion pay from ${factionSidedWith}`, { battlePay });
|
||||
}
|
||||
if (factionSidedWith) {
|
||||
if (battlePay) {
|
||||
// Decoupling rewards from the inbox message because it may delete itself without being read
|
||||
for (const item of battlePay) {
|
||||
await addItem(inventory, item.ItemType, item.ItemCount);
|
||||
}
|
||||
await createMessage(account._id, [
|
||||
{
|
||||
sndr: eFaction.find(x => x.tag == factionSidedWith)?.name ?? factionSidedWith, // TOVERIFY
|
||||
msg: `/Lotus/Language/G1Quests/${factionSidedWith}_InvasionThankyouMessageBody`,
|
||||
sub: `/Lotus/Language/G1Quests/${factionSidedWith}_InvasionThankyouMessageSubject`,
|
||||
countedAtt: battlePay,
|
||||
attVisualOnly: true,
|
||||
icon:
|
||||
factionSidedWith == "FC_GRINEER"
|
||||
? "/Lotus/Interface/Icons/Npcs/EliteRifleLancerAvatar.png" // Source: https://www.reddit.com/r/Warframe/comments/1aj4usx/battle_pay_worth_10_plat/, https://www.youtube.com/watch?v=XhNZ6ai6BOY
|
||||
: "/Lotus/Interface/Icons/Npcs/CrewmanNormal.png", // My best source for this is https://www.youtube.com/watch?v=rxrCCFm73XE around 1:37
|
||||
// TOVERIFY: highPriority?
|
||||
endDate: new Date(Date.now() + 86400_000) // TOVERIFY: This type of inbox message seems to automatically delete itself. We'll just delete it after 24 hours, but it's not clear if this is correct.
|
||||
}
|
||||
]);
|
||||
}
|
||||
if (invasion.Faction != "FC_INFESTATION") {
|
||||
// Sided with grineer -> opposed corpus -> send zanuka (harvester)
|
||||
// Sided with corpus -> opposed grineer -> send g3 (death squad)
|
||||
inventory[factionSidedWith != "FC_GRINEER" ? "DeathSquadable" : "Harvestable"] = true;
|
||||
// TOVERIFY: Should this happen earlier?
|
||||
// TOVERIFY: Should this send an (ephemeral) email?
|
||||
}
|
||||
}
|
||||
logger.debug(`removing QualifyingInvasions entry for completed invasion: ${qi.invasionId.toString()}`);
|
||||
inventory.QualifyingInvasions.splice(i, 1);
|
||||
continue;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
|
||||
if (inventory.LastInventorySync) {
|
||||
const lastSyncDuviriMood = Math.trunc(inventory.LastInventorySync.getTimestamp().getTime() / 7200000);
|
||||
const currentDuviriMood = Math.trunc(Date.now() / 7200000);
|
||||
@ -134,13 +280,21 @@ export const getInventoryResponse = async (
|
||||
xpBasedLevelCapDisabled: boolean,
|
||||
buildLabel: string | undefined
|
||||
): Promise<IInventoryClient> => {
|
||||
const [inventoryWithLoadOutPresets, ships] = await Promise.all([
|
||||
const [inventoryWithLoadOutPresets, ships, latestMessage] = await Promise.all([
|
||||
inventory.populate<{ LoadOutPresets: ILoadoutDatabase }>("LoadOutPresets"),
|
||||
Ship.find({ ShipOwnerId: inventory.accountOwnerId })
|
||||
Ship.find({ ShipOwnerId: inventory.accountOwnerId }),
|
||||
Inbox.findOne({ ownerId: inventory.accountOwnerId }, "_id").sort({ date: -1 })
|
||||
]);
|
||||
const inventoryResponse = inventoryWithLoadOutPresets.toJSON<IInventoryClient>();
|
||||
inventoryResponse.Ships = ships.map(x => x.toJSON<IShipInventory>());
|
||||
|
||||
// In case mission inventory update added an inbox message, we need to send the Mailbox part so the client knows to refresh it.
|
||||
if (latestMessage) {
|
||||
inventoryResponse.Mailbox = {
|
||||
LastInboxId: toOid(latestMessage._id)
|
||||
};
|
||||
}
|
||||
|
||||
if (config.infiniteCredits) {
|
||||
inventoryResponse.RegularCredits = 999999999;
|
||||
}
|
||||
@ -167,18 +321,6 @@ export const getInventoryResponse = async (
|
||||
}
|
||||
}
|
||||
|
||||
if (config.unlockAllMissions) {
|
||||
inventoryResponse.Missions = [];
|
||||
for (const tag of Object.keys(ExportRegions)) {
|
||||
inventoryResponse.Missions.push({
|
||||
Completes: 1,
|
||||
Tier: 1,
|
||||
Tag: tag
|
||||
});
|
||||
}
|
||||
addString(inventoryResponse.NodeIntrosCompleted, "TeshinHardModeUnlocked");
|
||||
}
|
||||
|
||||
if (config.unlockAllShipDecorations) {
|
||||
inventoryResponse.ShipDecorations = [];
|
||||
for (const [uniqueName, item] of Object.entries(ExportResources)) {
|
||||
@ -193,6 +335,17 @@ export const getInventoryResponse = async (
|
||||
for (const uniqueName in ExportFlavour) {
|
||||
inventoryResponse.FlavourItems.push({ ItemType: uniqueName });
|
||||
}
|
||||
} else if (config.worldState?.baroTennoConRelay) {
|
||||
[
|
||||
"/Lotus/Types/Items/Events/TennoConRelay2022EarlyAccess",
|
||||
"/Lotus/Types/Items/Events/TennoConRelay2023EarlyAccess",
|
||||
"/Lotus/Types/Items/Events/TennoConRelay2024EarlyAccess",
|
||||
"/Lotus/Types/Items/Events/TennoConRelay2025EarlyAccess"
|
||||
].forEach(uniqueName => {
|
||||
if (!inventoryResponse.FlavourItems.some(x => x.ItemType == uniqueName)) {
|
||||
inventoryResponse.FlavourItems.push({ ItemType: uniqueName });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (config.unlockAllSkins) {
|
||||
@ -300,9 +453,6 @@ export const getInventoryResponse = async (
|
||||
applyCheatsToInfestedFoundry(inventoryResponse.InfestedFoundry);
|
||||
}
|
||||
|
||||
// Omitting this field so opening the navigation resyncs the inventory which is more desirable for typical usage.
|
||||
inventoryResponse.LastInventorySync = undefined;
|
||||
|
||||
// Set 2FA enabled so trading post can be used
|
||||
inventoryResponse.HWIDProtectEnabled = true;
|
||||
|
||||
@ -310,7 +460,7 @@ export const getInventoryResponse = async (
|
||||
// Fix nemesis for older versions
|
||||
if (
|
||||
inventoryResponse.Nemesis &&
|
||||
version_compare(getNemesisManifest(inventoryResponse.Nemesis.manifest).minBuild, buildLabel) < 0
|
||||
version_compare(buildLabel, getNemesisManifest(inventoryResponse.Nemesis.manifest).minBuild) < 0
|
||||
) {
|
||||
inventoryResponse.Nemesis = undefined;
|
||||
}
|
||||
@ -339,14 +489,41 @@ export const getInventoryResponse = async (
|
||||
}
|
||||
}
|
||||
|
||||
if (config.unlockAllProfitTakerStages) {
|
||||
inventoryResponse.CompletedJobChains ??= [];
|
||||
const EudicoHeists = inventoryResponse.CompletedJobChains.find(x => x.LocationTag == "EudicoHeists");
|
||||
if (EudicoHeists) {
|
||||
EudicoHeists.Jobs = allEudicoHeistJobs;
|
||||
} else {
|
||||
inventoryResponse.CompletedJobChains.push({
|
||||
LocationTag: "EudicoHeists",
|
||||
Jobs: allEudicoHeistJobs
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (config.unlockAllSimarisResearchEntries) {
|
||||
inventoryResponse.LibraryPersonalTarget = undefined;
|
||||
inventoryResponse.LibraryPersonalProgress = [
|
||||
"/Lotus/Types/Game/Library/Targets/Research1Target",
|
||||
"/Lotus/Types/Game/Library/Targets/Research2Target",
|
||||
"/Lotus/Types/Game/Library/Targets/Research3Target",
|
||||
"/Lotus/Types/Game/Library/Targets/Research4Target",
|
||||
"/Lotus/Types/Game/Library/Targets/Research5Target",
|
||||
"/Lotus/Types/Game/Library/Targets/Research6Target",
|
||||
"/Lotus/Types/Game/Library/Targets/Research7Target"
|
||||
].map(type => ({ TargetType: type, Scans: 10, Completed: true }));
|
||||
}
|
||||
|
||||
return inventoryResponse;
|
||||
};
|
||||
|
||||
const addString = (arr: string[], str: string): void => {
|
||||
if (arr.indexOf(str) == -1) {
|
||||
arr.push(str);
|
||||
}
|
||||
};
|
||||
const allEudicoHeistJobs = [
|
||||
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyOne",
|
||||
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyTwo",
|
||||
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyThree",
|
||||
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyFour"
|
||||
];
|
||||
|
||||
const getExpRequiredForMr = (rank: number): number => {
|
||||
if (rank <= 30) {
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||
import { getInventory, updateCurrency, updateSlots } from "@/src/services/inventoryService";
|
||||
import { RequestHandler } from "express";
|
||||
import { updateSlots } from "@/src/services/inventoryService";
|
||||
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { exhaustive } from "@/src/utils/ts-utils";
|
||||
|
||||
/*
|
||||
loadout slots are additionally purchased slots only
|
||||
@ -23,13 +22,44 @@ export const inventorySlotsController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const body = JSON.parse(req.body as string) as IInventorySlotsRequest;
|
||||
|
||||
if (body.Bin != InventorySlot.SUITS && body.Bin != InventorySlot.PVE_LOADOUTS) {
|
||||
logger.warn(`unexpected slot purchase of type ${body.Bin}, account may be overcharged`);
|
||||
let price;
|
||||
let amount;
|
||||
switch (body.Bin) {
|
||||
case InventorySlot.SUITS:
|
||||
case InventorySlot.MECHSUITS:
|
||||
case InventorySlot.PVE_LOADOUTS:
|
||||
case InventorySlot.CREWMEMBERS:
|
||||
price = 20;
|
||||
amount = 1;
|
||||
break;
|
||||
|
||||
case InventorySlot.SPACESUITS:
|
||||
price = 12;
|
||||
amount = 1;
|
||||
break;
|
||||
|
||||
case InventorySlot.WEAPONS:
|
||||
case InventorySlot.SPACEWEAPONS:
|
||||
case InventorySlot.SENTINELS:
|
||||
case InventorySlot.RJ_COMPONENT_AND_ARMAMENTS:
|
||||
case InventorySlot.AMPS:
|
||||
price = 12;
|
||||
amount = 2;
|
||||
break;
|
||||
|
||||
case InventorySlot.RIVENS:
|
||||
price = 60;
|
||||
amount = 3;
|
||||
break;
|
||||
|
||||
default:
|
||||
exhaustive(body.Bin);
|
||||
throw new Error(`unexpected slot purchase of type ${body.Bin as string}`);
|
||||
}
|
||||
|
||||
const inventory = await getInventory(accountId);
|
||||
const currencyChanges = updateCurrency(inventory, 20, true);
|
||||
updateSlots(inventory, body.Bin, 1, 1);
|
||||
const currencyChanges = updateCurrency(inventory, price, true);
|
||||
updateSlots(inventory, body.Bin, amount, amount);
|
||||
await inventory.save();
|
||||
|
||||
res.json({ InventoryChanges: currencyChanges });
|
||||
|
@ -4,16 +4,16 @@ import { config } from "@/src/services/configService";
|
||||
import { buildConfig } from "@/src/services/buildConfigService";
|
||||
|
||||
import { Account } from "@/src/models/loginModel";
|
||||
import { createAccount, isCorrectPassword, isNameTaken } from "@/src/services/loginService";
|
||||
import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "@/src/services/loginService";
|
||||
import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { version_compare } from "@/src/helpers/inventoryHelpers";
|
||||
import { sendWsBroadcastTo } from "@/src/services/wsService";
|
||||
|
||||
export const loginController: RequestHandler = async (request, response) => {
|
||||
const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
|
||||
|
||||
const account = await Account.findOne({ email: loginRequest.email });
|
||||
const nonce = Math.round(Math.random() * Number.MAX_SAFE_INTEGER);
|
||||
|
||||
const buildLabel: string =
|
||||
typeof request.query.buildLabel == "string"
|
||||
@ -42,26 +42,14 @@ export const loginController: RequestHandler = async (request, response) => {
|
||||
loginRequest.ClientType == "webui-register")
|
||||
) {
|
||||
try {
|
||||
const nameFromEmail = loginRequest.email.substring(0, loginRequest.email.indexOf("@"));
|
||||
let name = nameFromEmail || loginRequest.email.substring(1) || "SpaceNinja";
|
||||
if (await isNameTaken(name)) {
|
||||
let suffix = 0;
|
||||
do {
|
||||
++suffix;
|
||||
name = nameFromEmail + suffix;
|
||||
} while (await isNameTaken(name));
|
||||
}
|
||||
const name = await getUsernameFromEmail(loginRequest.email);
|
||||
const newAccount = await createAccount({
|
||||
email: loginRequest.email,
|
||||
password: loginRequest.password,
|
||||
DisplayName: name,
|
||||
CountryCode: loginRequest.lang?.toUpperCase() ?? "EN",
|
||||
ClientType: loginRequest.ClientType == "webui-register" ? "webui" : loginRequest.ClientType,
|
||||
CrossPlatformAllowed: true,
|
||||
ForceLogoutVersion: 0,
|
||||
ConsentNeeded: false,
|
||||
TrackedSettings: [],
|
||||
Nonce: nonce,
|
||||
ClientType: loginRequest.ClientType,
|
||||
Nonce: createNonce(),
|
||||
BuildLabel: buildLabel,
|
||||
LastLogin: new Date()
|
||||
});
|
||||
@ -80,38 +68,29 @@ export const loginController: RequestHandler = async (request, response) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (loginRequest.ClientType == "webui-register") {
|
||||
response.status(400).json({ error: "account already exists" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isCorrectPassword(loginRequest.password, account.password)) {
|
||||
response.status(400).json({ error: "incorrect login data" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (loginRequest.ClientType == "webui") {
|
||||
if (!account.Nonce) {
|
||||
account.ClientType = "webui";
|
||||
account.Nonce = nonce;
|
||||
if (account.Nonce && account.ClientType != "webui" && !account.Dropped && !loginRequest.kick) {
|
||||
// U17 seems to handle "nonce still set" like a login failure.
|
||||
if (version_compare(buildLabel, "2015.12.05.18.07") >= 0) {
|
||||
response.status(400).send({ error: "nonce still set" });
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (account.Nonce && account.ClientType != "webui" && !account.Dropped && !loginRequest.kick) {
|
||||
// U17 seems to handle "nonce still set" like a login failure.
|
||||
if (version_compare(buildLabel, "2015.12.05.18.07") >= 0) {
|
||||
response.status(400).send({ error: "nonce still set" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
account.ClientType = loginRequest.ClientType;
|
||||
account.Nonce = nonce;
|
||||
account.CountryCode = loginRequest.lang?.toUpperCase() ?? "EN";
|
||||
account.BuildLabel = buildLabel;
|
||||
account.LastLogin = new Date();
|
||||
}
|
||||
|
||||
account.ClientType = loginRequest.ClientType;
|
||||
account.Nonce = createNonce();
|
||||
account.CountryCode = loginRequest.lang?.toUpperCase() ?? "EN";
|
||||
account.BuildLabel = buildLabel;
|
||||
account.LastLogin = new Date();
|
||||
await account.save();
|
||||
|
||||
// Tell WebUI its nonce has been invalidated
|
||||
sendWsBroadcastTo(account._id.toString(), { logged_out: true });
|
||||
|
||||
response.json(createLoginResponse(myAddress, myUrlBase, account.toJSON(), buildLabel));
|
||||
};
|
||||
|
||||
@ -151,7 +130,7 @@ const createLoginResponse = (
|
||||
resp.Groups = [];
|
||||
}
|
||||
if (version_compare(buildLabel, "2021.04.13.19.58") >= 0) {
|
||||
resp.DTLS = 99;
|
||||
resp.DTLS = 0; // bit 0 enables DTLS. if enabled, additional bits can be set, e.g. bit 2 to enable logging. on live, the value is 99.
|
||||
}
|
||||
if (version_compare(buildLabel, "2022.04.29.12.53") >= 0) {
|
||||
resp.ClientType = account.ClientType;
|
||||
|
@ -8,6 +8,8 @@ import {
|
||||
setAccountGotLoginRewardToday
|
||||
} from "@/src/services/loginRewardService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { config } from "@/src/services/configService";
|
||||
import { sendWsBroadcastTo } from "@/src/services/wsService";
|
||||
|
||||
export const loginRewardsController: RequestHandler = async (req, res) => {
|
||||
const account = await getAccountForRequest(req);
|
||||
@ -15,7 +17,7 @@ export const loginRewardsController: RequestHandler = async (req, res) => {
|
||||
const isMilestoneDay = account.LoginDays == 5 || account.LoginDays % 50 == 0;
|
||||
const nextMilestoneDay = account.LoginDays < 5 ? 5 : (Math.trunc(account.LoginDays / 50) + 1) * 50;
|
||||
|
||||
if (today == account.LastLoginRewardDate) {
|
||||
if (today == account.LastLoginRewardDate || config.disableDailyTribute) {
|
||||
res.json({
|
||||
DailyTributeInfo: {
|
||||
IsMilestoneDay: isMilestoneDay,
|
||||
@ -46,10 +48,10 @@ export const loginRewardsController: RequestHandler = async (req, res) => {
|
||||
response.DailyTributeInfo.HasChosenReward = true;
|
||||
response.DailyTributeInfo.ChosenReward = randomRewards[0];
|
||||
response.DailyTributeInfo.NewInventory = await claimLoginReward(inventory, randomRewards[0]);
|
||||
await inventory.save();
|
||||
|
||||
setAccountGotLoginRewardToday(account);
|
||||
await account.save();
|
||||
await Promise.all([inventory.save(), account.save()]);
|
||||
|
||||
sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
|
||||
}
|
||||
res.json(response);
|
||||
};
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
} from "@/src/services/loginRewardService";
|
||||
import { getAccountForRequest } from "@/src/services/loginService";
|
||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||
import { sendWsBroadcastTo } from "@/src/services/wsService";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { RequestHandler } from "express";
|
||||
@ -34,11 +35,10 @@ export const loginRewardsSelectionController: RequestHandler = async (req, res)
|
||||
chosenReward = randomRewards.find(x => x.StoreItemType == body.ChosenReward)!;
|
||||
inventoryChanges = await claimLoginReward(inventory, chosenReward);
|
||||
}
|
||||
await inventory.save();
|
||||
|
||||
setAccountGotLoginRewardToday(account);
|
||||
await account.save();
|
||||
await Promise.all([inventory.save(), account.save()]);
|
||||
|
||||
sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
|
||||
res.json({
|
||||
DailyTributeInfo: {
|
||||
NewInventory: inventoryChanges,
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { Account } from "@/src/models/loginModel";
|
||||
import { sendWsBroadcastTo } from "@/src/services/wsService";
|
||||
|
||||
export const logoutController: RequestHandler = async (req, res) => {
|
||||
if (!req.query.accountId) {
|
||||
@ -10,7 +11,7 @@ export const logoutController: RequestHandler = async (req, res) => {
|
||||
throw new Error("Request is missing nonce parameter");
|
||||
}
|
||||
|
||||
await Account.updateOne(
|
||||
const stat = await Account.updateOne(
|
||||
{
|
||||
_id: req.query.accountId,
|
||||
Nonce: nonce
|
||||
@ -19,6 +20,10 @@ export const logoutController: RequestHandler = async (req, res) => {
|
||||
Nonce: 0
|
||||
}
|
||||
);
|
||||
if (stat.modifiedCount) {
|
||||
// Tell WebUI its nonce has been invalidated
|
||||
sendWsBroadcastTo(req.query.accountId as string, { logged_out: true });
|
||||
}
|
||||
|
||||
res.writeHead(200, {
|
||||
"Content-Type": "text/html",
|
||||
|
@ -3,10 +3,16 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getAccountForRequest } from "@/src/services/loginService";
|
||||
import { IMissionInventoryUpdateRequest } from "@/src/types/requestTypes";
|
||||
import { addMissionInventoryUpdates, addMissionRewards } from "@/src/services/missionInventoryUpdateService";
|
||||
import { generateRewardSeed, getInventory } from "@/src/services/inventoryService";
|
||||
import { getInventoryResponse } from "./inventoryController";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getInventoryResponse } from "@/src/controllers/api/inventoryController";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { IMissionInventoryUpdateResponse } from "@/src/types/missionTypes";
|
||||
import {
|
||||
IMissionInventoryUpdateResponse,
|
||||
IMissionInventoryUpdateResponseBackToDryDock,
|
||||
IMissionInventoryUpdateResponseRailjackInterstitial
|
||||
} from "@/src/types/missionTypes";
|
||||
import { sendWsBroadcastTo } from "@/src/services/wsService";
|
||||
import { generateRewardSeed } from "@/src/services/rngService";
|
||||
|
||||
/*
|
||||
**** INPUT ****
|
||||
@ -76,6 +82,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
|
||||
InventoryJson: JSON.stringify(inventoryResponse),
|
||||
MissionRewards: []
|
||||
});
|
||||
sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
|
||||
return;
|
||||
}
|
||||
|
||||
@ -86,17 +93,15 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
|
||||
AffiliationMods,
|
||||
SyndicateXPItemReward,
|
||||
ConquestCompletedMissionsCount
|
||||
} = await addMissionRewards(inventory, missionReport, firstCompletion);
|
||||
} = await addMissionRewards(account, inventory, missionReport, firstCompletion);
|
||||
|
||||
if (missionReport.EndOfMatchUpload) {
|
||||
inventory.RewardSeed = generateRewardSeed();
|
||||
}
|
||||
await inventory.save();
|
||||
const inventoryResponse = await getInventoryResponse(inventory, true, account.BuildLabel);
|
||||
|
||||
//TODO: figure out when to send inventory. it is needed for many cases.
|
||||
res.json({
|
||||
InventoryJson: JSON.stringify(inventoryResponse),
|
||||
const deltas: IMissionInventoryUpdateResponseRailjackInterstitial = {
|
||||
InventoryChanges: inventoryChanges,
|
||||
MissionRewards,
|
||||
...credits,
|
||||
@ -105,7 +110,26 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
|
||||
SyndicateXPItemReward,
|
||||
AffiliationMods,
|
||||
ConquestCompletedMissionsCount
|
||||
} satisfies IMissionInventoryUpdateResponse);
|
||||
};
|
||||
if (missionReport.RJ) {
|
||||
logger.debug(`railjack interstitial request, sending only deltas`, deltas);
|
||||
res.json(deltas);
|
||||
} else if (missionReport.RewardInfo) {
|
||||
logger.debug(`classic mission completion, sending everything`);
|
||||
const inventoryResponse = await getInventoryResponse(inventory, true, account.BuildLabel);
|
||||
res.json({
|
||||
InventoryJson: JSON.stringify(inventoryResponse),
|
||||
...deltas
|
||||
} satisfies IMissionInventoryUpdateResponse);
|
||||
} else {
|
||||
logger.debug(`no reward info, assuming this wasn't a mission completion and we should just sync inventory`);
|
||||
const inventoryResponse = await getInventoryResponse(inventory, true, account.BuildLabel);
|
||||
res.json({
|
||||
InventoryJson: JSON.stringify(inventoryResponse)
|
||||
} satisfies IMissionInventoryUpdateResponseBackToDryDock);
|
||||
}
|
||||
|
||||
sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { sendWsBroadcastTo } from "@/src/services/wsService";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import {
|
||||
getInventory,
|
||||
@ -15,10 +16,9 @@ import {
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { getDefaultUpgrades } from "@/src/services/itemDataService";
|
||||
import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
|
||||
import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { getRandomInt } from "@/src/services/rngService";
|
||||
import { ExportSentinels, ExportWeapons, IDefaultUpgrade } from "warframe-public-export-plus";
|
||||
import { Status } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { IEquipmentDatabase, Status } from "@/src/types/equipmentTypes";
|
||||
|
||||
interface IModularCraftRequest {
|
||||
WeaponType: string;
|
||||
@ -195,4 +195,5 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
|
||||
MiscItems: miscItemChanges
|
||||
}
|
||||
});
|
||||
sendWsBroadcastTo(accountId, { update_inventory: true });
|
||||
};
|
||||
|
@ -3,7 +3,7 @@ import { ExportWeapons } from "warframe-public-export-plus";
|
||||
import { IMongoDate } from "@/src/types/commonTypes";
|
||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||
import { SRng } from "@/src/services/rngService";
|
||||
import { ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { ArtifactPolarity } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import {
|
||||
addEquipment,
|
||||
@ -15,8 +15,10 @@ import {
|
||||
} from "@/src/services/inventoryService";
|
||||
import { getDefaultUpgrades } from "@/src/services/itemDataService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { sendWsBroadcastTo } from "@/src/services/wsService";
|
||||
import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { EquipmentFeatures } from "@/src/types/equipmentTypes";
|
||||
|
||||
export const modularWeaponSaleController: RequestHandler = async (req, res) => {
|
||||
const partTypeToParts: Record<string, string[]> = {};
|
||||
@ -67,6 +69,7 @@ export const modularWeaponSaleController: RequestHandler = async (req, res) => {
|
||||
res.json({
|
||||
InventoryChanges: inventoryChanges
|
||||
});
|
||||
sendWsBroadcastTo(accountId, { update_inventory: true });
|
||||
} else {
|
||||
throw new Error(`unknown modularWeaponSale op: ${String(req.query.op)}`);
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { sendWsBroadcastTo } from "@/src/services/wsService";
|
||||
|
||||
interface INameWeaponRequest {
|
||||
ItemName: string;
|
||||
@ -27,4 +28,5 @@ export const nameWeaponController: RequestHandler = async (req, res) => {
|
||||
res.json({
|
||||
InventoryChanges: currencyChanges
|
||||
});
|
||||
sendWsBroadcastTo(accountId, { update_inventory: true });
|
||||
};
|
||||
|
@ -1,21 +1,28 @@
|
||||
import { version_compare } from "@/src/helpers/inventoryHelpers";
|
||||
import {
|
||||
consumeModCharge,
|
||||
antivirusMods,
|
||||
decodeNemesisGuess,
|
||||
encodeNemesisGuess,
|
||||
getInfNodes,
|
||||
getKnifeUpgrade,
|
||||
getNemesisManifest,
|
||||
getNemesisPasscode,
|
||||
getNemesisPasscodeModTypes,
|
||||
IKnifeResponse
|
||||
GUESS_CORRECT,
|
||||
GUESS_INCORRECT,
|
||||
GUESS_NEUTRAL,
|
||||
GUESS_NONE,
|
||||
GUESS_WILDCARD,
|
||||
IKnifeResponse,
|
||||
parseUpgrade
|
||||
} from "@/src/helpers/nemesisHelpers";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
|
||||
import { freeUpSlot, getInventory } from "@/src/services/inventoryService";
|
||||
import { addMods, freeUpSlot, getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountForRequest } from "@/src/services/loginService";
|
||||
import { SRng } from "@/src/services/rngService";
|
||||
import { IMongoDate, IOid } from "@/src/types/commonTypes";
|
||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { IEquipmentClient } from "@/src/types/equipmentTypes";
|
||||
import {
|
||||
IInnateDamageFingerprint,
|
||||
IInventoryClient,
|
||||
@ -29,6 +36,7 @@ import {
|
||||
} from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { RequestHandler } from "express";
|
||||
import { Types } from "mongoose";
|
||||
|
||||
export const nemesisController: RequestHandler = async (req, res) => {
|
||||
const account = await getAccountForRequest(req);
|
||||
@ -82,7 +90,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -97,18 +105,29 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
||||
if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
|
||||
const guess: number[] = [body.guess & 0xf, (body.guess >> 4) & 0xf, (body.guess >> 8) & 0xf];
|
||||
const passcode = getNemesisPasscode(inventory.Nemesis!)[0];
|
||||
|
||||
// Add to GuessHistory
|
||||
const result1 = passcode == guess[0] ? 0 : 1;
|
||||
const result2 = passcode == guess[1] ? 0 : 1;
|
||||
const result3 = passcode == guess[2] ? 0 : 1;
|
||||
const result1 = passcode == guess[0] ? GUESS_CORRECT : GUESS_INCORRECT;
|
||||
const result2 = passcode == guess[1] ? GUESS_CORRECT : GUESS_INCORRECT;
|
||||
const result3 = passcode == guess[2] ? GUESS_CORRECT : GUESS_INCORRECT;
|
||||
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
|
||||
const response: IKnifeResponse = {};
|
||||
if (result1 == 0 || result2 == 0 || result3 == 0) {
|
||||
if (result1 == GUESS_CORRECT || result2 == GUESS_CORRECT || result3 == GUESS_CORRECT) {
|
||||
let antivirusGain = 5;
|
||||
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!;
|
||||
const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid);
|
||||
@ -117,50 +136,108 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
||||
for (const upgrade of body.knife!.AttachedUpgrades) {
|
||||
switch (upgrade.ItemType) {
|
||||
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndSpeedOnUseMod":
|
||||
antivirusGain += 10;
|
||||
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
|
||||
break;
|
||||
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndWeaponDamageOnUseMod":
|
||||
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusSmallOnSingleUseMod":
|
||||
antivirusGain += 10;
|
||||
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
|
||||
break;
|
||||
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusLargeOnSingleUseMod": // Instant Secure
|
||||
antivirusGain += 15;
|
||||
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
|
||||
break;
|
||||
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusOnUseMod": // Immuno Shield
|
||||
antivirusGain += 15;
|
||||
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
|
||||
break;
|
||||
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusSmallOnSingleUseMod":
|
||||
antivirusGain += 10;
|
||||
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
|
||||
break;
|
||||
}
|
||||
}
|
||||
inventory.Nemesis!.HenchmenKilled += antivirusGain;
|
||||
if (inventory.Nemesis!.HenchmenKilled >= 100) {
|
||||
inventory.Nemesis!.HenchmenKilled = 100;
|
||||
|
||||
// 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) {
|
||||
inventory.Nemesis!.HenchmenKilled = 100;
|
||||
if (inventory.Nemesis!.HenchmenKilled < 100) {
|
||||
inventory.Nemesis!.InfNodes = getInfNodes(getNemesisManifest(inventory.Nemesis!.manifest), 0);
|
||||
}
|
||||
inventory.Nemesis!.InfNodes = getInfNodes(getNemesisManifest(inventory.Nemesis!.manifest), 0);
|
||||
|
||||
await inventory.save();
|
||||
res.json(response);
|
||||
} else {
|
||||
const passcode = getNemesisPasscode(inventory.Nemesis!);
|
||||
if (passcode[body.position] != body.guess) {
|
||||
res.end();
|
||||
} else {
|
||||
inventory.Nemesis!.Rank += 1;
|
||||
inventory.Nemesis!.InfNodes = getInfNodes(
|
||||
getNemesisManifest(inventory.Nemesis!.manifest),
|
||||
inventory.Nemesis!.Rank
|
||||
// For first guess, create a new entry.
|
||||
if (body.position == 0) {
|
||||
inventory.Nemesis!.GuessHistory.push(
|
||||
encodeNemesisGuess([
|
||||
{
|
||||
symbol: GUESS_NONE,
|
||||
result: GUESS_NEUTRAL
|
||||
},
|
||||
{
|
||||
symbol: GUESS_NONE,
|
||||
result: GUESS_NEUTRAL
|
||||
},
|
||||
{
|
||||
symbol: GUESS_NONE,
|
||||
result: GUESS_NEUTRAL
|
||||
}
|
||||
])
|
||||
);
|
||||
await inventory.save();
|
||||
res.json({ RankIncrease: 1 });
|
||||
}
|
||||
|
||||
// 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") {
|
||||
// report spawn; POST but no application data in body
|
||||
@ -170,11 +247,14 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
||||
res.json({ LastEnc: inventory.Nemesis!.LastEnc });
|
||||
} else if ((req.query.mode as string) == "s") {
|
||||
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));
|
||||
body.target.fp = BigInt(body.target.fp);
|
||||
|
||||
const manifest = getNemesisManifest(body.target.manifest);
|
||||
if (account.BuildLabel && version_compare(manifest.minBuild, account.BuildLabel) < 0) {
|
||||
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.`
|
||||
);
|
||||
@ -185,13 +265,15 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
||||
const weapons: readonly string[] = manifest.weapons;
|
||||
const initialWeaponIdx = new SRng(body.target.fp).randomInt(0, weapons.length - 1);
|
||||
weaponIdx = initialWeaponIdx;
|
||||
do {
|
||||
const weapon = weapons[weaponIdx];
|
||||
if (body.target.DisallowedWeapons.indexOf(weapon) == -1) {
|
||||
break;
|
||||
}
|
||||
weaponIdx = (weaponIdx + 1) % weapons.length;
|
||||
} while (weaponIdx != initialWeaponIdx);
|
||||
if (body.target.DisallowedWeapons) {
|
||||
do {
|
||||
const weapon = weapons[weaponIdx];
|
||||
if (body.target.DisallowedWeapons.indexOf(weapon) == -1) {
|
||||
break;
|
||||
}
|
||||
weaponIdx = (weaponIdx + 1) % weapons.length;
|
||||
} while (weaponIdx != initialWeaponIdx);
|
||||
}
|
||||
}
|
||||
|
||||
inventory.Nemesis = {
|
||||
@ -212,10 +294,10 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
||||
GuessHistory: [],
|
||||
Hints: [],
|
||||
HintProgress: 0,
|
||||
Weakened: body.target.Weakened,
|
||||
Weakened: false,
|
||||
PrevOwners: 0,
|
||||
HenchmenKilled: 0,
|
||||
SecondInCommand: body.target.SecondInCommand,
|
||||
SecondInCommand: false,
|
||||
MissionCount: 0,
|
||||
LastEnc: 0
|
||||
};
|
||||
@ -225,36 +307,15 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
||||
target: inventory.toJSON().Nemesis
|
||||
});
|
||||
} else if ((req.query.mode as string) == "w") {
|
||||
const inventory = await getInventory(
|
||||
account._id.toString(),
|
||||
"Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades"
|
||||
);
|
||||
const inventory = await getInventory(account._id.toString(), "Nemesis");
|
||||
//const body = getJSONfromString<INemesisWeakenRequest>(String(req.body));
|
||||
|
||||
inventory.Nemesis!.InfNodes = [
|
||||
{
|
||||
Node: getNemesisManifest(inventory.Nemesis!.manifest).showdownNode,
|
||||
Influence: 1
|
||||
}
|
||||
];
|
||||
inventory.Nemesis!.Weakened = true;
|
||||
// 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.
|
||||
|
||||
const response: IKnifeResponse & { target: INemesisClient } = {
|
||||
const response: INemesisWeakenResponse = {
|
||||
target: inventory.toJSON<IInventoryClient>().Nemesis!
|
||||
};
|
||||
|
||||
// Consume charge of the correct requiem mod(s)
|
||||
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!;
|
||||
const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid);
|
||||
const dataknifeConfigIndex = dataknifeLoadout?.s?.mod ?? 0;
|
||||
const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex].Upgrades!;
|
||||
const modTypes = getNemesisPasscodeModTypes(inventory.Nemesis!);
|
||||
for (const modType of modTypes) {
|
||||
const upgrade = getKnifeUpgrade(inventory, dataknifeUpgrades, modType);
|
||||
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
|
||||
}
|
||||
|
||||
await inventory.save();
|
||||
res.json(response);
|
||||
} else {
|
||||
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
||||
@ -276,7 +337,7 @@ interface INemesisStartRequest {
|
||||
KillingSuit: string;
|
||||
killingDamageType: number;
|
||||
ShoulderHelmet: string;
|
||||
DisallowedWeapons: string[];
|
||||
DisallowedWeapons?: string[];
|
||||
WeaponIdx: number;
|
||||
AgentIdx: number;
|
||||
BirthNode: string;
|
||||
@ -310,11 +371,19 @@ interface INemesisRequiemRequest {
|
||||
knife?: IKnife;
|
||||
}
|
||||
|
||||
interface INemesisRequiemResponse extends IKnifeResponse {
|
||||
RankIncrease?: number;
|
||||
}
|
||||
|
||||
// interface INemesisWeakenRequest {
|
||||
// target: INemesisClient;
|
||||
// knife: IKnife;
|
||||
// }
|
||||
|
||||
interface INemesisWeakenResponse extends IKnifeResponse {
|
||||
target: INemesisClient;
|
||||
}
|
||||
|
||||
interface IKnife {
|
||||
Item: IEquipmentClient;
|
||||
Skins: IWeaponSkinClient[];
|
||||
@ -323,3 +392,54 @@ interface IKnife {
|
||||
AttachedUpgrades: IUpgradeClient[];
|
||||
HiddenWhenHolstered: boolean;
|
||||
}
|
||||
|
||||
const consumeModCharge = (
|
||||
response: IKnifeResponse,
|
||||
inventory: TInventoryDatabaseDocument,
|
||||
upgrade: { ItemId: IOid; ItemType: string },
|
||||
dataknifeUpgrades: string[]
|
||||
): void => {
|
||||
response.UpgradeIds ??= [];
|
||||
response.UpgradeTypes ??= [];
|
||||
response.UpgradeFingerprints ??= [];
|
||||
response.UpgradeNew ??= [];
|
||||
response.HasKnife = true;
|
||||
|
||||
if (upgrade.ItemId.$oid != "000000000000000000000000") {
|
||||
const dbUpgrade = inventory.Upgrades.id(upgrade.ItemId.$oid)!;
|
||||
const fingerprint = JSON.parse(dbUpgrade.UpgradeFingerprint!) as { lvl: number };
|
||||
fingerprint.lvl += 1;
|
||||
dbUpgrade.UpgradeFingerprint = JSON.stringify(fingerprint);
|
||||
|
||||
response.UpgradeIds.push(upgrade.ItemId.$oid);
|
||||
response.UpgradeTypes.push(upgrade.ItemType);
|
||||
response.UpgradeFingerprints.push(fingerprint);
|
||||
response.UpgradeNew.push(false);
|
||||
} else {
|
||||
const id = new Types.ObjectId();
|
||||
inventory.Upgrades.push({
|
||||
_id: id,
|
||||
ItemType: upgrade.ItemType,
|
||||
UpgradeFingerprint: `{"lvl":1}`
|
||||
});
|
||||
|
||||
addMods(inventory, [
|
||||
{
|
||||
ItemType: upgrade.ItemType,
|
||||
ItemCount: -1
|
||||
}
|
||||
]);
|
||||
|
||||
const dataknifeRawUpgradeIndex = dataknifeUpgrades.indexOf(upgrade.ItemType);
|
||||
if (dataknifeRawUpgradeIndex != -1) {
|
||||
dataknifeUpgrades[dataknifeRawUpgradeIndex] = id.toString();
|
||||
} else {
|
||||
logger.warn(`${upgrade.ItemType} not found in dataknife config`);
|
||||
}
|
||||
|
||||
response.UpgradeIds.push(id.toString());
|
||||
response.UpgradeTypes.push(upgrade.ItemType);
|
||||
response.UpgradeFingerprints.push({ lvl: 1 });
|
||||
response.UpgradeNew.push(true);
|
||||
}
|
||||
};
|
||||
|
@ -57,7 +57,15 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
|
||||
component.DecoCapacity -= meta.capacityCost;
|
||||
}
|
||||
} else {
|
||||
const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)![0];
|
||||
const entry = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type);
|
||||
if (!entry) {
|
||||
throw new Error(`unknown deco type: ${deco.Type}`);
|
||||
}
|
||||
const [itemType, meta] = entry;
|
||||
if (meta.dojoCapacityCost === undefined) {
|
||||
throw new Error(`unknown deco type: ${deco.Type}`);
|
||||
}
|
||||
component.DecoCapacity -= meta.dojoCapacityCost;
|
||||
if (deco.Sockets !== undefined) {
|
||||
guild.VaultFusionTreasures!.find(x => x.ItemType == itemType && x.Sockets == deco.Sockets)!.ItemCount -=
|
||||
1;
|
||||
@ -71,7 +79,13 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
|
||||
if (meta) {
|
||||
processDojoBuildMaterialsGathered(guild, meta);
|
||||
}
|
||||
} else if (guild.AutoContributeFromVault && guild.VaultRegularCredits && guild.VaultMiscItems) {
|
||||
} else if (
|
||||
deco.Type.startsWith("/Lotus/Objects/Tenno/Dojo/NpcPlaceables/") ||
|
||||
(guild.AutoContributeFromVault && guild.VaultRegularCredits && guild.VaultMiscItems)
|
||||
) {
|
||||
if (!guild.VaultRegularCredits || !guild.VaultMiscItems) {
|
||||
throw new Error(`dojo visitor placed without anything in vault?!`);
|
||||
}
|
||||
if (guild.VaultRegularCredits >= scaleRequiredCount(guild.Tier, meta.price)) {
|
||||
let enoughMiscItems = true;
|
||||
for (const ingredient of meta.ingredients) {
|
||||
|
@ -1,25 +1,52 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { addConsumables, getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { IPlayerSkills } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const playerSkillsController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "PlayerSkills");
|
||||
const inventory = await getInventory(accountId, "PlayerSkills Consumables");
|
||||
const request = getJSONfromString<IPlayerSkillsRequest>(String(req.body));
|
||||
|
||||
const oldRank: number = inventory.PlayerSkills[request.Skill as keyof IPlayerSkills];
|
||||
const cost = (request.Pool == "LPP_DRIFTER" ? drifterCosts[oldRank] : 1 << oldRank) * 1000;
|
||||
inventory.PlayerSkills[request.Pool as keyof IPlayerSkills] -= cost;
|
||||
inventory.PlayerSkills[request.Skill as keyof IPlayerSkills]++;
|
||||
await inventory.save();
|
||||
|
||||
const inventoryChanges: IInventoryChanges = {};
|
||||
if (request.Skill == "LPS_COMMAND") {
|
||||
if (inventory.PlayerSkills.LPS_COMMAND == 9) {
|
||||
const consumablesChanges = [
|
||||
{
|
||||
ItemType: "/Lotus/Types/Restoratives/Consumable/CrewmateBall",
|
||||
ItemCount: 1
|
||||
}
|
||||
];
|
||||
addConsumables(inventory, consumablesChanges);
|
||||
inventoryChanges.Consumables = consumablesChanges;
|
||||
}
|
||||
} else if (request.Skill == "LPS_DRIFT_RIDING") {
|
||||
if (inventory.PlayerSkills.LPS_DRIFT_RIDING == 9) {
|
||||
const consumablesChanges = [
|
||||
{
|
||||
ItemType: "/Lotus/Types/Restoratives/ErsatzSummon",
|
||||
ItemCount: 1
|
||||
}
|
||||
];
|
||||
addConsumables(inventory, consumablesChanges);
|
||||
inventoryChanges.Consumables = consumablesChanges;
|
||||
}
|
||||
}
|
||||
|
||||
await inventory.save();
|
||||
res.json({
|
||||
Pool: request.Pool,
|
||||
PoolInc: -cost,
|
||||
Skill: request.Skill,
|
||||
Rank: oldRank + 1
|
||||
Rank: oldRank + 1,
|
||||
InventoryChanges: inventoryChanges
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -11,7 +11,7 @@ export const projectionManagerController: RequestHandler = async (req, res) => {
|
||||
const [era, category, currentQuality] = parseProjection(request.projectionType);
|
||||
const upgradeCost = config.dontSubtractVoidTraces
|
||||
? 0
|
||||
: (request.qualityTag - qualityKeywordToNumber[currentQuality]) * 25;
|
||||
: qualityNumberToCost[request.qualityTag] - qualityNumberToCost[qualityKeywordToNumber[currentQuality]];
|
||||
const newProjectionType = findProjection(era, category, qualityNumberToKeyword[request.qualityTag]);
|
||||
addMiscItems(inventory, [
|
||||
{
|
||||
@ -49,6 +49,7 @@ const qualityKeywordToNumber: Record<VoidProjectionQuality, number> = {
|
||||
VPQ_GOLD: 2,
|
||||
VPQ_PLATINUM: 3
|
||||
};
|
||||
const qualityNumberToCost = [0, 25, 50, 100];
|
||||
|
||||
// e.g. "/Lotus/Types/Game/Projections/T2VoidProjectionProteaPrimeDBronze" -> ["Lith", "W5", "VPQ_BRONZE"]
|
||||
const parseProjection = (typeName: string): [string, string, VoidProjectionQuality] => {
|
||||
|
@ -3,6 +3,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { IPurchaseRequest } from "@/src/types/purchaseTypes";
|
||||
import { handlePurchase } from "@/src/services/purchaseService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { sendWsBroadcastTo } from "@/src/services/wsService";
|
||||
|
||||
export const purchaseController: RequestHandler = async (req, res) => {
|
||||
const purchaseRequest = JSON.parse(String(req.body)) as IPurchaseRequest;
|
||||
@ -10,5 +11,7 @@ export const purchaseController: RequestHandler = async (req, res) => {
|
||||
const inventory = await getInventory(accountId);
|
||||
const response = await handlePurchase(purchaseRequest, inventory);
|
||||
await inventory.save();
|
||||
//console.log(JSON.stringify(response, null, 2));
|
||||
res.json(response);
|
||||
sendWsBroadcastTo(accountId, { update_inventory: true });
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { sendWsBroadcastTo } from "@/src/services/wsService";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const releasePetController: RequestHandler = async (req, res) => {
|
||||
@ -19,6 +20,7 @@ export const releasePetController: RequestHandler = async (req, res) => {
|
||||
|
||||
await inventory.save();
|
||||
res.json({ inventoryChanges }); // Not a mistake; it's "inventoryChanges" here.
|
||||
sendWsBroadcastTo(accountId, { update_inventory: true });
|
||||
};
|
||||
|
||||
interface IReleasePetRequest {
|
||||
|
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/wsService";
|
||||
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;
|
||||
}
|
5
src/controllers/api/resetQuestProgressController.ts
Normal file
5
src/controllers/api/resetQuestProgressController.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const resetQuestProgressController: RequestHandler = (_req, res) => {
|
||||
res.send("1").end();
|
||||
};
|
@ -1,7 +1,7 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { Status } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { Status } from "@/src/types/equipmentTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const retrievePetFromStasisController: RequestHandler = async (req, res) => {
|
||||
|
@ -24,7 +24,7 @@ export const saveDialogueController: RequestHandler = async (req, res) => {
|
||||
inventory.DialogueHistory.Dialogues ??= [];
|
||||
const dialogue = getDialogue(inventory, request.DialogueName);
|
||||
dialogue.Rank = request.Rank;
|
||||
dialogue.Chemistry = request.Chemistry;
|
||||
dialogue.Chemistry += request.Chemistry;
|
||||
dialogue.QueuedDialogues = request.QueuedDialogues;
|
||||
for (const bool of request.Booleans) {
|
||||
dialogue.Booleans.push(bool);
|
||||
|
@ -2,7 +2,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { RequestHandler } from "express";
|
||||
import { ISettings } from "../../types/inventoryTypes/inventoryTypes";
|
||||
import { ISettings } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
|
||||
interface ISaveSettingsRequest {
|
||||
Settings: ISettings;
|
||||
|
@ -9,21 +9,28 @@ import {
|
||||
freeUpSlot,
|
||||
combineInventoryChanges,
|
||||
addCrewShipRawSalvage,
|
||||
addFusionPoints
|
||||
addFusionPoints,
|
||||
addCrewShipFusionPoints,
|
||||
addFusionTreasures
|
||||
} from "@/src/services/inventoryService";
|
||||
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||
import { sendWsBroadcastEx } from "@/src/services/wsService";
|
||||
import { parseFusionTreasure } from "@/src/helpers/inventoryHelpers";
|
||||
|
||||
export const sellController: RequestHandler = async (req, res) => {
|
||||
const payload = JSON.parse(String(req.body)) as ISellRequest;
|
||||
//console.log(JSON.stringify(payload, null, 2));
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const requiredFields = new Set<keyof TInventoryDatabaseDocument>();
|
||||
if (payload.SellCurrency == "SC_RegularCredits") {
|
||||
requiredFields.add("RegularCredits");
|
||||
} else if (payload.SellCurrency == "SC_FusionPoints") {
|
||||
requiredFields.add("FusionPoints");
|
||||
} else if (payload.SellCurrency == "SC_CrewShipFusionPoints") {
|
||||
requiredFields.add("CrewShipFusionPoints");
|
||||
} else {
|
||||
requiredFields.add("MiscItems");
|
||||
}
|
||||
@ -57,6 +64,9 @@ export const sellController: RequestHandler = async (req, res) => {
|
||||
if (payload.Items.Hoverboards) {
|
||||
requiredFields.add(InventorySlot.SPACESUITS);
|
||||
}
|
||||
if (payload.Items.CrewMembers) {
|
||||
requiredFields.add(InventorySlot.CREWMEMBERS);
|
||||
}
|
||||
if (payload.Items.CrewShipWeapons || payload.Items.CrewShipWeaponSkins) {
|
||||
requiredFields.add(InventorySlot.RJ_COMPONENT_AND_ARMAMENTS);
|
||||
requiredFields.add("CrewShipRawSalvage");
|
||||
@ -74,6 +84,8 @@ export const sellController: RequestHandler = async (req, res) => {
|
||||
inventory.RegularCredits += payload.SellPrice;
|
||||
} else if (payload.SellCurrency == "SC_FusionPoints") {
|
||||
addFusionPoints(inventory, payload.SellPrice);
|
||||
} else if (payload.SellCurrency == "SC_CrewShipFusionPoints") {
|
||||
addCrewShipFusionPoints(inventory, payload.SellPrice);
|
||||
} else if (payload.SellCurrency == "SC_PrimeBucks") {
|
||||
addMiscItems(inventory, [
|
||||
{
|
||||
@ -180,6 +192,17 @@ export const sellController: RequestHandler = async (req, res) => {
|
||||
inventory.Drones.pull({ _id: sellItem.String });
|
||||
});
|
||||
}
|
||||
if (payload.Items.KubrowPetPrints) {
|
||||
payload.Items.KubrowPetPrints.forEach(sellItem => {
|
||||
inventory.KubrowPetPrints.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) {
|
||||
payload.Items.CrewShipWeapons.forEach(sellItem => {
|
||||
if (sellItem.String[0] == "/") {
|
||||
@ -274,11 +297,17 @@ export const sellController: RequestHandler = async (req, res) => {
|
||||
]);
|
||||
});
|
||||
}
|
||||
if (payload.Items.FusionTreasures) {
|
||||
payload.Items.FusionTreasures.forEach(sellItem => {
|
||||
addFusionTreasures(inventory, [parseFusionTreasure(sellItem.String, sellItem.Count * -1)]);
|
||||
});
|
||||
}
|
||||
|
||||
await inventory.save();
|
||||
res.json({
|
||||
inventoryChanges: inventoryChanges // "inventoryChanges" for this response instead of the usual "InventoryChanges"
|
||||
});
|
||||
sendWsBroadcastEx({ update_inventory: true }, accountId, parseInt(String(req.query.wsid)));
|
||||
};
|
||||
|
||||
interface ISellRequest {
|
||||
@ -301,8 +330,11 @@ interface ISellRequest {
|
||||
OperatorAmps?: ISellItem[];
|
||||
Hoverboards?: ISellItem[];
|
||||
Drones?: ISellItem[];
|
||||
KubrowPetPrints?: ISellItem[];
|
||||
CrewMembers?: ISellItem[];
|
||||
CrewShipWeapons?: ISellItem[];
|
||||
CrewShipWeaponSkins?: ISellItem[];
|
||||
FusionTreasures?: ISellItem[];
|
||||
};
|
||||
SellPrice: number;
|
||||
SellCurrency:
|
||||
@ -311,7 +343,8 @@ interface ISellRequest {
|
||||
| "SC_FusionPoints"
|
||||
| "SC_DistillPoints"
|
||||
| "SC_CrewShipFusionPoints"
|
||||
| "SC_Resources";
|
||||
| "SC_Resources"
|
||||
| "somethingelsewemightnotknowabout";
|
||||
buildLabel: string;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getPersonalRooms } from "@/src/services/personalRoomsService";
|
||||
import { TBootLocation } from "@/src/types/shipTypes";
|
||||
import { TBootLocation } from "@/src/types/personalRoomsTypes";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
|
||||
export const setBootLocationController: RequestHandler = async (req, res) => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { IPictureFrameInfo, ISetPlacedDecoInfoRequest } from "@/src/types/shipTypes";
|
||||
import { IPictureFrameInfo, ISetPlacedDecoInfoRequest } from "@/src/types/personalRoomsTypes";
|
||||
import { RequestHandler } from "express";
|
||||
import { handleSetPlacedDecoInfo } from "@/src/services/shipCustomizationsService";
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { setShipCustomizations } from "@/src/services/shipCustomizationsService";
|
||||
import { ISetShipCustomizationsRequest } from "@/src/types/shipTypes";
|
||||
import { ISetShipCustomizationsRequest } from "@/src/types/personalRoomsTypes";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { RequestHandler } from "express";
|
||||
import { getPersonalRooms } from "@/src/services/personalRoomsService";
|
||||
import { IOid } from "@/src/types/commonTypes";
|
||||
import { Types } from "mongoose";
|
||||
import { IFavouriteLoadoutDatabase, TBootLocation } from "@/src/types/shipTypes";
|
||||
import { IFavouriteLoadoutDatabase, TBootLocation } from "@/src/types/personalRoomsTypes";
|
||||
|
||||
export const setShipFavouriteLoadoutController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
|
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/equipmentTypes";
|
||||
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[];
|
||||
}
|
@ -1,20 +1,17 @@
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { IShipDecorationsRequest } from "@/src/types/shipTypes";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { IShipDecorationsRequest, IResetShipDecorationsRequest } from "@/src/types/personalRoomsTypes";
|
||||
import { RequestHandler } from "express";
|
||||
import { handleSetShipDecorations } from "@/src/services/shipCustomizationsService";
|
||||
import { handleResetShipDecorations, handleSetShipDecorations } from "@/src/services/shipCustomizationsService";
|
||||
|
||||
export const shipDecorationsController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const shipDecorationsRequest = JSON.parse(req.body as string) as IShipDecorationsRequest;
|
||||
|
||||
try {
|
||||
if (req.query.reset == "1") {
|
||||
const request = JSON.parse(req.body as string) as IResetShipDecorationsRequest;
|
||||
const response = await handleResetShipDecorations(accountId, request);
|
||||
res.send(response);
|
||||
} else {
|
||||
const shipDecorationsRequest = JSON.parse(req.body as string) as IShipDecorationsRequest;
|
||||
const placedDecoration = await handleSetShipDecorations(accountId, shipDecorationsRequest);
|
||||
res.send(placedDecoration);
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
logger.error(`error in shipDecorationsController: ${error.message}`);
|
||||
res.status(400).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -3,12 +3,14 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { RequestHandler } from "express";
|
||||
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 { Types } from "mongoose";
|
||||
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 { getRandomElement } from "@/src/services/rngService";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
|
||||
interface IStartRecipeRequest {
|
||||
RecipeName: string;
|
||||
@ -42,24 +44,39 @@ export const startRecipeController: RequestHandler = async (req, res) => {
|
||||
|
||||
for (let i = 0; i != recipe.ingredients.length; ++i) {
|
||||
if (startRecipeRequest.Ids[i] && startRecipeRequest.Ids[i][0] != "/") {
|
||||
const category = ExportWeapons[recipe.ingredients[i].ItemType].productCategory;
|
||||
if (category != "LongGuns" && category != "Pistols" && category != "Melee") {
|
||||
throw new Error(`unexpected equipment ingredient type: ${category}`);
|
||||
if (recipe.ingredients[i].ItemType == "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem") {
|
||||
const index = inventory.KubrowPetEggs.findIndex(x => x._id.equals(startRecipeRequest.Ids[i]));
|
||||
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 {
|
||||
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 = {
|
||||
ItemType: recipe.resultType,
|
||||
Suits: "",
|
||||
@ -116,5 +133,5 @@ export const startRecipeController: RequestHandler = async (req, res) => {
|
||||
|
||||
await inventory.save();
|
||||
|
||||
res.json({ RecipeId: toOid(pr._id) });
|
||||
res.json({ RecipeId: toOid(pr._id), InventoryChanges: inventoryChanges });
|
||||
};
|
||||
|
@ -3,7 +3,7 @@ import { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { ExportSyndicates, ISyndicateSacrifice } from "warframe-public-export-plus";
|
||||
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 { toStoreItem } from "@/src/services/itemDataService";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
@ -18,80 +18,83 @@ export const syndicateSacrificeController: RequestHandler = async (request, resp
|
||||
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 = {
|
||||
AffiliationTag: data.AffiliationTag,
|
||||
InventoryChanges: {},
|
||||
Level: data.SacrificeLevel,
|
||||
LevelIncrease: level <= 0 ? 1 : level,
|
||||
LevelIncrease: data.SacrificeLevel < 0 ? 1 : levelIncrease,
|
||||
NewEpisodeReward: false
|
||||
};
|
||||
|
||||
// Process sacrifices and rewards for every level we're reaching
|
||||
const manifest = ExportSyndicates[data.AffiliationTag];
|
||||
let sacrifice: ISyndicateSacrifice | undefined;
|
||||
let reward: string | undefined;
|
||||
if (data.SacrificeLevel == 0) {
|
||||
sacrifice = manifest.initiationSacrifice;
|
||||
reward = manifest.initiationReward;
|
||||
syndicate.Initiated = true;
|
||||
} else {
|
||||
sacrifice = manifest.titles?.find(x => x.level == data.SacrificeLevel)?.sacrifice;
|
||||
}
|
||||
|
||||
if (sacrifice) {
|
||||
res.InventoryChanges = { ...updateCurrency(inventory, sacrifice.credits, false) };
|
||||
|
||||
const miscItemChanges = sacrifice.items.map(x => ({
|
||||
ItemType: x.ItemType,
|
||||
ItemCount: x.ItemCount * -1
|
||||
}));
|
||||
addMiscItems(inventory, miscItemChanges);
|
||||
res.InventoryChanges.MiscItems = miscItemChanges;
|
||||
}
|
||||
|
||||
syndicate.Title ??= 0;
|
||||
syndicate.Title += 1;
|
||||
|
||||
if (reward) {
|
||||
combineInventoryChanges(
|
||||
res.InventoryChanges,
|
||||
(await handleStoreItemAcquisition(reward, inventory)).InventoryChanges
|
||||
);
|
||||
}
|
||||
|
||||
// Quacks like a nightwave syndicate?
|
||||
if (manifest.dailyChallenges) {
|
||||
const title = manifest.titles!.find(x => x.level == syndicate.Title);
|
||||
if (title) {
|
||||
res.NewEpisodeReward = true;
|
||||
let rewardType: string;
|
||||
let rewardCount: number;
|
||||
if (title.storeItemReward) {
|
||||
rewardType = title.storeItemReward;
|
||||
rewardCount = 1;
|
||||
} else {
|
||||
rewardType = toStoreItem(title.reward!.ItemType);
|
||||
rewardCount = title.reward!.ItemCount;
|
||||
for (let level = oldLevel + Math.min(levelIncrease, 1); level <= data.SacrificeLevel; ++level) {
|
||||
let sacrifice: ISyndicateSacrifice | undefined;
|
||||
if (level == 0) {
|
||||
sacrifice = manifest.initiationSacrifice;
|
||||
if (manifest.initiationReward) {
|
||||
combineInventoryChanges(
|
||||
res.InventoryChanges,
|
||||
(await handleStoreItemAcquisition(manifest.initiationReward, inventory)).InventoryChanges
|
||||
);
|
||||
}
|
||||
const rewardInventoryChanges = (await handleStoreItemAcquisition(rewardType, inventory, rewardCount))
|
||||
.InventoryChanges;
|
||||
if (Object.keys(rewardInventoryChanges).length == 0) {
|
||||
logger.debug(`nightwave rank up reward did not seem to get added, giving 50 creds instead`);
|
||||
const nightwaveCredsItemType = manifest.titles![0].reward!.ItemType;
|
||||
rewardInventoryChanges.MiscItems = [{ ItemType: nightwaveCredsItemType, ItemCount: 50 }];
|
||||
addMiscItems(inventory, rewardInventoryChanges.MiscItems);
|
||||
}
|
||||
combineInventoryChanges(res.InventoryChanges, rewardInventoryChanges);
|
||||
syndicate.Initiated = true;
|
||||
} else {
|
||||
sacrifice = manifest.titles?.find(x => x.level == level)?.sacrifice;
|
||||
}
|
||||
} else {
|
||||
if (syndicate.Title > 0 && manifest.favours.find(x => x.rankUpReward && x.requiredLevel == syndicate.Title)) {
|
||||
syndicate.FreeFavorsEarned ??= [];
|
||||
if (!syndicate.FreeFavorsEarned.includes(syndicate.Title)) {
|
||||
syndicate.FreeFavorsEarned.push(syndicate.Title);
|
||||
|
||||
if (sacrifice) {
|
||||
updateCurrency(inventory, sacrifice.credits, false, res.InventoryChanges);
|
||||
|
||||
for (const item of sacrifice.items) {
|
||||
addMiscItem(inventory, item.ItemType, item.ItemCount * -1, res.InventoryChanges);
|
||||
}
|
||||
}
|
||||
|
||||
// Quacks like a nightwave syndicate?
|
||||
if (manifest.dailyChallenges) {
|
||||
const title = manifest.titles!.find(x => x.level == level);
|
||||
if (title) {
|
||||
res.NewEpisodeReward = true;
|
||||
let rewardType: string;
|
||||
let rewardCount: number;
|
||||
if (title.storeItemReward) {
|
||||
rewardType = title.storeItemReward;
|
||||
rewardCount = 1;
|
||||
} else {
|
||||
rewardType = toStoreItem(title.reward!.ItemType);
|
||||
rewardCount = title.reward!.ItemCount;
|
||||
}
|
||||
const rewardInventoryChanges = (await handleStoreItemAcquisition(rewardType, inventory, rewardCount))
|
||||
.InventoryChanges;
|
||||
if (Object.keys(rewardInventoryChanges).length == 0) {
|
||||
logger.debug(`nightwave rank up reward did not seem to get added, giving 50 creds instead`);
|
||||
const nightwaveCredsItemType = manifest.titles![0].reward!.ItemType;
|
||||
addMiscItem(inventory, nightwaveCredsItemType, 50, rewardInventoryChanges);
|
||||
}
|
||||
combineInventoryChanges(res.InventoryChanges, rewardInventoryChanges);
|
||||
}
|
||||
} else {
|
||||
if (level > 0 && manifest.favours.find(x => x.rankUpReward && x.requiredLevel == level)) {
|
||||
syndicate.FreeFavorsEarned ??= [];
|
||||
if (!syndicate.FreeFavorsEarned.includes(level)) {
|
||||
syndicate.FreeFavorsEarned.push(level);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Commit
|
||||
syndicate.Title = data.SacrificeLevel < 0 ? data.SacrificeLevel + 1 : data.SacrificeLevel;
|
||||
await inventory.save();
|
||||
|
||||
response.json(res);
|
||||
|
@ -5,8 +5,8 @@ import { IMiscItem, InventorySlot } from "@/src/types/inventoryTypes/inventoryTy
|
||||
import { IOid } from "@/src/types/commonTypes";
|
||||
import { ExportSyndicates, ExportWeapons } from "warframe-public-export-plus";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { IAffiliationMods, IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { EquipmentFeatures } from "@/src/types/equipmentTypes";
|
||||
|
||||
export const syndicateStandingBonusController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
@ -54,13 +54,14 @@ export const syndicateStandingBonusController: RequestHandler = async (req, res)
|
||||
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();
|
||||
|
||||
res.json({
|
||||
InventoryChanges: inventoryChanges,
|
||||
AffiliationMods: [affiliationMod]
|
||||
AffiliationMods: affiliationMods
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -35,6 +35,17 @@ const trainingResultController: RequestHandler = async (req, res): Promise<void>
|
||||
inventory.PlayerLevel += 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, [
|
||||
{
|
||||
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/equipmentTypes";
|
||||
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,36 +1,63 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getAccountForRequest } from "@/src/services/loginService";
|
||||
import { addChallenges, getInventory } from "@/src/services/inventoryService";
|
||||
import { addCalendarProgress, addChallenges, getInventory } from "@/src/services/inventoryService";
|
||||
import { IChallengeProgress, ISeasonChallenge } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { IAffiliationMods } from "@/src/types/purchaseTypes";
|
||||
import { getEntriesUnsafe } from "@/src/utils/ts-utils";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
|
||||
export const updateChallengeProgressController: RequestHandler = async (req, res) => {
|
||||
const challenges = getJSONfromString<IUpdateChallengeProgressRequest>(String(req.body));
|
||||
const account = await getAccountForRequest(req);
|
||||
logger.debug(`challenge report:`, challenges);
|
||||
|
||||
const inventory = await getInventory(
|
||||
account._id.toString(),
|
||||
"ChallengeProgress SeasonChallengeHistory Affiliations"
|
||||
"ChallengesFixVersion ChallengeProgress SeasonChallengeHistory Affiliations CalendarProgress"
|
||||
);
|
||||
let affiliationMods: IAffiliationMods[] = [];
|
||||
if (challenges.ChallengeProgress) {
|
||||
affiliationMods = addChallenges(
|
||||
affiliationMods = await addChallenges(
|
||||
account,
|
||||
inventory,
|
||||
challenges.ChallengeProgress,
|
||||
challenges.SeasonChallengeCompletions
|
||||
);
|
||||
}
|
||||
if (challenges.SeasonChallengeHistory) {
|
||||
challenges.SeasonChallengeHistory.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 });
|
||||
}
|
||||
});
|
||||
for (const [key, value] of getEntriesUnsafe(challenges)) {
|
||||
if (value === undefined) {
|
||||
logger.error(`Challenge progress update key ${key} has no value`);
|
||||
continue;
|
||||
}
|
||||
switch (key) {
|
||||
case "ChallengesFixVersion":
|
||||
inventory.ChallengesFixVersion = value;
|
||||
break;
|
||||
|
||||
case "SeasonChallengeHistory":
|
||||
value.forEach(({ challenge, id }) => {
|
||||
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();
|
||||
|
||||
@ -40,7 +67,11 @@ export const updateChallengeProgressController: RequestHandler = async (req, res
|
||||
};
|
||||
|
||||
interface IUpdateChallengeProgressRequest {
|
||||
ChallengePTS?: number;
|
||||
ChallengesFixVersion?: number;
|
||||
ChallengeProgress?: IChallengeProgress[];
|
||||
SeasonChallengeHistory?: ISeasonChallenge[];
|
||||
SeasonChallengeCompletions?: ISeasonChallenge[];
|
||||
CalendarProgress?: { challenge: string }[];
|
||||
crossPlaySetting?: string;
|
||||
}
|
||||
|
@ -1,11 +1,6 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { IUpgradesRequest } from "@/src/types/requestTypes";
|
||||
import {
|
||||
ArtifactPolarity,
|
||||
IEquipmentDatabase,
|
||||
EquipmentFeatures,
|
||||
IAbilityOverride
|
||||
} from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { ArtifactPolarity, IAbilityOverride } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { IInventoryClient, IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { addMiscItems, addRecipes, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||
@ -13,6 +8,8 @@ import { getRecipeByResult } from "@/src/services/itemDataService";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { addInfestedFoundryXP, applyCheatsToInfestedFoundry } from "@/src/services/infestedFoundryService";
|
||||
import { config } from "@/src/services/configService";
|
||||
import { sendWsBroadcastTo } from "@/src/services/wsService";
|
||||
import { EquipmentFeatures, IEquipmentDatabase } from "@/src/types/equipmentTypes";
|
||||
|
||||
export const upgradesController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
@ -120,6 +117,7 @@ export const upgradesController: RequestHandler = async (req, res) => {
|
||||
setSlotPolarity(item, operation.PolarizeSlot, operation.PolarizeValue);
|
||||
item.Polarized ??= 0;
|
||||
item.Polarized += 1;
|
||||
sendWsBroadcastTo(accountId, { update_inventory: true }); // webui may need to to re-add "max rank" button
|
||||
break;
|
||||
}
|
||||
case "/Lotus/Types/Items/MiscItems/ModSlotUnlocker": {
|
||||
|
33
src/controllers/custom/abilityOverrideController.ts
Normal file
33
src/controllers/custom/abilityOverrideController.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const abilityOverrideController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const request = req.body as IAbilityOverrideRequest;
|
||||
if (request.category === "Suits") {
|
||||
const inventory = await getInventory(accountId, request.category);
|
||||
const item = inventory[request.category].id(request.oid);
|
||||
if (item) {
|
||||
if (request.action == "set") {
|
||||
item.Configs[request.configIndex].AbilityOverride = request.AbilityOverride;
|
||||
} else {
|
||||
item.Configs[request.configIndex].AbilityOverride = undefined;
|
||||
}
|
||||
await inventory.save();
|
||||
}
|
||||
}
|
||||
res.end();
|
||||
};
|
||||
|
||||
interface IAbilityOverrideRequest {
|
||||
category: TEquipmentKey;
|
||||
oid: string;
|
||||
action: "set" | "remove";
|
||||
configIndex: number;
|
||||
AbilityOverride: {
|
||||
Ability: string;
|
||||
Index: number;
|
||||
};
|
||||
}
|
@ -7,7 +7,7 @@ export const addItemsController: RequestHandler = async (req, res) => {
|
||||
const requests = req.body as IAddItemRequest[];
|
||||
const inventory = await getInventory(accountId);
|
||||
for (const request of requests) {
|
||||
await addItem(inventory, request.ItemType, request.ItemCount, true, undefined, undefined, true);
|
||||
await addItem(inventory, request.ItemType, request.ItemCount, true, undefined, request.Fingerprint, true);
|
||||
}
|
||||
await inventory.save();
|
||||
res.end();
|
||||
@ -16,4 +16,5 @@ export const addItemsController: RequestHandler = async (req, res) => {
|
||||
interface IAddItemRequest {
|
||||
ItemType: string;
|
||||
ItemCount: number;
|
||||
Fingerprint?: 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,7 +1,7 @@
|
||||
import { applyClientEquipmentUpdates, getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { IOid } from "@/src/types/commonTypes";
|
||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { IEquipmentClient } from "@/src/types/equipmentTypes";
|
||||
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { RequestHandler } from "express";
|
||||
import { ExportMisc } from "warframe-public-export-plus";
|
||||
|
65
src/controllers/custom/changeModularPartsController.ts
Normal file
65
src/controllers/custom/changeModularPartsController.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const changeModularPartsController: 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) {
|
||||
item.ModularParts = request.modularParts;
|
||||
|
||||
request.modularParts.forEach(part => {
|
||||
const categoryMap = mapping[part];
|
||||
if (categoryMap && categoryMap[request.category]) {
|
||||
item.ItemType = categoryMap[request.category]!;
|
||||
}
|
||||
});
|
||||
await inventory.save();
|
||||
}
|
||||
res.end();
|
||||
};
|
||||
|
||||
interface IUpdateFingerPrintRequest {
|
||||
category: TEquipmentKey;
|
||||
oid: string;
|
||||
modularParts: string[];
|
||||
}
|
||||
|
||||
const mapping: Partial<Record<string, Partial<Record<TEquipmentKey, string>>>> = {
|
||||
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelAPart": {
|
||||
LongGuns: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun",
|
||||
Pistols: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun"
|
||||
},
|
||||
"/Lotus/Weapons/Infested/Pistols/InfKitGun/Barrels/InfBarrelEgg/InfModularBarrelEggPart": {
|
||||
LongGuns: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun",
|
||||
Pistols: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun"
|
||||
},
|
||||
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelBPart": {
|
||||
LongGuns: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
|
||||
Pistols: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary"
|
||||
},
|
||||
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelCPart": {
|
||||
LongGuns: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
|
||||
Pistols: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary"
|
||||
},
|
||||
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelDPart": {
|
||||
LongGuns: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam",
|
||||
Pistols: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam"
|
||||
},
|
||||
"/Lotus/Weapons/Infested/Pistols/InfKitGun/Barrels/InfBarrelBeam/InfModularBarrelBeamPart": {
|
||||
LongGuns: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam",
|
||||
Pistols: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam"
|
||||
},
|
||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA": {
|
||||
MoaPets: "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetAPowerSuit"
|
||||
},
|
||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB": {
|
||||
MoaPets: "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetBPowerSuit"
|
||||
},
|
||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC": {
|
||||
MoaPets: "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetCPowerSuit"
|
||||
}
|
||||
};
|
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();
|
||||
};
|
47
src/controllers/custom/configController.ts
Normal file
47
src/controllers/custom/configController.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { config, syncConfigWithDatabase } from "@/src/services/configService";
|
||||
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
|
||||
import { saveConfig } from "@/src/services/configWriterService";
|
||||
import { sendWsBroadcastEx } from "@/src/services/wsService";
|
||||
|
||||
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;
|
||||
}
|
||||
sendWsBroadcastEx({ config_reloaded: true }, undefined, parseInt(String(req.query.wsid)));
|
||||
syncConfigWithDatabase();
|
||||
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) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
obj[arr[0]] ??= {} as never;
|
||||
obj = obj[arr[0]];
|
||||
arr.splice(0, 1);
|
||||
}
|
||||
return [obj, arr[0]];
|
||||
};
|
@ -11,6 +11,7 @@ import { GuildMember } from "@/src/models/guildModel";
|
||||
import { Leaderboard } from "@/src/models/leaderboardModel";
|
||||
import { deleteGuild } from "@/src/services/guildService";
|
||||
import { Friendship } from "@/src/models/friendModel";
|
||||
import { sendWsBroadcastTo } from "@/src/services/wsService";
|
||||
|
||||
export const deleteAccountController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
@ -36,5 +37,8 @@ export const deleteAccountController: RequestHandler = async (req, res) => {
|
||||
Ship.deleteMany({ ShipOwnerId: accountId }),
|
||||
Stats.deleteOne({ accountOwnerId: accountId })
|
||||
]);
|
||||
|
||||
sendWsBroadcastTo(accountId, { logged_out: true });
|
||||
|
||||
res.end();
|
||||
};
|
||||
|
@ -0,0 +1,34 @@
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
const DEFAULT_UPGRADE_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
||||
|
||||
export const editSuitInvigorationUpgradeController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const { oid, data } = req.body as {
|
||||
oid: string;
|
||||
data?: {
|
||||
DefensiveUpgrade: string;
|
||||
OffensiveUpgrade: string;
|
||||
UpgradesExpiry?: number;
|
||||
};
|
||||
};
|
||||
const inventory = await getInventory(accountId);
|
||||
const suit = inventory.Suits.id(oid)!;
|
||||
if (data) {
|
||||
suit.DefensiveUpgrade = data.DefensiveUpgrade;
|
||||
suit.OffensiveUpgrade = data.OffensiveUpgrade;
|
||||
if (data.UpgradesExpiry) {
|
||||
suit.UpgradesExpiry = new Date(data.UpgradesExpiry);
|
||||
} else {
|
||||
suit.UpgradesExpiry = new Date(Date.now() + DEFAULT_UPGRADE_EXPIRY_MS);
|
||||
}
|
||||
} else {
|
||||
suit.DefensiveUpgrade = undefined;
|
||||
suit.OffensiveUpgrade = undefined;
|
||||
suit.UpgradesExpiry = undefined;
|
||||
}
|
||||
await inventory.save();
|
||||
res.end();
|
||||
};
|
@ -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 };
|
@ -1,8 +1,10 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { getDict, getItemName, getString } from "@/src/services/itemDataService";
|
||||
import {
|
||||
ExportAbilities,
|
||||
ExportArcanes,
|
||||
ExportAvionics,
|
||||
ExportBoosters,
|
||||
ExportCustoms,
|
||||
ExportDrones,
|
||||
ExportGear,
|
||||
@ -19,12 +21,13 @@ import {
|
||||
ExportWeapons,
|
||||
TRelicQuality
|
||||
} from "warframe-public-export-plus";
|
||||
import archonCrystalUpgrades from "@/static/fixed_responses/webuiArchonCrystalUpgrades.json";
|
||||
import allIncarnons from "@/static/fixed_responses/allIncarnonList.json";
|
||||
import varzia from "@/static/fixed_responses/worldState/varzia.json";
|
||||
|
||||
interface ListedItem {
|
||||
uniqueName: string;
|
||||
name: string;
|
||||
subtype?: string;
|
||||
fusionLimit?: number;
|
||||
exalted?: string[];
|
||||
badReason?: "starter" | "frivolous" | "notraw";
|
||||
@ -34,7 +37,6 @@ interface ListedItem {
|
||||
}
|
||||
|
||||
interface ItemLists {
|
||||
archonCrystalUpgrades: Record<string, string>;
|
||||
uniqueLevelCaps: Record<string, number>;
|
||||
Suits: ListedItem[];
|
||||
LongGuns: ListedItem[];
|
||||
@ -54,6 +56,10 @@ interface ItemLists {
|
||||
KubrowPets: ListedItem[];
|
||||
EvolutionProgress: ListedItem[];
|
||||
mods: ListedItem[];
|
||||
Boosters: ListedItem[];
|
||||
VarziaOffers: ListedItem[];
|
||||
Abilities: ListedItem[];
|
||||
//circuitGameModes: ListedItem[];
|
||||
}
|
||||
|
||||
const relicQualitySuffixes: Record<TRelicQuality, string> = {
|
||||
@ -63,10 +69,13 @@ const relicQualitySuffixes: Record<TRelicQuality, string> = {
|
||||
VPQ_PLATINUM: " [Exceptional]"
|
||||
};
|
||||
|
||||
/*const toTitleCase = (str: string): string => {
|
||||
return str.replace(/[^\s-]+/g, word => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase());
|
||||
};*/
|
||||
|
||||
const getItemListsController: RequestHandler = (req, response) => {
|
||||
const lang = getDict(typeof req.query.lang == "string" ? req.query.lang : "en");
|
||||
const res: ItemLists = {
|
||||
archonCrystalUpgrades,
|
||||
uniqueLevelCaps: ExportMisc.uniqueLevelCaps,
|
||||
Suits: [],
|
||||
LongGuns: [],
|
||||
@ -85,7 +94,40 @@ const getItemListsController: RequestHandler = (req, response) => {
|
||||
QuestKeys: [],
|
||||
KubrowPets: [],
|
||||
EvolutionProgress: [],
|
||||
mods: []
|
||||
mods: [],
|
||||
Boosters: [],
|
||||
VarziaOffers: [],
|
||||
Abilities: []
|
||||
/*circuitGameModes: [
|
||||
{
|
||||
uniqueName: "Survival",
|
||||
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Survival", lang))
|
||||
},
|
||||
{
|
||||
uniqueName: "VoidFlood",
|
||||
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Corruption", lang))
|
||||
},
|
||||
{
|
||||
uniqueName: "Excavation",
|
||||
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Excavation", lang))
|
||||
},
|
||||
{
|
||||
uniqueName: "Defense",
|
||||
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Defense", lang))
|
||||
},
|
||||
{
|
||||
uniqueName: "Exterminate",
|
||||
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Exterminate", lang))
|
||||
},
|
||||
{
|
||||
uniqueName: "Assassination",
|
||||
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Assassination", lang))
|
||||
},
|
||||
{
|
||||
uniqueName: "Alchemy",
|
||||
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Alchemy", lang))
|
||||
}
|
||||
]*/
|
||||
};
|
||||
for (const [uniqueName, item] of Object.entries(ExportWarframes)) {
|
||||
res[item.productCategory].push({
|
||||
@ -93,6 +135,12 @@ const getItemListsController: RequestHandler = (req, response) => {
|
||||
name: getString(item.name, lang),
|
||||
exalted: item.exalted
|
||||
});
|
||||
item.abilities.forEach(ability => {
|
||||
res.Abilities.push({
|
||||
uniqueName: ability.uniqueName,
|
||||
name: getString(ability.name || uniqueName, lang)
|
||||
});
|
||||
});
|
||||
}
|
||||
for (const [uniqueName, item] of Object.entries(ExportSentinels)) {
|
||||
if (item.productCategory == "Sentinels" || item.productCategory == "KubrowPets") {
|
||||
@ -105,18 +153,21 @@ const getItemListsController: RequestHandler = (req, response) => {
|
||||
}
|
||||
for (const [uniqueName, item] of Object.entries(ExportWeapons)) {
|
||||
if (item.partType) {
|
||||
if (!uniqueName.startsWith("/Lotus/Types/Items/Deimos/")) {
|
||||
res.ModularParts.push({
|
||||
uniqueName,
|
||||
name: getString(item.name, lang),
|
||||
partType: item.partType
|
||||
});
|
||||
}
|
||||
if (uniqueName.split("/")[5] != "SentTrainingAmplifier") {
|
||||
res.miscitems.push({
|
||||
uniqueName: uniqueName,
|
||||
name: getString(item.name, lang)
|
||||
});
|
||||
if (!uniqueName.split("/")[7]?.startsWith("PvPVariant")) {
|
||||
// not a pvp variant
|
||||
if (!uniqueName.startsWith("/Lotus/Types/Items/Deimos/")) {
|
||||
res.ModularParts.push({
|
||||
uniqueName,
|
||||
name: getString(item.name, lang),
|
||||
partType: item.partType
|
||||
});
|
||||
}
|
||||
if (uniqueName.split("/")[5] != "SentTrainingAmplifier") {
|
||||
res.miscitems.push({
|
||||
uniqueName: uniqueName,
|
||||
name: getString(item.name, lang)
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (item.totalDamage !== 0) {
|
||||
if (
|
||||
@ -175,7 +226,8 @@ const getItemListsController: RequestHandler = (req, response) => {
|
||||
) {
|
||||
res.miscitems.push({
|
||||
uniqueName: uniqueName,
|
||||
name: name
|
||||
name: name,
|
||||
subtype: "Resource"
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -193,7 +245,8 @@ const getItemListsController: RequestHandler = (req, response) => {
|
||||
for (const [uniqueName, item] of Object.entries(ExportGear)) {
|
||||
res.miscitems.push({
|
||||
uniqueName: uniqueName,
|
||||
name: getString(item.name, lang)
|
||||
name: getString(item.name, lang),
|
||||
subtype: "Gear"
|
||||
});
|
||||
}
|
||||
const recipeNameTemplate = getString("/Lotus/Language/Items/BlueprintAndItem", lang);
|
||||
@ -293,6 +346,27 @@ const getItemListsController: RequestHandler = (req, response) => {
|
||||
});
|
||||
}
|
||||
|
||||
for (const item of Object.values(ExportBoosters)) {
|
||||
res.Boosters.push({
|
||||
uniqueName: item.typeName,
|
||||
name: getString(item.name, lang)
|
||||
});
|
||||
}
|
||||
|
||||
for (const item of Object.values(varzia.primeDualPacks)) {
|
||||
res.VarziaOffers.push({
|
||||
uniqueName: item.ItemType,
|
||||
name: getString(getItemName(item.ItemType) || "", lang)
|
||||
});
|
||||
}
|
||||
|
||||
for (const [uniqueName, ability] of Object.entries(ExportAbilities)) {
|
||||
res.Abilities.push({
|
||||
uniqueName,
|
||||
name: getString(ability.name || uniqueName, lang)
|
||||
});
|
||||
}
|
||||
|
||||
response.json(res);
|
||||
};
|
||||
|
||||
|
@ -128,7 +128,7 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
|
||||
await completeQuest(inventory, questKey.ItemType);
|
||||
} else {
|
||||
const progress = {
|
||||
c: questManifest.chainStages![currentStage].key ? -1 : 0,
|
||||
c: 0,
|
||||
i: false,
|
||||
m: false,
|
||||
b: []
|
||||
|
@ -12,6 +12,7 @@ export const popArchonCrystalUpgradeController: RequestHandler = async (req, res
|
||||
);
|
||||
await inventory.save();
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
res.status(400).end();
|
||||
};
|
||||
|
@ -15,6 +15,7 @@ export const pushArchonCrystalUpgradeController: RequestHandler = async (req, re
|
||||
}
|
||||
await inventory.save();
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
}
|
||||
res.status(400).end();
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { getAccountForRequest, isAdministrator, isNameTaken } from "@/src/services/loginService";
|
||||
import { config } from "@/src/services/configService";
|
||||
import { saveConfig } from "@/src/services/configWatcherService";
|
||||
import { saveConfig } from "@/src/services/configWriterService";
|
||||
|
||||
export const renameAccountController: RequestHandler = async (req, res) => {
|
||||
const account = await getAccountForRequest(req);
|
||||
|
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;
|
||||
}[];
|
||||
};
|
||||
}
|
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