Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
af258d1c7a |
23
.eslintrc
23
.eslintrc
@ -11,20 +11,21 @@
|
|||||||
"node": true
|
"node": true
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"@typescript-eslint/explicit-function-return-type": "error",
|
"@typescript-eslint/explicit-function-return-type": "warn",
|
||||||
"@typescript-eslint/restrict-template-expressions": "error",
|
"@typescript-eslint/restrict-template-expressions": "warn",
|
||||||
"@typescript-eslint/restrict-plus-operands": "error",
|
"@typescript-eslint/restrict-plus-operands": "warn",
|
||||||
"@typescript-eslint/no-unsafe-member-access": "error",
|
"@typescript-eslint/no-unsafe-member-access": "warn",
|
||||||
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "caughtErrors": "none" }],
|
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
|
||||||
|
"@typescript-eslint/no-misused-promises": "warn",
|
||||||
"@typescript-eslint/no-unsafe-argument": "error",
|
"@typescript-eslint/no-unsafe-argument": "error",
|
||||||
"@typescript-eslint/no-unsafe-call": "error",
|
"@typescript-eslint/no-unsafe-call": "warn",
|
||||||
"@typescript-eslint/no-unsafe-assignment": "error",
|
"@typescript-eslint/no-unsafe-assignment": "warn",
|
||||||
"@typescript-eslint/no-explicit-any": "error",
|
"@typescript-eslint/no-explicit-any": "warn",
|
||||||
"no-loss-of-precision": "error",
|
"@typescript-eslint/no-loss-of-precision": "warn",
|
||||||
"@typescript-eslint/no-unnecessary-condition": "error",
|
"@typescript-eslint/no-unnecessary-condition": "warn",
|
||||||
"@typescript-eslint/no-base-to-string": "off",
|
|
||||||
"no-case-declarations": "error",
|
"no-case-declarations": "error",
|
||||||
"prettier/prettier": "error",
|
"prettier/prettier": "error",
|
||||||
|
"@typescript-eslint/semi": "error",
|
||||||
"no-mixed-spaces-and-tabs": "error",
|
"no-mixed-spaces-and-tabs": "error",
|
||||||
"require-await": "off",
|
"require-await": "off",
|
||||||
"@typescript-eslint/require-await": "error"
|
"@typescript-eslint/require-await": "error"
|
||||||
|
2
.gitattributes
vendored
2
.gitattributes
vendored
@ -1,4 +1,4 @@
|
|||||||
# Auto detect text files and perform LF normalization
|
# Auto detect text files and perform LF normalization
|
||||||
* text=auto eol=lf
|
* text=auto
|
||||||
|
|
||||||
static/webui/libs/ linguist-vendored
|
static/webui/libs/ linguist-vendored
|
||||||
|
22
.github/workflows/build.yml
vendored
22
.github/workflows/build.yml
vendored
@ -1,29 +1,21 @@
|
|||||||
name: Build
|
name: Build
|
||||||
on:
|
on:
|
||||||
push:
|
push: {}
|
||||||
branches: ["main"]
|
|
||||||
pull_request: {}
|
pull_request: {}
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
version: [18, 20, 22]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4.1.2
|
uses: actions/checkout@v4.1.2
|
||||||
- name: Setup Node.js environment
|
- name: Setup Node.js environment
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.2
|
||||||
with:
|
with:
|
||||||
node-version: ">=20.6.0"
|
node-version: ${{ matrix.version }}
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: cp config.json.example config.json
|
- run: cp config.json.example config.json
|
||||||
- run: npm run verify
|
- run: npm run build
|
||||||
- run: npm run lint:ci
|
- run: npm run lint
|
||||||
- run: npm run prettier
|
|
||||||
- run: npm run update-translations
|
|
||||||
- name: Fail if there are uncommitted changes
|
|
||||||
run: |
|
|
||||||
if [[ -n "$(git status --porcelain)" ]]; then
|
|
||||||
echo "Uncommitted changes detected:"
|
|
||||||
git status
|
|
||||||
git --no-pager diff
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
25
.github/workflows/docker.yml
vendored
25
.github/workflows/docker.yml
vendored
@ -4,9 +4,9 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
jobs:
|
jobs:
|
||||||
docker-amd64:
|
docker:
|
||||||
if: github.repository == 'OpenWF/SpaceNinjaServer'
|
if: github.repository == 'OpenWF/SpaceNinjaServer'
|
||||||
runs-on: amd64
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Docker buildx
|
- name: Set up Docker buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
@ -18,27 +18,8 @@ jobs:
|
|||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
openwf/spaceninjaserver:latest
|
openwf/spaceninjaserver:latest
|
||||||
openwf/spaceninjaserver:${{ github.sha }}
|
openwf/spaceninjaserver:${{ github.sha }}
|
||||||
docker-arm64:
|
|
||||||
if: github.repository == 'OpenWF/SpaceNinjaServer'
|
|
||||||
runs-on: arm64
|
|
||||||
steps:
|
|
||||||
- name: Set up Docker buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
- name: Log in to container registry
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: openwf
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
platforms: linux/arm64
|
|
||||||
push: true
|
|
||||||
tags: |
|
|
||||||
openwf/spaceninjaserver:latest-arm64
|
|
||||||
openwf/spaceninjaserver:${{ github.sha }}-arm64
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
src/routes/api.ts
|
|
||||||
static/webui/libs/
|
static/webui/libs/
|
||||||
*.html
|
*.html
|
||||||
*.md
|
*.md
|
||||||
|
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"recommendations": ["dbaeumer.vscode-eslint"]
|
|
||||||
}
|
|
3
.vscode/launch.json
vendored
3
.vscode/launch.json
vendored
@ -8,7 +8,8 @@
|
|||||||
"type": "node",
|
"type": "node",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Debug and Watch",
|
"name": "Debug and Watch",
|
||||||
"args": ["${workspaceFolder}/scripts/dev.js"],
|
"runtimeArgs": ["-r", "tsconfig-paths/register", "-r", "ts-node/register", "--watch-path", "src"],
|
||||||
|
"args": ["${workspaceFolder}/src/index.ts"],
|
||||||
"console": "integratedTerminal"
|
"console": "integratedTerminal"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
17
AGENTS.md
17
AGENTS.md
@ -1,17 +0,0 @@
|
|||||||
## 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.
|
|
27
Dockerfile
27
Dockerfile
@ -1,11 +1,28 @@
|
|||||||
FROM node:24-alpine3.21
|
FROM node:18-alpine3.19
|
||||||
|
|
||||||
RUN apk add --no-cache bash jq
|
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_STORY_MODE_CHOICE=true
|
||||||
|
ENV APP_SKIP_TUTORIAL=true
|
||||||
|
ENV APP_SKIP_ALL_DIALOGUE=true
|
||||||
|
ENV APP_UNLOCK_ALL_SCANS=true
|
||||||
|
ENV APP_UNLOCK_ALL_MISSIONS=true
|
||||||
|
ENV APP_UNLOCK_ALL_QUESTS=true
|
||||||
|
ENV APP_COMPLETE_ALL_QUESTS=true
|
||||||
|
ENV APP_INFINITE_RESOURCES=true
|
||||||
|
ENV APP_UNLOCK_ALL_SHIP_FEATURES=true
|
||||||
|
ENV APP_UNLOCK_ALL_SHIP_DECORATIONS=true
|
||||||
|
ENV APP_UNLOCK_ALL_FLAVOUR_ITEMS=true
|
||||||
|
ENV APP_UNLOCK_ALL_SKINS=true
|
||||||
|
ENV APP_UNIVERSAL_POLARITY_EVERYWHERE=true
|
||||||
|
ENV APP_SPOOF_MASTERY_RANK=-1
|
||||||
|
|
||||||
|
RUN apk add --no-cache bash sed wget jq
|
||||||
|
|
||||||
COPY . /app
|
COPY . /app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN npm i --omit=dev
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
||||||
|
24
README.md
24
README.md
@ -10,29 +10,5 @@ To get an idea of what functionality you can expect to be missing [have a look t
|
|||||||
|
|
||||||
## config.json
|
## config.json
|
||||||
|
|
||||||
SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [config.json.example](config.json.example), which has most cheats disabled.
|
|
||||||
|
|
||||||
- `logger.level` can be `fatal`, `error`, `warn`, `info`, `http`, `debug`, or `trace`.
|
- `logger.level` can be `fatal`, `error`, `warn`, `info`, `http`, `debug`, or `trace`.
|
||||||
- `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`.
|
- `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`.
|
||||||
- `worldState.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`.
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
@echo off
|
@echo off
|
||||||
|
|
||||||
echo Updating SpaceNinjaServer...
|
echo Updating SpaceNinjaServer...
|
||||||
|
git config remote.origin.url https://openwf.io/SpaceNinjaServer.git
|
||||||
git fetch --prune
|
git fetch --prune
|
||||||
git stash
|
git reset --hard origin/main
|
||||||
git checkout -f origin/main
|
|
||||||
|
|
||||||
if exist static\data\0\ (
|
if exist static\data\0\ (
|
||||||
echo Updating stripped assets...
|
echo Updating stripped assets...
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
#!/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,18 +13,12 @@
|
|||||||
"skipTutorial": false,
|
"skipTutorial": false,
|
||||||
"skipAllDialogue": false,
|
"skipAllDialogue": false,
|
||||||
"unlockAllScans": false,
|
"unlockAllScans": false,
|
||||||
|
"unlockAllMissions": false,
|
||||||
"infiniteCredits": false,
|
"infiniteCredits": false,
|
||||||
"infinitePlatinum": false,
|
"infinitePlatinum": false,
|
||||||
"infiniteEndo": false,
|
"infiniteEndo": false,
|
||||||
"infiniteRegalAya": false,
|
"infiniteRegalAya": false,
|
||||||
"infiniteHelminthMaterials": false,
|
"infiniteHelminthMaterials": false,
|
||||||
"claimingBlueprintRefundsIngredients": false,
|
|
||||||
"dontSubtractPurchaseCreditCost": false,
|
|
||||||
"dontSubtractPurchasePlatinumCost": false,
|
|
||||||
"dontSubtractPurchaseItemCost": false,
|
|
||||||
"dontSubtractPurchaseStandingCost": false,
|
|
||||||
"dontSubtractVoidTraces": false,
|
|
||||||
"dontSubtractConsumables": false,
|
|
||||||
"unlockAllShipFeatures": false,
|
"unlockAllShipFeatures": false,
|
||||||
"unlockAllShipDecorations": false,
|
"unlockAllShipDecorations": false,
|
||||||
"unlockAllFlavourItems": false,
|
"unlockAllFlavourItems": false,
|
||||||
@ -35,52 +29,19 @@
|
|||||||
"unlockExilusEverywhere": false,
|
"unlockExilusEverywhere": false,
|
||||||
"unlockArcanesEverywhere": false,
|
"unlockArcanesEverywhere": false,
|
||||||
"noDailyStandingLimits": false,
|
"noDailyStandingLimits": false,
|
||||||
"noDailyFocusLimit": false,
|
|
||||||
"noArgonCrystalDecay": 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,
|
"instantResourceExtractorDrones": false,
|
||||||
"noResourceExtractorDronesDamage": false,
|
|
||||||
"skipClanKeyCrafting": false,
|
|
||||||
"noDojoRoomBuildStage": false,
|
"noDojoRoomBuildStage": false,
|
||||||
"noDecoBuildStage": false,
|
|
||||||
"fastDojoRoomDestruction": false,
|
"fastDojoRoomDestruction": false,
|
||||||
"noDojoResearchCosts": false,
|
"noDojoResearchCosts": false,
|
||||||
"noDojoResearchTime": false,
|
"noDojoResearchTime": false,
|
||||||
"fastClanAscension": false,
|
"fastClanAscension": false,
|
||||||
"missionsCanGiveAllRelics": false,
|
|
||||||
"unlockAllSimarisResearchEntries": false,
|
|
||||||
"disableDailyTribute": false,
|
|
||||||
"spoofMasteryRank": -1,
|
"spoofMasteryRank": -1,
|
||||||
"relicRewardItemCountMultiplier": 1,
|
"events": {
|
||||||
"nightwaveStandingMultiplier": 1,
|
|
||||||
"unfaithfulBugFixes": {
|
|
||||||
"ignore1999LastRegionPlayed": false,
|
|
||||||
"fixXtraCheeseTimer": false
|
|
||||||
},
|
|
||||||
"worldState": {
|
|
||||||
"creditBoost": false,
|
"creditBoost": false,
|
||||||
"affinityBoost": false,
|
"affinityBoost": false,
|
||||||
"resourceBoost": false,
|
"resourceBoost": false,
|
||||||
"starDays": true,
|
"starDays": true
|
||||||
"galleonOfGhouls": 0,
|
|
||||||
"eidolonOverride": "",
|
|
||||||
"vallisOverride": "",
|
|
||||||
"duviriOverride": "",
|
|
||||||
"nightwaveOverride": "",
|
|
||||||
"allTheFissures": "",
|
|
||||||
"circuitGameModes": null,
|
|
||||||
"darvoStockMultiplier": 1
|
|
||||||
},
|
|
||||||
"dev": {
|
|
||||||
"keepVendorsExpired": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,37 @@
|
|||||||
services:
|
services:
|
||||||
spaceninjaserver:
|
spaceninjaserver:
|
||||||
# The image to use. If you have an ARM CPU, replace 'latest' with 'latest-arm64'.
|
# build: .
|
||||||
image: openwf/spaceninjaserver:latest
|
image: openwf/spaceninjaserver:latest
|
||||||
|
environment:
|
||||||
|
APP_MONGODB_URL: mongodb://openwfagent:spaceninjaserver@mongodb:27017/
|
||||||
|
|
||||||
|
# Following environment variables are set to default image values.
|
||||||
|
# Uncomment to edit.
|
||||||
|
|
||||||
|
# APP_MY_ADDRESS: localhost
|
||||||
|
# APP_HTTP_PORT: 80
|
||||||
|
# APP_HTTPS_PORT: 443
|
||||||
|
# APP_AUTO_CREATE_ACCOUNT: true
|
||||||
|
# APP_SKIP_STORY_MODE_CHOICE: true
|
||||||
|
# APP_SKIP_TUTORIAL: true
|
||||||
|
# APP_SKIP_ALL_DIALOGUE: true
|
||||||
|
# APP_UNLOCK_ALL_SCANS: true
|
||||||
|
# APP_UNLOCK_ALL_MISSIONS: true
|
||||||
|
# APP_UNLOCK_ALL_QUESTS: true
|
||||||
|
# APP_COMPLETE_ALL_QUESTS: true
|
||||||
|
# APP_INFINITE_RESOURCES: true
|
||||||
|
# APP_UNLOCK_ALL_SHIP_FEATURES: true
|
||||||
|
# APP_UNLOCK_ALL_SHIP_DECORATIONS: true
|
||||||
|
# APP_UNLOCK_ALL_FLAVOUR_ITEMS: true
|
||||||
|
# APP_UNLOCK_ALL_SKINS: true
|
||||||
|
# APP_UNIVERSAL_POLARITY_EVERYWHERE: true
|
||||||
|
# APP_SPOOF_MASTERY_RANK: -1
|
||||||
volumes:
|
volumes:
|
||||||
- ./docker-data/conf:/app/conf
|
- ./docker-data/static:/app/static/data
|
||||||
- ./docker-data/static-data:/app/static/data
|
|
||||||
- ./docker-data/logs:/app/logs
|
- ./docker-data/logs:/app/logs
|
||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 80:80
|
||||||
- 443:443
|
- 443:443
|
||||||
|
|
||||||
# Normally, the image is fetched from Docker Hub, but you can use the local Dockerfile by removing "image" above and adding this:
|
|
||||||
#build: .
|
|
||||||
# Works best when using `docker-compose up --force-recreate --build`.
|
|
||||||
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- mongodb
|
- mongodb
|
||||||
mongodb:
|
mongodb:
|
||||||
@ -24,4 +41,3 @@ services:
|
|||||||
MONGO_INITDB_ROOT_PASSWORD: spaceninjaserver
|
MONGO_INITDB_ROOT_PASSWORD: spaceninjaserver
|
||||||
volumes:
|
volumes:
|
||||||
- ./docker-data/database:/data/db
|
- ./docker-data/database:/data/db
|
||||||
command: mongod --quiet --logpath /dev/null
|
|
||||||
|
@ -1,8 +1,23 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [ ! -f conf/config.json ]; then
|
# Set up the configuration file using environment variables.
|
||||||
jq --arg value "mongodb://openwfagent:spaceninjaserver@mongodb:27017/" '.mongodbUrl = $value' /app/config.json.example > /app/conf/config.json
|
echo '{
|
||||||
fi
|
"logger": {
|
||||||
|
"files": true,
|
||||||
|
"level": "trace",
|
||||||
|
"__valid_levels": "fatal, error, warn, info, http, debug, trace"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' > config.json
|
||||||
|
|
||||||
exec npm run start -- --configPath conf/config.json
|
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 install
|
||||||
|
exec npm run dev
|
||||||
|
1449
inventoryService.ts
Normal file
1449
inventoryService.ts
Normal file
File diff suppressed because it is too large
Load Diff
906
missionInventoryUpdateService.ts
Normal file
906
missionInventoryUpdateService.ts
Normal file
@ -0,0 +1,906 @@
|
|||||||
|
import {
|
||||||
|
ExportEnemies,
|
||||||
|
ExportFusionBundles,
|
||||||
|
ExportRegions,
|
||||||
|
ExportRewards,
|
||||||
|
IMissionReward as IMissionRewardExternal,
|
||||||
|
IReward
|
||||||
|
} from "warframe-public-export-plus";
|
||||||
|
import { IMissionInventoryUpdateRequest, IRewardInfo } from "../types/requestTypes";
|
||||||
|
import { logger } from "@/src/utils/logger";
|
||||||
|
import { IRngResult, getRandomElement, getRandomReward } from "@/src/services/rngService";
|
||||||
|
import { equipmentKeys, IInventoryDatabase, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import {
|
||||||
|
addChallenges,
|
||||||
|
addConsumables,
|
||||||
|
addCrewShipAmmo,
|
||||||
|
addCrewShipRawSalvage,
|
||||||
|
addEmailItem,
|
||||||
|
addFocusXpIncreases,
|
||||||
|
addFusionTreasures,
|
||||||
|
addGearExpByCategory,
|
||||||
|
addItem,
|
||||||
|
addMiscItems,
|
||||||
|
addMissionComplete,
|
||||||
|
addMods,
|
||||||
|
addRecipes,
|
||||||
|
combineInventoryChanges,
|
||||||
|
updateSyndicate
|
||||||
|
} from "@/src/services/inventoryService";
|
||||||
|
import { updateQuestKey } from "@/src/services/questService";
|
||||||
|
import { HydratedDocument } from "mongoose";
|
||||||
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
import { getLevelKeyRewards, getNode, toStoreItem } from "@/src/services/itemDataService";
|
||||||
|
import { InventoryDocumentProps, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
|
import { getEntriesUnsafe } from "@/src/utils/ts-utils";
|
||||||
|
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
|
import { handleStoreItemAcquisition } from "./purchaseService";
|
||||||
|
import { IMissionReward } from "../types/missionTypes";
|
||||||
|
import { crackRelic } from "@/src/helpers/relicHelper";
|
||||||
|
import { createMessage } from "./inboxService";
|
||||||
|
import kuriaMessage50 from "@/static/fixed_responses/kuriaMessages/fiftyPercent.json";
|
||||||
|
import kuriaMessage75 from "@/static/fixed_responses/kuriaMessages/seventyFivePercent.json";
|
||||||
|
import kuriaMessage100 from "@/static/fixed_responses/kuriaMessages/oneHundredPercent.json";
|
||||||
|
|
||||||
|
const getRotations = (rotationCount: number): number[] => {
|
||||||
|
if (rotationCount === 0) return [0];
|
||||||
|
|
||||||
|
const rotationPattern = [0, 0, 1, 2]; // A, A, B, C
|
||||||
|
const rotatedValues = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < rotationCount; i++) {
|
||||||
|
rotatedValues.push(rotationPattern[i % rotationPattern.length]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rotatedValues;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRandomRewardByChance = (pool: IReward[]): IRngResult | undefined => {
|
||||||
|
return getRandomReward(pool as IRngResult[]);
|
||||||
|
};
|
||||||
|
|
||||||
|
//type TMissionInventoryUpdateKeys = keyof IMissionInventoryUpdateRequest;
|
||||||
|
//const ignoredInventoryUpdateKeys = ["FpsAvg", "FpsMax", "FpsMin", "FpsSamples"] satisfies TMissionInventoryUpdateKeys[]; // for keys with no meaning for this server
|
||||||
|
//type TignoredInventoryUpdateKeys = (typeof ignoredInventoryUpdateKeys)[number];
|
||||||
|
//const knownUnhandledKeys: readonly string[] = ["test"] as const; // for unimplemented but important keys
|
||||||
|
|
||||||
|
export const addMissionInventoryUpdates = async (
|
||||||
|
inventory: HydratedDocument<IInventoryDatabase, InventoryDocumentProps>,
|
||||||
|
inventoryUpdates: IMissionInventoryUpdateRequest
|
||||||
|
): Promise<IInventoryChanges> => {
|
||||||
|
const inventoryChanges: IInventoryChanges = {};
|
||||||
|
if (inventoryUpdates.RewardInfo && inventoryUpdates.RewardInfo.periodicMissionTag) {
|
||||||
|
const tag = inventoryUpdates.RewardInfo.periodicMissionTag;
|
||||||
|
const existingCompletion = inventory.PeriodicMissionCompletions.find(completion => completion.tag === tag);
|
||||||
|
|
||||||
|
if (existingCompletion) {
|
||||||
|
existingCompletion.date = new Date();
|
||||||
|
} else {
|
||||||
|
inventory.PeriodicMissionCompletions.push({
|
||||||
|
tag: tag,
|
||||||
|
date: new Date()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inventoryUpdates.RewardInfo && inventoryUpdates.RewardInfo.NemesisAbandonedRewards) {
|
||||||
|
inventory.NemesisAbandonedRewards = inventoryUpdates.RewardInfo.NemesisAbandonedRewards;
|
||||||
|
}
|
||||||
|
for (const [key, value] of getEntriesUnsafe(inventoryUpdates)) {
|
||||||
|
if (value === undefined) {
|
||||||
|
logger.error(`Inventory update key ${key} has no value `);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
switch (key) {
|
||||||
|
case "RegularCredits":
|
||||||
|
inventory.RegularCredits += value;
|
||||||
|
break;
|
||||||
|
case "QuestKeys":
|
||||||
|
await updateQuestKey(inventory, value);
|
||||||
|
break;
|
||||||
|
case "AffiliationChanges":
|
||||||
|
updateSyndicate(inventory, value);
|
||||||
|
break;
|
||||||
|
// Incarnon Challenges
|
||||||
|
case "EvolutionProgress": {
|
||||||
|
for (const evoProgress of value) {
|
||||||
|
const entry = inventory.EvolutionProgress
|
||||||
|
? inventory.EvolutionProgress.find(entry => entry.ItemType == evoProgress.ItemType)
|
||||||
|
: undefined;
|
||||||
|
if (entry) {
|
||||||
|
entry.Progress = evoProgress.Progress;
|
||||||
|
entry.Rank = evoProgress.Rank;
|
||||||
|
} else {
|
||||||
|
inventory.EvolutionProgress ??= [];
|
||||||
|
inventory.EvolutionProgress.push(evoProgress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "Missions":
|
||||||
|
addMissionComplete(inventory, value);
|
||||||
|
break;
|
||||||
|
case "LastRegionPlayed":
|
||||||
|
inventory.LastRegionPlayed = value;
|
||||||
|
break;
|
||||||
|
case "RawUpgrades":
|
||||||
|
addMods(inventory, value);
|
||||||
|
break;
|
||||||
|
case "MiscItems":
|
||||||
|
case "BonusMiscItems":
|
||||||
|
addMiscItems(inventory, value);
|
||||||
|
break;
|
||||||
|
case "Consumables":
|
||||||
|
addConsumables(inventory, value);
|
||||||
|
break;
|
||||||
|
case "Recipes":
|
||||||
|
addRecipes(inventory, value);
|
||||||
|
break;
|
||||||
|
case "ChallengeProgress":
|
||||||
|
addChallenges(inventory, value);
|
||||||
|
break;
|
||||||
|
case "FusionTreasures":
|
||||||
|
addFusionTreasures(inventory, value);
|
||||||
|
break;
|
||||||
|
case "CrewShipRawSalvage":
|
||||||
|
addCrewShipRawSalvage(inventory, value);
|
||||||
|
break;
|
||||||
|
case "CrewShipAmmo":
|
||||||
|
addCrewShipAmmo(inventory, value);
|
||||||
|
break;
|
||||||
|
case "FusionBundles": {
|
||||||
|
let fusionPoints = 0;
|
||||||
|
for (const fusionBundle of value) {
|
||||||
|
const fusionPointsTotal =
|
||||||
|
ExportFusionBundles[fusionBundle.ItemType].fusionPoints * fusionBundle.ItemCount;
|
||||||
|
inventory.FusionPoints += fusionPointsTotal;
|
||||||
|
fusionPoints += fusionPointsTotal;
|
||||||
|
}
|
||||||
|
inventoryChanges.FusionPoints = fusionPoints;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "EmailItems": {
|
||||||
|
for (const tc of value) {
|
||||||
|
await addEmailItem(inventory, tc.ItemType);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "FocusXpIncreases": {
|
||||||
|
addFocusXpIncreases(inventory, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "PlayerSkillGains": {
|
||||||
|
inventory.PlayerSkills.LPP_SPACE += value.LPP_SPACE;
|
||||||
|
inventory.PlayerSkills.LPP_DRIFTER += value.LPP_DRIFTER;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CustomMarkers": {
|
||||||
|
value.forEach(markers => {
|
||||||
|
const map = inventory.CustomMarkers
|
||||||
|
? inventory.CustomMarkers.find(entry => entry.tag == markers.tag)
|
||||||
|
: undefined;
|
||||||
|
if (map) {
|
||||||
|
map.markerInfos = markers.markerInfos;
|
||||||
|
} else {
|
||||||
|
inventory.CustomMarkers ??= [];
|
||||||
|
inventory.CustomMarkers.push(markers);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "LoreFragmentScans":
|
||||||
|
value.forEach(clientFragment => {
|
||||||
|
const fragment = inventory.LoreFragmentScans.find(x => x.ItemType == clientFragment.ItemType);
|
||||||
|
if (fragment) {
|
||||||
|
fragment.Progress += clientFragment.Progress;
|
||||||
|
} else {
|
||||||
|
inventory.LoreFragmentScans.push(clientFragment);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "LibraryScans":
|
||||||
|
value.forEach(scan => {
|
||||||
|
let synthesisIgnored = true;
|
||||||
|
if (
|
||||||
|
inventory.LibraryPersonalTarget &&
|
||||||
|
libraryPersonalTargetToAvatar[inventory.LibraryPersonalTarget] == scan.EnemyType
|
||||||
|
) {
|
||||||
|
let progress = inventory.LibraryPersonalProgress.find(
|
||||||
|
x => x.TargetType == inventory.LibraryPersonalTarget
|
||||||
|
);
|
||||||
|
if (!progress) {
|
||||||
|
progress =
|
||||||
|
inventory.LibraryPersonalProgress[
|
||||||
|
inventory.LibraryPersonalProgress.push({
|
||||||
|
TargetType: inventory.LibraryPersonalTarget,
|
||||||
|
Scans: 0,
|
||||||
|
Completed: false
|
||||||
|
}) - 1
|
||||||
|
];
|
||||||
|
}
|
||||||
|
progress.Scans += scan.Count;
|
||||||
|
if (
|
||||||
|
progress.Scans >=
|
||||||
|
(inventory.LibraryPersonalTarget ==
|
||||||
|
"/Lotus/Types/Game/Library/Targets/DragonframeQuestTarget"
|
||||||
|
? 3
|
||||||
|
: 10)
|
||||||
|
) {
|
||||||
|
progress.Completed = true;
|
||||||
|
}
|
||||||
|
logger.debug(`synthesis of ${scan.EnemyType} added to personal target progress`);
|
||||||
|
synthesisIgnored = false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
inventory.LibraryActiveDailyTaskInfo &&
|
||||||
|
inventory.LibraryActiveDailyTaskInfo.EnemyTypes.find(x => x == scan.EnemyType)
|
||||||
|
) {
|
||||||
|
inventory.LibraryActiveDailyTaskInfo.Scans ??= 0;
|
||||||
|
inventory.LibraryActiveDailyTaskInfo.Scans += scan.Count;
|
||||||
|
logger.debug(`synthesis of ${scan.EnemyType} added to daily task progress`);
|
||||||
|
synthesisIgnored = false;
|
||||||
|
}
|
||||||
|
if (synthesisIgnored) {
|
||||||
|
logger.warn(`ignoring synthesis of ${scan.EnemyType} due to not knowing why you did that`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "CollectibleScans":
|
||||||
|
for (const scan of value) {
|
||||||
|
const entry = inventory.CollectibleSeries?.find(x => x.CollectibleType == scan.CollectibleType);
|
||||||
|
if (entry) {
|
||||||
|
entry.Count = scan.Count;
|
||||||
|
entry.Tracking = scan.Tracking;
|
||||||
|
if (entry.CollectibleType == "/Lotus/Objects/Orokin/Props/CollectibleSeriesOne") {
|
||||||
|
const progress = entry.Count / entry.ReqScans;
|
||||||
|
for (const gate of entry.IncentiveStates) {
|
||||||
|
gate.complete = progress >= gate.threshold;
|
||||||
|
if (gate.complete && !gate.sent) {
|
||||||
|
gate.sent = true;
|
||||||
|
if (gate.threshold == 0.5) {
|
||||||
|
await createMessage(inventory.accountOwnerId.toString(), [kuriaMessage50]);
|
||||||
|
} else {
|
||||||
|
await createMessage(inventory.accountOwnerId.toString(), [kuriaMessage75]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (progress >= 1.0) {
|
||||||
|
await createMessage(inventory.accountOwnerId.toString(), [kuriaMessage100]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.warn(`${scan.CollectibleType} was not found in inventory, ignoring scans`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Upgrades":
|
||||||
|
value.forEach(clientUpgrade => {
|
||||||
|
const upgrade = inventory.Upgrades.id(clientUpgrade.ItemId.$oid)!;
|
||||||
|
upgrade.UpgradeFingerprint = clientUpgrade.UpgradeFingerprint; // primitive way to copy over the riven challenge progress
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "SyndicateId": {
|
||||||
|
inventory.CompletedSyndicates.push(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "SortieId": {
|
||||||
|
inventory.CompletedSorties.push(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "SeasonChallengeCompletions": {
|
||||||
|
const processedCompletions = value.map(({ challenge, id }) => ({
|
||||||
|
challenge: challenge.substring(challenge.lastIndexOf("/") + 1),
|
||||||
|
id
|
||||||
|
}));
|
||||||
|
inventory.SeasonChallengeHistory.push(...processedCompletions);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "DeathMarks": {
|
||||||
|
for (const deathMark of value) {
|
||||||
|
if (!inventory.DeathMarks.find(x => x == deathMark)) {
|
||||||
|
// It's a new death mark; we have to say the line.
|
||||||
|
await createMessage(inventory.accountOwnerId.toString(), [
|
||||||
|
{
|
||||||
|
sub: "/Lotus/Language/G1Quests/DeathMarkTitle",
|
||||||
|
sndr: "/Lotus/Language/G1Quests/DeathMarkSender",
|
||||||
|
msg: "/Lotus/Language/G1Quests/DeathMarkMessage",
|
||||||
|
icon: "/Lotus/Interface/Icons/Npcs/Stalker_d.png",
|
||||||
|
highPriority: true
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
// TODO: This type of inbox message seems to automatically delete itself. Figure out under which conditions.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inventory.DeathMarks = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// Equipment XP updates
|
||||||
|
if (equipmentKeys.includes(key as TEquipmentKey)) {
|
||||||
|
const multipliedValue = (value as IEquipmentClient[]).map(equipment => {
|
||||||
|
// 生成一个 0 到 1 的随机数
|
||||||
|
const randomChance = Math.random();
|
||||||
|
// 基础倍率为 5 倍
|
||||||
|
let multiplier = 5;
|
||||||
|
// 10% 的概率触发 10 倍经验
|
||||||
|
if (randomChance < 0.1) {
|
||||||
|
multiplier = 10; // ✅ 10% 概率 10 倍经验
|
||||||
|
}
|
||||||
|
// 计算最终经验值
|
||||||
|
const finalXP = (equipment.XP ?? 0) * multiplier;
|
||||||
|
|
||||||
|
// 日志输出(中文)
|
||||||
|
logger.debug(`[经验倍率] 随机数: ${randomChance.toFixed(2)}, 倍率: ${multiplier}, 最终经验值: ${finalXP}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...equipment,
|
||||||
|
XP: finalXP // ✅ 处理 undefined
|
||||||
|
};
|
||||||
|
});
|
||||||
|
addGearExpByCategory(inventory, multipliedValue, key as TEquipmentKey);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// if (
|
||||||
|
// (ignoredInventoryUpdateKeys as readonly string[]).includes(key) ||
|
||||||
|
// knownUnhandledKeys.includes(key)
|
||||||
|
// ) {
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
// logger.error(`Unhandled inventory update key: ${key}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return inventoryChanges;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface AddMissionRewardsReturnType {
|
||||||
|
MissionRewards: IMissionReward[];
|
||||||
|
inventoryChanges?: IInventoryChanges;
|
||||||
|
credits?: IMissionCredits;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: return type of partial missioninventoryupdate response
|
||||||
|
export const addMissionRewards = async (
|
||||||
|
inventory: TInventoryDatabaseDocument,
|
||||||
|
{
|
||||||
|
RewardInfo: rewardInfo,
|
||||||
|
LevelKeyName: levelKeyName,
|
||||||
|
Missions: missions,
|
||||||
|
RegularCredits: creditDrops,
|
||||||
|
VoidTearParticipantsCurrWave: voidTearWave,
|
||||||
|
StrippedItems: strippedItems
|
||||||
|
}: IMissionInventoryUpdateRequest
|
||||||
|
): Promise<AddMissionRewardsReturnType> => {
|
||||||
|
if (!rewardInfo) {
|
||||||
|
//TODO: if there is a case where you can have credits collected during a mission but no rewardInfo, add credits needs to be handled earlier
|
||||||
|
logger.debug(`Mission ${missions!.Tag} did not have Reward Info `);
|
||||||
|
return { MissionRewards: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: check double reward merging
|
||||||
|
const MissionRewards: IMissionReward[] = getRandomMissionDrops(rewardInfo);
|
||||||
|
logger.debug("random mission drops:", MissionRewards);
|
||||||
|
const inventoryChanges: IInventoryChanges = {};
|
||||||
|
|
||||||
|
let missionCompletionCredits = 0;
|
||||||
|
//inventory change is what the client has not rewarded itself, also the client needs to know the credit changes for display
|
||||||
|
if (levelKeyName) {
|
||||||
|
const fixedLevelRewards = getLevelKeyRewards(levelKeyName);
|
||||||
|
//logger.debug(`fixedLevelRewards ${fixedLevelRewards}`);
|
||||||
|
if (fixedLevelRewards.levelKeyRewards) {
|
||||||
|
addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, inventory, MissionRewards);
|
||||||
|
}
|
||||||
|
if (fixedLevelRewards.levelKeyRewards2) {
|
||||||
|
for (const reward of fixedLevelRewards.levelKeyRewards2) {
|
||||||
|
//quest stage completion credit rewards
|
||||||
|
if (reward.rewardType == "RT_CREDITS") {
|
||||||
|
inventory.RegularCredits += reward.amount;
|
||||||
|
missionCompletionCredits += reward.amount;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
MissionRewards.push({
|
||||||
|
StoreItem: reward.itemType,
|
||||||
|
ItemCount: reward.rewardType === "RT_RESOURCE" ? reward.amount : 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
missions &&
|
||||||
|
missions.Tag != "" // https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1013
|
||||||
|
) {
|
||||||
|
const node = getNode(missions.Tag);
|
||||||
|
|
||||||
|
//node based credit rewards for mission completion
|
||||||
|
if (node.missionIndex !== 28) {
|
||||||
|
const levelCreditReward = getLevelCreditRewards(missions.Tag);
|
||||||
|
missionCompletionCredits += levelCreditReward;
|
||||||
|
inventory.RegularCredits += levelCreditReward;
|
||||||
|
logger.debug(`levelCreditReward ${levelCreditReward}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.missionReward) {
|
||||||
|
missionCompletionCredits += addFixedLevelRewards(node.missionReward, inventory, MissionRewards);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rewardInfo.useVaultManifest) {
|
||||||
|
MissionRewards.push({
|
||||||
|
StoreItem: getRandomElement(corruptedMods),
|
||||||
|
ItemCount: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const reward of MissionRewards) {
|
||||||
|
const inventoryChange = await handleStoreItemAcquisition(reward.StoreItem, inventory, reward.ItemCount);
|
||||||
|
//TODO: combineInventoryChanges improve type safety, merging 2 of the same item?
|
||||||
|
//TODO: check for the case when two of the same item are added, combineInventoryChanges should merge them, but the client also merges them
|
||||||
|
//TODO: some conditional types to rule out binchanges?
|
||||||
|
combineInventoryChanges(inventoryChanges, inventoryChange.InventoryChanges);
|
||||||
|
}
|
||||||
|
|
||||||
|
const credits = addCredits(inventory, {
|
||||||
|
missionCompletionCredits,
|
||||||
|
missionDropCredits: creditDrops ?? 0,
|
||||||
|
rngRewardCredits: inventoryChanges.RegularCredits ?? 0
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
voidTearWave &&
|
||||||
|
voidTearWave.Participants[0].QualifiesForReward &&
|
||||||
|
!voidTearWave.Participants[0].HaveRewardResponse
|
||||||
|
) {
|
||||||
|
const reward = await crackRelic(inventory, voidTearWave.Participants[0], inventoryChanges);
|
||||||
|
MissionRewards.push({ StoreItem: reward.type, ItemCount: reward.itemCount });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strippedItems) {
|
||||||
|
for (const si of strippedItems) {
|
||||||
|
const droptable = ExportEnemies.droptables[si.DropTable];
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
|
if (!droptable) {
|
||||||
|
logger.error(`unknown droptable ${si.DropTable}`);
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i != (si.DROP_MOD || []).length; ++i) {
|
||||||
|
for (const pool of droptable) {
|
||||||
|
const reward = getRandomReward(pool.items)!;
|
||||||
|
logger.debug(`stripped droptable rolled`, reward);
|
||||||
|
await addItem(inventory, reward.type);
|
||||||
|
MissionRewards.push({
|
||||||
|
StoreItem: toStoreItem(reward.type),
|
||||||
|
ItemCount: 1,
|
||||||
|
FromEnemyCache: true // to show "identified"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { inventoryChanges, MissionRewards, credits };
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IMissionCredits {
|
||||||
|
MissionCredits: number[];
|
||||||
|
CreditBonus: number[];
|
||||||
|
TotalCredits: number[];
|
||||||
|
DailyMissionBonus?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
//creditBonus is not entirely accurate.
|
||||||
|
//TODO: consider ActiveBoosters
|
||||||
|
export const addCredits = (
|
||||||
|
inventory: HydratedDocument<IInventoryDatabase>,
|
||||||
|
{
|
||||||
|
missionDropCredits,
|
||||||
|
missionCompletionCredits,
|
||||||
|
rngRewardCredits
|
||||||
|
}: { missionDropCredits: number; missionCompletionCredits: number; rngRewardCredits: number }
|
||||||
|
): IMissionCredits => {
|
||||||
|
const hasDailyCreditBonus = true;
|
||||||
|
const totalCredits = missionDropCredits + missionCompletionCredits + rngRewardCredits;
|
||||||
|
|
||||||
|
const finalCredits: IMissionCredits = {
|
||||||
|
MissionCredits: [missionDropCredits, missionDropCredits],
|
||||||
|
CreditBonus: [missionCompletionCredits, missionCompletionCredits],
|
||||||
|
TotalCredits: [totalCredits, totalCredits]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (hasDailyCreditBonus) {
|
||||||
|
inventory.RegularCredits += missionCompletionCredits;
|
||||||
|
finalCredits.CreditBonus[1] *= 2;
|
||||||
|
finalCredits.MissionCredits[1] *= 2;
|
||||||
|
finalCredits.TotalCredits[1] *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasDailyCreditBonus) {
|
||||||
|
return finalCredits;
|
||||||
|
}
|
||||||
|
return { ...finalCredits, DailyMissionBonus: true };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addFixedLevelRewards = (
|
||||||
|
rewards: IMissionRewardExternal,
|
||||||
|
inventory: TInventoryDatabaseDocument,
|
||||||
|
MissionRewards: IMissionReward[]
|
||||||
|
): number => {
|
||||||
|
let missionBonusCredits = 0;
|
||||||
|
if (rewards.credits) {
|
||||||
|
missionBonusCredits += rewards.credits;
|
||||||
|
inventory.RegularCredits += rewards.credits;
|
||||||
|
}
|
||||||
|
if (rewards.items) {
|
||||||
|
for (const item of rewards.items) {
|
||||||
|
MissionRewards.push({
|
||||||
|
StoreItem: item,
|
||||||
|
ItemCount: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rewards.countedItems) {
|
||||||
|
for (const item of rewards.countedItems) {
|
||||||
|
MissionRewards.push({
|
||||||
|
StoreItem: `/Lotus/StoreItems${item.ItemType.substring("Lotus/".length)}`,
|
||||||
|
ItemCount: item.ItemCount
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rewards.countedStoreItems) {
|
||||||
|
for (const item of rewards.countedStoreItems) {
|
||||||
|
MissionRewards.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return missionBonusCredits;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getLevelCreditRewards(nodeName: string): number {
|
||||||
|
const minEnemyLevel = getNode(nodeName).minEnemyLevel;
|
||||||
|
|
||||||
|
return 1000 + (minEnemyLevel - 1) * 100;
|
||||||
|
|
||||||
|
//TODO: get dark sektor fixed credit rewards and railjack bonus
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRandomMissionDrops(RewardInfo: IRewardInfo): IMissionReward[] {
|
||||||
|
const drops: IMissionReward[] = [];
|
||||||
|
|
||||||
|
// 模糊匹配 jobId 并处理奖励
|
||||||
|
if (RewardInfo.jobId) {
|
||||||
|
// 定义任务类型和对应的奖励表、声望阵营及声望值
|
||||||
|
const jobRewardsMap: Record<string, { rewardManifest: string; tag: string; standingValue: number }> = {
|
||||||
|
"AssassinateBountyCap": {
|
||||||
|
rewardManifest: "/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierATableCRewards",
|
||||||
|
tag: "CetusSyndicate",
|
||||||
|
standingValue: 430
|
||||||
|
},
|
||||||
|
"DeimosGrnSurvivorBounty": {
|
||||||
|
rewardManifest: "/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierATableBRewards",
|
||||||
|
tag: "EntratiSyndicate",
|
||||||
|
standingValue: 450
|
||||||
|
},
|
||||||
|
"DeimosAreaDefenseBounty": {
|
||||||
|
rewardManifest: "/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierCTableBRewards",
|
||||||
|
tag: "EntratiSyndicate",
|
||||||
|
standingValue: 500
|
||||||
|
},
|
||||||
|
"DeimosEndlessExcavateBounty": {
|
||||||
|
rewardManifest: "/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierBTableARewards",
|
||||||
|
tag: "EntratiSyndicate",
|
||||||
|
standingValue: 550
|
||||||
|
},
|
||||||
|
"DeimosAssassinateBounty": {
|
||||||
|
rewardManifest: "/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierDTableBRewards",
|
||||||
|
tag: "EntratiSyndicate",
|
||||||
|
standingValue: 600
|
||||||
|
},
|
||||||
|
"DeimosKeyPiecesBounty": {
|
||||||
|
rewardManifest: "/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierETableARewards",
|
||||||
|
tag: "EntratiSyndicate",
|
||||||
|
standingValue: 650
|
||||||
|
},
|
||||||
|
"DeimosExcavateBounty": {
|
||||||
|
rewardManifest: "/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierETableARewards",
|
||||||
|
tag: "EntratiSyndicate",
|
||||||
|
standingValue: 700
|
||||||
|
},
|
||||||
|
"VenusIntelJobSpy": {
|
||||||
|
rewardManifest: "/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierATableBRewards",
|
||||||
|
tag: "SolarisSyndicate",
|
||||||
|
standingValue: 450
|
||||||
|
},
|
||||||
|
"VenusCullJobResource": {
|
||||||
|
rewardManifest: "/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierBTableBRewards",
|
||||||
|
tag: "SolarisSyndicate",
|
||||||
|
standingValue: 500
|
||||||
|
},
|
||||||
|
"VenusIntelJobRecovery": {
|
||||||
|
rewardManifest: "/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierCTableBRewards",
|
||||||
|
tag: "SolarisSyndicate",
|
||||||
|
standingValue: 550
|
||||||
|
},
|
||||||
|
"VenusHelpingJobCaches": {
|
||||||
|
rewardManifest: "/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierDTableBRewards",
|
||||||
|
tag: "SolarisSyndicate",
|
||||||
|
standingValue: 600
|
||||||
|
},
|
||||||
|
"VenusArtifactJobAmbush": {
|
||||||
|
rewardManifest: "/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETableBRewards",
|
||||||
|
tag: "SolarisSyndicate",
|
||||||
|
standingValue: 650
|
||||||
|
},
|
||||||
|
"VenusChaosJobExcavation": {
|
||||||
|
rewardManifest: "/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETableBRewards",
|
||||||
|
tag: "SolarisSyndicate",
|
||||||
|
standingValue: 700
|
||||||
|
},
|
||||||
|
"NarmerVenusCullJobExterminate": {
|
||||||
|
rewardManifest: "/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/NarmerTableBRewards",
|
||||||
|
tag: "SolarisSyndicate",
|
||||||
|
standingValue: 800
|
||||||
|
},
|
||||||
|
"AttritionBountyLib": {
|
||||||
|
rewardManifest: "/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierBTableBRewards",
|
||||||
|
tag: "CetusSyndicate",
|
||||||
|
standingValue: 500
|
||||||
|
},
|
||||||
|
"RescueBountyResc": {
|
||||||
|
rewardManifest: "/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierCTableBRewards",
|
||||||
|
tag: "CetusSyndicate",
|
||||||
|
standingValue: 550
|
||||||
|
},
|
||||||
|
"CaptureBountyCapTwo": {
|
||||||
|
rewardManifest: "/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierDTableBRewards",
|
||||||
|
tag: "CetusSyndicate",
|
||||||
|
standingValue: 600
|
||||||
|
},
|
||||||
|
"ReclamationBountyCache": {
|
||||||
|
rewardManifest: "/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierETableBRewards",
|
||||||
|
tag: "CetusSyndicate",
|
||||||
|
standingValue: 650
|
||||||
|
},
|
||||||
|
"AttritionBountyCap": {
|
||||||
|
rewardManifest: "/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierETableBRewards",
|
||||||
|
tag: "CetusSyndicate",
|
||||||
|
standingValue: 700
|
||||||
|
},
|
||||||
|
"ChamberB": {
|
||||||
|
rewardManifest: "/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/VaultBountyTierATableCRewards",
|
||||||
|
tag: "EntratiSyndicate",
|
||||||
|
standingValue: 500
|
||||||
|
},
|
||||||
|
"ChamberA": {
|
||||||
|
rewardManifest: "/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/VaultBountyTierBTableCRewards",
|
||||||
|
tag: "EntratiSyndicate",
|
||||||
|
standingValue: 800
|
||||||
|
},
|
||||||
|
"Chamberc": {
|
||||||
|
rewardManifest: "/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/VaultBountyTierCTableCRewards",
|
||||||
|
tag: "EntratiSyndicate",
|
||||||
|
standingValue: 1000
|
||||||
|
},
|
||||||
|
"HeistProfitTakerBountyOne": {
|
||||||
|
rewardManifest: "/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/VaultBountyTierCTableCRewards111",
|
||||||
|
tag: "EntratiSyndicate",
|
||||||
|
standingValue: 1000
|
||||||
|
},
|
||||||
|
"AssassinateBountyAss": {
|
||||||
|
rewardManifest: "/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/NarmerTableBRewards",
|
||||||
|
tag: "CetusSyndicate",
|
||||||
|
standingValue: 800
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 遍历任务类型,模糊匹配 jobId
|
||||||
|
for (const [jobType, { rewardManifest, tag, standingValue }] of Object.entries(jobRewardsMap)) {
|
||||||
|
if (RewardInfo.jobId.includes(jobType)) {
|
||||||
|
logger.debug(`Job ID contains ${jobType}, using reward manifest: ${rewardManifest}`);
|
||||||
|
|
||||||
|
const rewardTable = ExportRewards[rewardManifest];
|
||||||
|
if (rewardTable) {
|
||||||
|
// 使用 JobStage 作为轮次索引
|
||||||
|
let rotation = RewardInfo.JobStage || 0; // 默认值为 0
|
||||||
|
logger.debug("Using JobStage as rotation index:", rotation);
|
||||||
|
|
||||||
|
// 如果 JobStage 超过 3,则按最高档(第 3 档)处理
|
||||||
|
if (rotation > 3) {
|
||||||
|
rotation = 3;
|
||||||
|
logger.debug("JobStage exceeds 3, using highest rotation (3)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查轮次索引是否在奖励表范围内
|
||||||
|
if (rotation >= rewardTable.length || rotation < 0) {
|
||||||
|
logger.error(`Rotation index ${rotation} is out of bounds for reward table ${rewardManifest}`);
|
||||||
|
} else {
|
||||||
|
// 获取当前轮次的奖励池
|
||||||
|
const rotationRewards = rewardTable[rotation];
|
||||||
|
logger.debug("Rotation rewards:", rotationRewards);
|
||||||
|
|
||||||
|
// 从奖励池中随机选择一个奖励
|
||||||
|
const drop = getRandomRewardByChance(rotationRewards);
|
||||||
|
if (drop) {
|
||||||
|
logger.debug("Random drop selected:", drop);
|
||||||
|
drops.push({ StoreItem: drop.type, ItemCount: drop.itemCount });
|
||||||
|
} else {
|
||||||
|
logger.debug("No drop selected from reward table");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.error(`Reward table ${rewardManifest} not found in ExportRewards`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增一个固定的物品奖励
|
||||||
|
const additionalReward = {
|
||||||
|
StoreItem: "/Lotus/StoreItems/Types/Items/SyndicateDogTags/UniversalSyndicateDogTag", // 新物品的路径
|
||||||
|
ItemCount: 1 // 物品数量
|
||||||
|
};
|
||||||
|
drops.push(additionalReward);
|
||||||
|
logger.debug("Added additional reward:", additionalReward);
|
||||||
|
|
||||||
|
// 直接返回,不再执行后续的区域奖励逻辑
|
||||||
|
return drops;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (RewardInfo.node in ExportRegions) {
|
||||||
|
const region = ExportRegions[RewardInfo.node];
|
||||||
|
const rewardManifests: string[] =
|
||||||
|
RewardInfo.periodicMissionTag == "EliteAlert" || RewardInfo.periodicMissionTag == "EliteAlertB"
|
||||||
|
? ["/Lotus/Types/Game/MissionDecks/EliteAlertMissionRewards/EliteAlertMissionRewards"]
|
||||||
|
: region.rewardManifests;
|
||||||
|
|
||||||
|
let rotations: number[] = [];
|
||||||
|
if (RewardInfo.VaultsCracked) {
|
||||||
|
// For Spy missions, e.g. 3 vaults cracked = A, B, C
|
||||||
|
for (let i = 0; i != RewardInfo.VaultsCracked; ++i) {
|
||||||
|
rotations.push(i);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const rotationCount = RewardInfo.rewardQualifications?.length || 0;
|
||||||
|
rotations = getRotations(rotationCount);
|
||||||
|
}
|
||||||
|
rewardManifests
|
||||||
|
.map(name => ExportRewards[name])
|
||||||
|
.forEach(table => {
|
||||||
|
for (const rotation of rotations) {
|
||||||
|
const rotationRewards = table[rotation];
|
||||||
|
const drop = getRandomRewardByChance(rotationRewards);
|
||||||
|
|
||||||
|
// 原始掉落逻辑
|
||||||
|
if (drop) {
|
||||||
|
drops.push({ StoreItem: drop.type, ItemCount: drop.itemCount });
|
||||||
|
}
|
||||||
|
|
||||||
|
// EliteAlert奖励逻辑
|
||||||
|
if (RewardInfo.periodicMissionTag === "EliteAlert" || RewardInfo.periodicMissionTag === "EliteAlertB") {
|
||||||
|
const randomCount = Math.floor(Math.random() * 5) + 1;
|
||||||
|
drops.push({ StoreItem: "/Lotus/StoreItems/Types/Items/MiscItems/Elitium", ItemCount: randomCount });
|
||||||
|
}
|
||||||
|
// 添加 HardDaily 任务的 钢铁精华 掉落
|
||||||
|
if (RewardInfo.periodicMissionTag?.startsWith("HardDaily")) {
|
||||||
|
let randomCount = Math.floor(Math.random() * 5) + 1; // 生成 1 到 5 的随机数
|
||||||
|
|
||||||
|
// 20% 的几率翻 1 到 10 倍
|
||||||
|
if (Math.random() < 0.2) {
|
||||||
|
const multiplier = Math.floor(Math.random() * 10) + 1; // 生成 1 到 10 的随机倍数
|
||||||
|
randomCount *= multiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
drops.push({ StoreItem: "/Lotus/StoreItems/Types/Items/MiscItems/SteelEssence", ItemCount: randomCount });
|
||||||
|
}
|
||||||
|
// 新增10%概率独立掉落 ▼▼▼
|
||||||
|
if (Math.random() < 0.01) { // 每个rotation独立判定
|
||||||
|
drops.push({
|
||||||
|
StoreItem: "/Lotus/StoreItems/Upgrades/Skins/Volt/SWTechnoshockHelmet",
|
||||||
|
ItemCount: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (region.cacheRewardManifest && RewardInfo.EnemyCachesFound) {
|
||||||
|
const deck = ExportRewards[region.cacheRewardManifest];
|
||||||
|
for (let rotation = 0; rotation != RewardInfo.EnemyCachesFound; ++rotation) {
|
||||||
|
const drop = getRandomRewardByChance(deck[rotation]);
|
||||||
|
if (drop) {
|
||||||
|
drops.push({ StoreItem: drop.type, ItemCount: drop.itemCount, FromEnemyCache: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RewardInfo.nightmareMode) {
|
||||||
|
const deck = ExportRewards["/Lotus/Types/Game/MissionDecks/NightmareModeRewards"];
|
||||||
|
let rotation = 0;
|
||||||
|
|
||||||
|
// 确保 region 已正确初始化
|
||||||
|
if (region) {
|
||||||
|
if (region.missionIndex === 3 && RewardInfo.rewardTier) {
|
||||||
|
// 如果 missionIndex 为 3 且 rewardTier 存在,则使用 rewardTier
|
||||||
|
rotation = RewardInfo.rewardTier;
|
||||||
|
} else if ([6, 7, 8, 10, 11].includes(region.systemIndex)) {
|
||||||
|
// 如果 systemIndex 在 [6, 7, 8, 10, 11] 中,则 rotation 为 2
|
||||||
|
rotation = 2;
|
||||||
|
} else if ([4, 9, 12, 14, 15, 16, 17, 18].includes(region.systemIndex)) {
|
||||||
|
// 如果 systemIndex 在 [4, 9, 12, 14, 15, 16, 17, 18] 中,则 rotation 为 1
|
||||||
|
rotation = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保 rotation 在 deck 的范围内
|
||||||
|
if (rotation >= deck.length || rotation < 0) {
|
||||||
|
logger.error(`Rotation index ${rotation} is out of bounds for NightmareModeRewards`);
|
||||||
|
rotation = 0; // 如果超出范围,则使用默认值 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前轮次的奖励池
|
||||||
|
const rotationRewards = deck[rotation];
|
||||||
|
if (rotationRewards) {
|
||||||
|
// 从奖励池中随机选择一个奖励
|
||||||
|
const drop = getRandomRewardByChance(rotationRewards);
|
||||||
|
if (drop) {
|
||||||
|
logger.debug("Nightmare mode drop selected:", drop);
|
||||||
|
drops.push({ StoreItem: drop.type, ItemCount: drop.itemCount });
|
||||||
|
} else {
|
||||||
|
logger.debug("No drop selected from NightmareModeRewards");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.error("No rewards found for NightmareModeRewards");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保函数有返回值
|
||||||
|
return drops;
|
||||||
|
}
|
||||||
|
|
||||||
|
const corruptedMods = [
|
||||||
|
"/Lotus/StoreItems/Upgrades/Mods/Melee/DualStat/CorruptedHeavyDamageChargeSpeedMod", // Corrupt Charge
|
||||||
|
"/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/CorruptedCritDamagePistol", // Hollow Point
|
||||||
|
"/Lotus/StoreItems/Upgrades/Mods/Melee/DualStat/CorruptedDamageSpeedMod", // Spoiled Strike
|
||||||
|
"/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/CorruptedDamageRecoilPistol", // Magnum Force
|
||||||
|
"/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/CorruptedMaxClipReloadSpeedPistol", // Tainted Clip
|
||||||
|
"/Lotus/StoreItems/Upgrades/Mods/Rifle/DualStat/CorruptedCritRateFireRateRifle", // Critical Delay
|
||||||
|
"/Lotus/StoreItems/Upgrades/Mods/Rifle/DualStat/CorruptedDamageRecoilRifle", // Heavy Caliber
|
||||||
|
"/Lotus/StoreItems/Upgrades/Mods/Rifle/DualStat/CorruptedMaxClipReloadSpeedRifle", // Tainted Mag
|
||||||
|
"/Lotus/StoreItems/Upgrades/Mods/Rifle/DualStat/CorruptedRecoilFireRateRifle", // Vile Precision
|
||||||
|
"/Lotus/StoreItems/Upgrades/Mods/Warframe/DualStat/CorruptedDurationRangeWarframe", // Narrow Minded
|
||||||
|
"/Lotus/StoreItems/Upgrades/Mods/Warframe/DualStat/CorruptedEfficiencyDurationWarframe", // Fleeting Expertise
|
||||||
|
"/Lotus/StoreItems/Upgrades/Mods/Warframe/DualStat/CorruptedPowerEfficiencyWarframe", // Blind Rage
|
||||||
|
"/Lotus/StoreItems/Upgrades/Mods/Warframe/DualStat/CorruptedRangePowerWarframe", // Overextended
|
||||||
|
"/Lotus/StoreItems/Upgrades/Mods/Shotgun/DualStat/CorruptedAccuracyFireRateShotgun", // Tainted Shell
|
||||||
|
"/Lotus/StoreItems/Upgrades/Mods/Shotgun/DualStat/CorruptedDamageAccuracyShotgun", // Vicious Spread
|
||||||
|
"/Lotus/StoreItems/Upgrades/Mods/Shotgun/DualStat/CorruptedMaxClipReloadSpeedShotgun", // Burdened Magazine
|
||||||
|
"/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/CorruptedFireRateDamagePistol", // Anemic Agility
|
||||||
|
"/Lotus/StoreItems/Upgrades/Mods/Rifle/DualStat/CorruptedFireRateDamageRifle", // Vile Acceleration
|
||||||
|
"/Lotus/StoreItems/Upgrades/Mods/Shotgun/DualStat/CorruptedFireRateDamageShotgun", // Frail Momentum
|
||||||
|
"/Lotus/StoreItems/Upgrades/Mods/Shotgun/DualStat/CorruptedCritChanceFireRateShotgun", // Critical Deceleration
|
||||||
|
"/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/CorruptedCritChanceFireRatePistol", // Creeping Bullseye
|
||||||
|
"/Lotus/StoreItems/Upgrades/Mods/Warframe/DualStat/CorruptedPowerStrengthPowerDurationWarframe", // Transient Fortitude
|
||||||
|
"/Lotus/StoreItems/Upgrades/Mods/Rifle/DualStat/CorruptedReloadSpeedMaxClipRifle", // Depleted Reload
|
||||||
|
"/Lotus/StoreItems/Upgrades/Mods/Warframe/DualStat/FixedShieldAndShieldGatingDuration" // Catalyzing Shields
|
||||||
|
];
|
||||||
|
|
||||||
|
const libraryPersonalTargetToAvatar: Record<string, string> = {
|
||||||
|
"/Lotus/Types/Game/Library/Targets/DragonframeQuestTarget":
|
||||||
|
"/Lotus/Types/Enemies/Grineer/Desert/Avatars/RifleLancerAvatar",
|
||||||
|
"/Lotus/Types/Game/Library/Targets/Research1Target":
|
||||||
|
"/Lotus/Types/Enemies/Grineer/Desert/Avatars/RifleLancerAvatar",
|
||||||
|
"/Lotus/Types/Game/Library/Targets/Research2Target":
|
||||||
|
"/Lotus/Types/Enemies/Corpus/BipedRobot/AIWeek/LaserDiscBipedAvatar",
|
||||||
|
"/Lotus/Types/Game/Library/Targets/Research3Target":
|
||||||
|
"/Lotus/Types/Enemies/Grineer/Desert/Avatars/EvisceratorLancerAvatar",
|
||||||
|
"/Lotus/Types/Game/Library/Targets/Research4Target": "/Lotus/Types/Enemies/Orokin/OrokinHealingAncientAvatar",
|
||||||
|
"/Lotus/Types/Game/Library/Targets/Research5Target":
|
||||||
|
"/Lotus/Types/Enemies/Corpus/Spaceman/AIWeek/ShotgunSpacemanAvatar",
|
||||||
|
"/Lotus/Types/Game/Library/Targets/Research6Target": "/Lotus/Types/Enemies/Infested/AiWeek/Runners/RunnerAvatar",
|
||||||
|
"/Lotus/Types/Game/Library/Targets/Research7Target":
|
||||||
|
"/Lotus/Types/Enemies/Grineer/AIWeek/Avatars/GrineerMeleeStaffAvatar",
|
||||||
|
"/Lotus/Types/Game/Library/Targets/Research8Target": "/Lotus/Types/Enemies/Orokin/OrokinHeavyFemaleAvatar",
|
||||||
|
"/Lotus/Types/Game/Library/Targets/Research9Target":
|
||||||
|
"/Lotus/Types/Enemies/Infested/AiWeek/Quadrupeds/QuadrupedAvatar",
|
||||||
|
"/Lotus/Types/Game/Library/Targets/Research10Target":
|
||||||
|
"/Lotus/Types/Enemies/Corpus/Spaceman/AIWeek/NullifySpacemanAvatar"
|
||||||
|
};
|
1719
package-lock.json
generated
1719
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
47
package.json
47
package.json
@ -4,19 +4,10 @@
|
|||||||
"description": "WF Emulator",
|
"description": "WF Emulator",
|
||||||
"main": "index.ts",
|
"main": "index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node --enable-source-maps --import ./build/src/pathman.js build/src/index.js",
|
"start": "node --import ./build/src/pathman.js build/src/index.js",
|
||||||
"build": "tsgo --sourceMap && ncp static/webui build/static/webui",
|
"dev": "ts-node-dev --openssl-legacy-provider -r tsconfig-paths/register src/index.ts ",
|
||||||
"build:tsc": "tsc --incremental --sourceMap && ncp static/webui build/static/webui",
|
"build": "tsc && copyfiles static/webui/** build",
|
||||||
"build:dev": "tsgo --sourceMap",
|
|
||||||
"build:dev:tsc": "tsc --incremental --sourceMap",
|
|
||||||
"build-and-start": "npm run build && npm run start",
|
|
||||||
"build-and-start:bun": "npm run verify && npm run bun-run",
|
|
||||||
"dev": "node scripts/dev.js",
|
|
||||||
"dev:bun": "bun scripts/dev.js",
|
|
||||||
"verify": "tsgo --noEmit",
|
|
||||||
"bun-run": "bun src/index.ts",
|
|
||||||
"lint": "eslint --ext .ts .",
|
"lint": "eslint --ext .ts .",
|
||||||
"lint:ci": "eslint --ext .ts --rule \"prettier/prettier: off\" .",
|
|
||||||
"lint:fix": "eslint --fix --ext .ts .",
|
"lint:fix": "eslint --fix --ext .ts .",
|
||||||
"prettier": "prettier --write .",
|
"prettier": "prettier --write .",
|
||||||
"update-translations": "cd scripts && node update-translations.js"
|
"update-translations": "cd scripts && node update-translations.js"
|
||||||
@ -25,30 +16,28 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/express": "^5",
|
"@types/express": "^5",
|
||||||
"@types/morgan": "^1.9.9",
|
"@types/morgan": "^1.9.9",
|
||||||
"@types/websocket": "^1.0.10",
|
"copyfiles": "^2.4.1",
|
||||||
"@types/ws": "^8.18.1",
|
|
||||||
"@typescript/native-preview": "^7.0.0-dev.20250625.1",
|
|
||||||
"chokidar": "^4.0.3",
|
|
||||||
"crc-32": "^1.2.2",
|
"crc-32": "^1.2.2",
|
||||||
"express": "^5",
|
"express": "^5",
|
||||||
"json-with-bigint": "^3.4.4",
|
|
||||||
"mongoose": "^8.11.0",
|
"mongoose": "^8.11.0",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"ncp": "^2.0.0",
|
"typescript": ">=5.5 <5.6.0",
|
||||||
"typescript": "^5.5",
|
"warframe-public-export-plus": "^0.5.46",
|
||||||
"undici": "^7.10.0",
|
|
||||||
"warframe-public-export-plus": "^0.5.76",
|
|
||||||
"warframe-riven-info": "^0.1.2",
|
"warframe-riven-info": "^0.1.2",
|
||||||
"winston": "^3.17.0",
|
"winston": "^3.17.0",
|
||||||
"winston-daily-rotate-file": "^5.0.0",
|
"winston-daily-rotate-file": "^5.0.0"
|
||||||
"ws": "^8.18.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "^8.28.0",
|
"@typescript-eslint/eslint-plugin": "^7.18",
|
||||||
"@typescript-eslint/parser": "^8.28.0",
|
"@typescript-eslint/parser": "^7.18",
|
||||||
"eslint": "^8",
|
"eslint": "^8.56.0",
|
||||||
"eslint-plugin-prettier": "^5.2.5",
|
"eslint-plugin-prettier": "^5.2.3",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.4.2",
|
||||||
"tree-kill": "^1.2.2"
|
"ts-node-dev": "^2.0.0",
|
||||||
|
"tsconfig-paths": "^4.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.15.0",
|
||||||
|
"npm": ">=9.5.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
const { spawn } = require("child_process");
|
|
||||||
const chokidar = require("chokidar");
|
|
||||||
const kill = require("tree-kill");
|
|
||||||
|
|
||||||
let secret = "";
|
|
||||||
for (let i = 0; i != 10; ++i) {
|
|
||||||
secret += String.fromCharCode(Math.floor(Math.random() * 26) + 0x41);
|
|
||||||
}
|
|
||||||
|
|
||||||
const args = [...process.argv].splice(2);
|
|
||||||
args.push("--dev");
|
|
||||||
args.push("--secret");
|
|
||||||
args.push(secret);
|
|
||||||
|
|
||||||
let buildproc, runproc;
|
|
||||||
const spawnopts = { stdio: "inherit", shell: true };
|
|
||||||
function run(changedFile) {
|
|
||||||
if (changedFile) {
|
|
||||||
console.log(`Change to ${changedFile} detected`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buildproc) {
|
|
||||||
kill(buildproc.pid);
|
|
||||||
buildproc = undefined;
|
|
||||||
}
|
|
||||||
if (runproc) {
|
|
||||||
kill(runproc.pid);
|
|
||||||
runproc = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const thisbuildproc = spawn("npm", ["run", process.versions.bun ? "verify" : "build:dev"], spawnopts);
|
|
||||||
const thisbuildstart = Date.now();
|
|
||||||
buildproc = thisbuildproc;
|
|
||||||
buildproc.on("exit", code => {
|
|
||||||
if (buildproc !== thisbuildproc) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
buildproc = undefined;
|
|
||||||
if (code === 0) {
|
|
||||||
console.log(`${process.versions.bun ? "Verified" : "Built"} in ${Date.now() - thisbuildstart} ms`);
|
|
||||||
runproc = spawn("npm", ["run", process.versions.bun ? "bun-run" : "start", "--", ...args], spawnopts);
|
|
||||||
runproc.on("exit", () => {
|
|
||||||
runproc = undefined;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
run();
|
|
||||||
chokidar.watch("src").on("change", run);
|
|
||||||
chokidar.watch("static/fixed_responses").on("change", run);
|
|
||||||
|
|
||||||
chokidar.watch("static/webui").on("change", async () => {
|
|
||||||
try {
|
|
||||||
await fetch("http://localhost/custom/webuiFileChangeDetected?secret=" + secret);
|
|
||||||
} catch (e) {}
|
|
||||||
});
|
|
@ -1,11 +1,10 @@
|
|||||||
// Based on https://onlyg.it/OpenWF/Translations/src/branch/main/update.php
|
// Based on https://onlyg.it/OpenWF/Translations/src/branch/main/update.php
|
||||||
// Converted via ChatGPT-4o
|
// Converted via ChatGPT-4o
|
||||||
|
|
||||||
/* eslint-disable */
|
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
|
||||||
function extractStrings(content) {
|
function extractStrings(content) {
|
||||||
const regex = /([a-zA-Z0-9_]+): `([^`]*)`,/g;
|
const regex = /([a-zA-Z_]+): `([^`]*)`,/g;
|
||||||
let matches;
|
let matches;
|
||||||
const strings = {};
|
const strings = {};
|
||||||
while ((matches = regex.exec(content)) !== null) {
|
while ((matches = regex.exec(content)) !== null) {
|
||||||
@ -16,7 +15,7 @@ function extractStrings(content) {
|
|||||||
|
|
||||||
const source = fs.readFileSync("../static/webui/translations/en.js", "utf8");
|
const source = fs.readFileSync("../static/webui/translations/en.js", "utf8");
|
||||||
const sourceStrings = extractStrings(source);
|
const sourceStrings = extractStrings(source);
|
||||||
const sourceLines = source.substring(0, source.length - 1).split("\n");
|
const sourceLines = source.split("\n");
|
||||||
|
|
||||||
fs.readdirSync("../static/webui/translations").forEach(file => {
|
fs.readdirSync("../static/webui/translations").forEach(file => {
|
||||||
if (fs.lstatSync(`../static/webui/translations/${file}`).isFile() && file !== "en.js") {
|
if (fs.lstatSync(`../static/webui/translations/${file}`).isFile() && file !== "en.js") {
|
||||||
@ -37,7 +36,7 @@ fs.readdirSync("../static/webui/translations").forEach(file => {
|
|||||||
fs.writeSync(fileHandle, ` ${key}: \`[UNTRANSLATED] ${value}\`,\n`);
|
fs.writeSync(fileHandle, ` ${key}: \`[UNTRANSLATED] ${value}\`,\n`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else if (line.length) {
|
||||||
fs.writeSync(fileHandle, line + "\n");
|
fs.writeSync(fileHandle, line + "\n");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
100
serversideVendorsService.ts
Normal file
100
serversideVendorsService.ts
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import { IMongoDate, IOid } from "@/src/types/commonTypes";
|
||||||
|
|
||||||
|
import ArchimedeanVendorManifest from "@/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json";
|
||||||
|
import DeimosEntratiFragmentVendorProductsManifest from "@/static/fixed_responses/getVendorInfo/DeimosEntratiFragmentVendorProductsManifest.json";
|
||||||
|
import DeimosFishmongerVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosFishmongerVendorManifest.json";
|
||||||
|
import DeimosHivemindCommisionsManifestFishmonger from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestFishmonger.json";
|
||||||
|
import DeimosHivemindCommisionsManifestPetVendor from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestPetVendor.json";
|
||||||
|
import DeimosHivemindCommisionsManifestProspector from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestProspector.json";
|
||||||
|
import DeimosHivemindCommisionsManifestTokenVendor from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestTokenVendor.json";
|
||||||
|
import DeimosHivemindCommisionsManifestWeaponsmith from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestWeaponsmith.json";
|
||||||
|
import DeimosHivemindTokenVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosHivemindTokenVendorManifest.json";
|
||||||
|
import DeimosPetVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosPetVendorManifest.json";
|
||||||
|
import DeimosProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosProspectorVendorManifest.json";
|
||||||
|
import DuviriAcrithisVendorManifest from "@/static/fixed_responses/getVendorInfo/DuviriAcrithisVendorManifest.json";
|
||||||
|
import EntratiLabsEntratiLabsCommisionsManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabsCommisionsManifest.json";
|
||||||
|
import EntratiLabsEntratiLabVendorManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabVendorManifest.json";
|
||||||
|
import GuildAdvertisementVendorManifest from "@/static/fixed_responses/getVendorInfo/GuildAdvertisementVendorManifest.json";
|
||||||
|
import HubsIronwakeDondaVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsIronwakeDondaVendorManifest.json";
|
||||||
|
import HubsPerrinSequenceWeaponVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsPerrinSequenceWeaponVendorManifest.json";
|
||||||
|
import HubsRailjackCrewMemberVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsRailjackCrewMemberVendorManifest.json";
|
||||||
|
import MaskSalesmanManifest from "@/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json";
|
||||||
|
import OstronFishmongerVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronFishmongerVendorManifest.json";
|
||||||
|
import OstronPetVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json";
|
||||||
|
import OstronProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronProspectorVendorManifest.json";
|
||||||
|
import RadioLegionIntermission12VendorManifest from "@/static/fixed_responses/getVendorInfo/RadioLegionIntermission12VendorManifest.json";
|
||||||
|
import SolarisDebtTokenVendorManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorManifest.json";
|
||||||
|
import SolarisDebtTokenVendorRepossessionsManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json";
|
||||||
|
import SolarisFishmongerVendorManifest from "@/static/fixed_responses/getVendorInfo/SolarisFishmongerVendorManifest.json";
|
||||||
|
import SolarisProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/SolarisProspectorVendorManifest.json";
|
||||||
|
import TeshinHardModeVendorManifest from "@/static/fixed_responses/getVendorInfo/TeshinHardModeVendorManifest.json";
|
||||||
|
import ZarimanCommisionsManifestArchimedean from "@/static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json";
|
||||||
|
|
||||||
|
interface IVendorManifest {
|
||||||
|
VendorInfo: {
|
||||||
|
_id: IOid;
|
||||||
|
TypeName: string;
|
||||||
|
ItemManifest: {
|
||||||
|
StoreItem: string;
|
||||||
|
ItemPrices?: { ItemType: string; ItemCount: number; ProductCategory: string }[];
|
||||||
|
Bin: string;
|
||||||
|
QuantityMultiplier: number;
|
||||||
|
Expiry: IMongoDate;
|
||||||
|
PurchaseQuantityLimit?: number;
|
||||||
|
RotatedWeekly?: boolean;
|
||||||
|
AllowMultipurchase: boolean;
|
||||||
|
Id: IOid;
|
||||||
|
}[];
|
||||||
|
Expiry: IMongoDate;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const vendorManifests: IVendorManifest[] = [
|
||||||
|
ArchimedeanVendorManifest,
|
||||||
|
DeimosEntratiFragmentVendorProductsManifest,
|
||||||
|
DeimosFishmongerVendorManifest,
|
||||||
|
DeimosHivemindCommisionsManifestFishmonger,
|
||||||
|
DeimosHivemindCommisionsManifestPetVendor,
|
||||||
|
DeimosHivemindCommisionsManifestProspector,
|
||||||
|
DeimosHivemindCommisionsManifestTokenVendor,
|
||||||
|
DeimosHivemindCommisionsManifestWeaponsmith,
|
||||||
|
DeimosHivemindTokenVendorManifest,
|
||||||
|
DeimosPetVendorManifest,
|
||||||
|
DeimosProspectorVendorManifest,
|
||||||
|
DuviriAcrithisVendorManifest,
|
||||||
|
EntratiLabsEntratiLabsCommisionsManifest,
|
||||||
|
EntratiLabsEntratiLabVendorManifest,
|
||||||
|
GuildAdvertisementVendorManifest,
|
||||||
|
HubsIronwakeDondaVendorManifest,
|
||||||
|
HubsPerrinSequenceWeaponVendorManifest,
|
||||||
|
HubsRailjackCrewMemberVendorManifest,
|
||||||
|
MaskSalesmanManifest,
|
||||||
|
OstronFishmongerVendorManifest,
|
||||||
|
OstronPetVendorManifest,
|
||||||
|
OstronProspectorVendorManifest,
|
||||||
|
RadioLegionIntermission12VendorManifest,
|
||||||
|
SolarisDebtTokenVendorManifest,
|
||||||
|
SolarisDebtTokenVendorRepossessionsManifest,
|
||||||
|
SolarisFishmongerVendorManifest,
|
||||||
|
SolarisProspectorVendorManifest,
|
||||||
|
TeshinHardModeVendorManifest,
|
||||||
|
ZarimanCommisionsManifestArchimedean
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getVendorManifestByTypeName = (typeName: string): IVendorManifest | undefined => {
|
||||||
|
for (const vendorManifest of vendorManifests) {
|
||||||
|
if (vendorManifest.VendorInfo.TypeName == typeName) {
|
||||||
|
return vendorManifest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getVendorManifestByOid = (oid: string): IVendorManifest | undefined => {
|
||||||
|
for (const vendorManifest of vendorManifests) {
|
||||||
|
if (vendorManifest.VendorInfo._id.$oid == oid) {
|
||||||
|
return vendorManifest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
19
src/app.ts
19
src/app.ts
@ -15,31 +15,14 @@ import { webuiRouter } from "@/src/routes/webui";
|
|||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
app.use((req, _res, next) => {
|
|
||||||
// 38.5.0 introduced "ezip" for encrypted body blobs and "e" for request verification only (encrypted body blobs with no application data).
|
|
||||||
// The bootstrapper decrypts it for us but having an unsupported Content-Encoding here would still be an issue for Express, so removing it.
|
|
||||||
if (req.headers["content-encoding"] == "ezip" || req.headers["content-encoding"] == "e") {
|
|
||||||
req.headers["content-encoding"] = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// U18 uses application/x-www-form-urlencoded even tho the data is JSON which Express doesn't like.
|
|
||||||
// U17 sets no Content-Type at all, which Express also doesn't like.
|
|
||||||
if (!req.headers["content-type"] || req.headers["content-type"] == "application/x-www-form-urlencoded") {
|
|
||||||
req.headers["content-type"] = "application/octet-stream";
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
app.use(bodyParser.raw());
|
app.use(bodyParser.raw());
|
||||||
app.use(express.json({ limit: "4mb" }));
|
app.use(express.json({ limit: "4mb" }));
|
||||||
app.use(bodyParser.text({ limit: "4mb" }));
|
app.use(bodyParser.text());
|
||||||
app.use(requestLogger);
|
app.use(requestLogger);
|
||||||
|
|
||||||
app.use("/api", apiRouter);
|
app.use("/api", apiRouter);
|
||||||
app.use("/", cacheRouter);
|
app.use("/", cacheRouter);
|
||||||
app.use("/custom", customRouter);
|
app.use("/custom", customRouter);
|
||||||
app.use("/dynamic", dynamicController);
|
|
||||||
app.use("/:id/dynamic", dynamicController);
|
app.use("/:id/dynamic", dynamicController);
|
||||||
app.use("/pay", payRouter);
|
app.use("/pay", payRouter);
|
||||||
app.use("/stats", statsRouter);
|
app.use("/stats", statsRouter);
|
||||||
|
@ -2,18 +2,15 @@ const millisecondsPerSecond = 1000;
|
|||||||
const secondsPerMinute = 60;
|
const secondsPerMinute = 60;
|
||||||
const minutesPerHour = 60;
|
const minutesPerHour = 60;
|
||||||
const hoursPerDay = 24;
|
const hoursPerDay = 24;
|
||||||
const daysPerWeek = 7;
|
|
||||||
|
|
||||||
const unixSecond = millisecondsPerSecond;
|
const unixSecond = millisecondsPerSecond;
|
||||||
const unixMinute = secondsPerMinute * millisecondsPerSecond;
|
const unixMinute = secondsPerMinute * millisecondsPerSecond;
|
||||||
const unixHour = unixMinute * minutesPerHour;
|
const unixHour = unixMinute * minutesPerHour;
|
||||||
const unixDay = hoursPerDay * unixHour;
|
const unixDay = hoursPerDay * unixHour;
|
||||||
const unixWeek = daysPerWeek * unixDay;
|
|
||||||
|
|
||||||
export const unixTimesInMs = {
|
export const unixTimesInMs = {
|
||||||
second: unixSecond,
|
second: unixSecond,
|
||||||
minute: unixMinute,
|
minute: unixMinute,
|
||||||
hour: unixHour,
|
hour: unixHour,
|
||||||
day: unixDay,
|
day: unixDay
|
||||||
week: unixWeek
|
|
||||||
};
|
};
|
||||||
|
@ -32,7 +32,7 @@ export const abortDojoComponentController: RequestHandler = async (req, res) =>
|
|||||||
if (request.DecoId) {
|
if (request.DecoId) {
|
||||||
removeDojoDeco(guild, request.ComponentId, request.DecoId);
|
removeDojoDeco(guild, request.ComponentId, request.DecoId);
|
||||||
} else {
|
} else {
|
||||||
await removeDojoRoom(guild, request.ComponentId);
|
removeDojoRoom(guild, request.ComponentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
await guild.save();
|
await guild.save();
|
||||||
|
@ -1,16 +1,11 @@
|
|||||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
import {
|
import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "@/src/helpers/rivenHelper";
|
||||||
createVeiledRivenFingerprint,
|
|
||||||
createUnveiledRivenFingerprint,
|
|
||||||
rivenRawToRealWeighted
|
|
||||||
} from "@/src/helpers/rivenHelper";
|
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { addMods, getInventory } from "@/src/services/inventoryService";
|
import { addMods, getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getRandomElement } from "@/src/services/rngService";
|
import { getRandomElement } from "@/src/services/rngService";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { ExportUpgrades } from "warframe-public-export-plus";
|
import { ExportUpgrades } from "warframe-public-export-plus";
|
||||||
import { config } from "@/src/services/configService";
|
|
||||||
|
|
||||||
export const activateRandomModController: RequestHandler = async (req, res) => {
|
export const activateRandomModController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
@ -22,10 +17,8 @@ export const activateRandomModController: RequestHandler = async (req, res) => {
|
|||||||
ItemCount: -1
|
ItemCount: -1
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType])!;
|
const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType]);
|
||||||
const fingerprint = config.instantFinishRivenChallenge
|
const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
|
||||||
? createUnveiledRivenFingerprint(ExportUpgrades[rivenType])
|
|
||||||
: createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
|
|
||||||
const upgradeIndex =
|
const upgradeIndex =
|
||||||
inventory.Upgrades.push({
|
inventory.Upgrades.push({
|
||||||
ItemType: rivenType,
|
ItemType: rivenType,
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|
||||||
import { Friendship } from "@/src/models/friendModel";
|
|
||||||
import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "@/src/services/friendService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { IFriendInfo } from "@/src/types/friendTypes";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
|
|
||||||
export const addFriendController: RequestHandler = async (req, res) => {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const payload = getJSONfromString<IAddFriendRequest>(String(req.body));
|
|
||||||
const promises: Promise<void>[] = [];
|
|
||||||
const newFriends: IFriendInfo[] = [];
|
|
||||||
if (payload.friend == "all") {
|
|
||||||
const [internalFriendships, externalFriendships] = await Promise.all([
|
|
||||||
Friendship.find({ owner: accountId }, "friend"),
|
|
||||||
Friendship.find({ friend: accountId }, "owner")
|
|
||||||
]);
|
|
||||||
for (const externalFriendship of externalFriendships) {
|
|
||||||
if (!internalFriendships.find(x => x.friend.equals(externalFriendship.owner))) {
|
|
||||||
promises.push(
|
|
||||||
Friendship.insertOne({
|
|
||||||
owner: accountId,
|
|
||||||
friend: externalFriendship.owner,
|
|
||||||
Note: externalFriendship.Note // TOVERIFY: Should the note be copied when accepting a friend request?
|
|
||||||
}) as unknown as Promise<void>
|
|
||||||
);
|
|
||||||
newFriends.push({
|
|
||||||
_id: toOid(externalFriendship.owner)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const externalFriendship = await Friendship.findOne({ owner: payload.friend, friend: accountId }, "Note");
|
|
||||||
if (externalFriendship) {
|
|
||||||
promises.push(
|
|
||||||
Friendship.insertOne({
|
|
||||||
owner: accountId,
|
|
||||||
friend: payload.friend,
|
|
||||||
Note: externalFriendship.Note
|
|
||||||
}) as unknown as Promise<void>
|
|
||||||
);
|
|
||||||
newFriends.push({
|
|
||||||
_id: { $oid: payload.friend }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const newFriend of newFriends) {
|
|
||||||
promises.push(addAccountDataToFriendInfo(newFriend));
|
|
||||||
promises.push(addInventoryDataToFriendInfo(newFriend));
|
|
||||||
}
|
|
||||||
await Promise.all(promises);
|
|
||||||
res.json({
|
|
||||||
Friends: newFriends
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IAddFriendRequest {
|
|
||||||
friend: string; // oid or "all" in which case all=1 is also a query parameter
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|
||||||
import { Account, Ignore } from "@/src/models/loginModel";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { IFriendInfo } from "@/src/types/friendTypes";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
|
|
||||||
export const addIgnoredUserController: RequestHandler = async (req, res) => {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const data = getJSONfromString<IAddIgnoredUserRequest>(String(req.body));
|
|
||||||
const ignoreeAccount = await Account.findOne(
|
|
||||||
{ DisplayName: data.playerName.substring(0, data.playerName.length - 1) },
|
|
||||||
"_id"
|
|
||||||
);
|
|
||||||
if (ignoreeAccount) {
|
|
||||||
await Ignore.create({ ignorer: accountId, ignoree: ignoreeAccount._id });
|
|
||||||
res.json({
|
|
||||||
Ignored: {
|
|
||||||
_id: toOid(ignoreeAccount._id),
|
|
||||||
DisplayName: data.playerName
|
|
||||||
} satisfies IFriendInfo
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.status(400).end();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IAddIgnoredUserRequest {
|
|
||||||
playerName: string;
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
|
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|
||||||
import { Friendship } from "@/src/models/friendModel";
|
|
||||||
import { Account } from "@/src/models/loginModel";
|
|
||||||
import { addInventoryDataToFriendInfo, areFriendsOfFriends } from "@/src/services/friendService";
|
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { IFriendInfo } from "@/src/types/friendTypes";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
|
|
||||||
export const addPendingFriendController: RequestHandler = async (req, res) => {
|
|
||||||
const payload = getJSONfromString<IAddPendingFriendRequest>(String(req.body));
|
|
||||||
|
|
||||||
const account = await Account.findOne({ DisplayName: payload.friend });
|
|
||||||
if (!account) {
|
|
||||||
res.status(400).end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const inventory = await getInventory(account._id.toString(), "Settings");
|
|
||||||
if (
|
|
||||||
inventory.Settings?.FriendInvRestriction == "GIFT_MODE_NONE" ||
|
|
||||||
(inventory.Settings?.FriendInvRestriction == "GIFT_MODE_FRIENDS" &&
|
|
||||||
!(await areFriendsOfFriends(account._id, accountId)))
|
|
||||||
) {
|
|
||||||
res.status(400).send("Friend Invite Restriction");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await Friendship.insertOne({
|
|
||||||
owner: accountId,
|
|
||||||
friend: account._id,
|
|
||||||
Note: payload.message
|
|
||||||
});
|
|
||||||
|
|
||||||
const friendInfo: IFriendInfo = {
|
|
||||||
_id: toOid(account._id),
|
|
||||||
DisplayName: account.DisplayName,
|
|
||||||
LastLogin: toMongoDate(account.LastLogin),
|
|
||||||
Note: payload.message
|
|
||||||
};
|
|
||||||
await addInventoryDataToFriendInfo(friendInfo);
|
|
||||||
res.json({
|
|
||||||
Friend: friendInfo
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IAddPendingFriendRequest {
|
|
||||||
friend: string;
|
|
||||||
message: string;
|
|
||||||
}
|
|
@ -1,117 +0,0 @@
|
|||||||
import { getJSONfromString, regexEscape } from "@/src/helpers/stringHelpers";
|
|
||||||
import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
|
|
||||||
import { createMessage } from "@/src/services/inboxService";
|
|
||||||
import { getEffectiveAvatarImageType, getInventory } from "@/src/services/inventoryService";
|
|
||||||
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
|
||||||
import { GuildPermission } from "@/src/types/guildTypes";
|
|
||||||
import { logger } from "@/src/utils/logger";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
import { ExportFlavour } from "warframe-public-export-plus";
|
|
||||||
|
|
||||||
export const addToAllianceController: RequestHandler = async (req, res) => {
|
|
||||||
// Check requester is a warlord in their guild
|
|
||||||
const account = await getAccountForRequest(req);
|
|
||||||
const guildMember = (await GuildMember.findOne({ accountId: account._id, status: 0 }))!;
|
|
||||||
if (guildMember.rank > 1) {
|
|
||||||
res.status(400).json({ Error: 104 });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check guild has invite permissions in the alliance
|
|
||||||
const allianceMember = (await AllianceMember.findOne({
|
|
||||||
allianceId: req.query.allianceId,
|
|
||||||
guildId: guildMember.guildId
|
|
||||||
}))!;
|
|
||||||
if (!(allianceMember.Permissions & GuildPermission.Recruiter)) {
|
|
||||||
res.status(400).json({ Error: 104 });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find clan to invite
|
|
||||||
const payload = getJSONfromString<IAddToAllianceRequest>(String(req.body));
|
|
||||||
const guilds = await Guild.find(
|
|
||||||
{
|
|
||||||
Name:
|
|
||||||
payload.clanName.indexOf("#") == -1
|
|
||||||
? new RegExp("^" + regexEscape(payload.clanName) + "#...$")
|
|
||||||
: payload.clanName
|
|
||||||
},
|
|
||||||
"Name"
|
|
||||||
);
|
|
||||||
if (guilds.length == 0) {
|
|
||||||
res.status(400).json({ Error: 101 });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (guilds.length > 1) {
|
|
||||||
const choices: IGuildChoice[] = [];
|
|
||||||
for (const guild of guilds) {
|
|
||||||
choices.push({
|
|
||||||
OriginalPlatform: 0,
|
|
||||||
Name: guild.Name
|
|
||||||
});
|
|
||||||
}
|
|
||||||
res.json(choices);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add clan as a pending alliance member
|
|
||||||
try {
|
|
||||||
await AllianceMember.insertOne({
|
|
||||||
allianceId: req.query.allianceId,
|
|
||||||
guildId: guilds[0]._id,
|
|
||||||
Pending: true,
|
|
||||||
Permissions: 0
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
logger.debug(`alliance invite failed due to ${String(e)}`);
|
|
||||||
res.status(400).json({ Error: 102 });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send inbox message to founding warlord
|
|
||||||
// TOVERIFY: Should other warlords get this as well?
|
|
||||||
// TOVERIFY: Who/what should the sender be?
|
|
||||||
// TOVERIFY: Should this message be highPriority?
|
|
||||||
const invitedClanOwnerMember = (await GuildMember.findOne({ guildId: guilds[0]._id, rank: 0 }))!;
|
|
||||||
const senderInventory = await getInventory(account._id.toString(), "ActiveAvatarImageType");
|
|
||||||
const senderGuild = (await Guild.findById(allianceMember.guildId, "Name"))!;
|
|
||||||
const alliance = (await Alliance.findById(req.query.allianceId as string, "Name"))!;
|
|
||||||
await createMessage(invitedClanOwnerMember.accountId, [
|
|
||||||
{
|
|
||||||
sndr: getSuffixedName(account),
|
|
||||||
msg: "/Lotus/Language/Menu/Mailbox_AllianceInvite_Body",
|
|
||||||
arg: [
|
|
||||||
{
|
|
||||||
Key: "THEIR_CLAN",
|
|
||||||
Tag: senderGuild.Name
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "CLAN",
|
|
||||||
Tag: guilds[0].Name
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "ALLIANCE",
|
|
||||||
Tag: alliance.Name
|
|
||||||
}
|
|
||||||
],
|
|
||||||
sub: "/Lotus/Language/Menu/Mailbox_AllianceInvite_Title",
|
|
||||||
icon: ExportFlavour[getEffectiveAvatarImageType(senderInventory)].icon,
|
|
||||||
contextInfo: alliance._id.toString(),
|
|
||||||
highPriority: true,
|
|
||||||
acceptAction: "ALLIANCE_INVITE",
|
|
||||||
declineAction: "ALLIANCE_INVITE",
|
|
||||||
hasAccountAction: true
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
res.end();
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IAddToAllianceRequest {
|
|
||||||
clanName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IGuildChoice {
|
|
||||||
OriginalPlatform: number;
|
|
||||||
Name: string;
|
|
||||||
}
|
|
@ -1,111 +1,77 @@
|
|||||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
|
||||||
import { Guild, GuildMember } from "@/src/models/guildModel";
|
import { Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
import { Account } from "@/src/models/loginModel";
|
import { Account } from "@/src/models/loginModel";
|
||||||
import { addInventoryDataToFriendInfo, areFriends } from "@/src/services/friendService";
|
import { fillInInventoryDataForGuildMember, hasGuildPermission } from "@/src/services/guildService";
|
||||||
import { hasGuildPermission } from "@/src/services/guildService";
|
|
||||||
import { createMessage } from "@/src/services/inboxService";
|
import { createMessage } from "@/src/services/inboxService";
|
||||||
import { getEffectiveAvatarImageType, getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountForRequest, getAccountIdForRequest, getSuffixedName } from "@/src/services/loginService";
|
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||||
import { IOid } from "@/src/types/commonTypes";
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
import { GuildPermission, IGuildMemberClient } from "@/src/types/guildTypes";
|
import { GuildPermission, IGuildMemberClient } from "@/src/types/guildTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { ExportFlavour } from "warframe-public-export-plus";
|
import { ExportFlavour } from "warframe-public-export-plus";
|
||||||
|
|
||||||
export const addToGuildController: RequestHandler = async (req, res) => {
|
export const addToGuildController: RequestHandler = async (req, res) => {
|
||||||
const payload = JSON.parse(String(req.body)) as IAddToGuildRequest;
|
const payload = JSON.parse(String(req.body)) as IAddToGuildRequest;
|
||||||
|
|
||||||
if ("UserName" in payload) {
|
const account = await Account.findOne({ DisplayName: payload.UserName });
|
||||||
// Clan recruiter sending an invite
|
if (!account) {
|
||||||
|
res.status(400).json("Username does not exist");
|
||||||
const account = await Account.findOne({ DisplayName: payload.UserName });
|
return;
|
||||||
if (!account) {
|
|
||||||
res.status(400).json("Username does not exist");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const senderAccount = await getAccountForRequest(req);
|
|
||||||
const inventory = await getInventory(account._id.toString(), "Settings");
|
|
||||||
if (
|
|
||||||
inventory.Settings?.GuildInvRestriction == "GIFT_MODE_NONE" ||
|
|
||||||
(inventory.Settings?.GuildInvRestriction == "GIFT_MODE_FRIENDS" &&
|
|
||||||
!(await areFriends(account._id, senderAccount._id)))
|
|
||||||
) {
|
|
||||||
res.status(400).json("Invite restricted");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const guild = (await Guild.findById(payload.GuildId.$oid, "Name Ranks"))!;
|
|
||||||
if (!(await hasGuildPermission(guild, senderAccount._id.toString(), GuildPermission.Recruiter))) {
|
|
||||||
res.status(400).json("Invalid permission");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await GuildMember.insertOne({
|
|
||||||
accountId: account._id,
|
|
||||||
guildId: payload.GuildId.$oid,
|
|
||||||
status: 2 // outgoing invite
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
logger.debug(`guild invite failed due to ${String(e)}`);
|
|
||||||
res.status(400).json("User already invited to clan");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const senderInventory = await getInventory(senderAccount._id.toString(), "ActiveAvatarImageType");
|
|
||||||
await createMessage(account._id, [
|
|
||||||
{
|
|
||||||
sndr: getSuffixedName(senderAccount),
|
|
||||||
msg: "/Lotus/Language/Menu/Mailbox_ClanInvite_Body",
|
|
||||||
arg: [
|
|
||||||
{
|
|
||||||
Key: "clan",
|
|
||||||
Tag: guild.Name
|
|
||||||
}
|
|
||||||
],
|
|
||||||
sub: "/Lotus/Language/Menu/Mailbox_ClanInvite_Title",
|
|
||||||
icon: ExportFlavour[getEffectiveAvatarImageType(senderInventory)].icon,
|
|
||||||
contextInfo: payload.GuildId.$oid,
|
|
||||||
highPriority: true,
|
|
||||||
acceptAction: "GUILD_INVITE",
|
|
||||||
declineAction: "GUILD_INVITE",
|
|
||||||
hasAccountAction: true
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
const member: IGuildMemberClient = {
|
|
||||||
_id: { $oid: account._id.toString() },
|
|
||||||
DisplayName: account.DisplayName,
|
|
||||||
LastLogin: toMongoDate(account.LastLogin),
|
|
||||||
Rank: 7,
|
|
||||||
Status: 2
|
|
||||||
};
|
|
||||||
await addInventoryDataToFriendInfo(member);
|
|
||||||
res.json({ NewMember: member });
|
|
||||||
} else if ("RequestMsg" in payload) {
|
|
||||||
// Player applying to join a clan
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
try {
|
|
||||||
await GuildMember.insertOne({
|
|
||||||
accountId,
|
|
||||||
guildId: payload.GuildId.$oid,
|
|
||||||
status: 1, // incoming invite
|
|
||||||
RequestMsg: payload.RequestMsg,
|
|
||||||
RequestExpiry: new Date(Date.now() + 14 * 86400 * 1000) // TOVERIFY: I can't find any good information about this with regards to live, but 2 weeks seem reasonable.
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
logger.debug(`guild invite failed due to ${String(e)}`);
|
|
||||||
res.status(400).send("Already requested");
|
|
||||||
}
|
|
||||||
res.end();
|
|
||||||
} else {
|
|
||||||
logger.error(`data provided to ${req.path}: ${String(req.body)}`);
|
|
||||||
res.status(400).end();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const guild = (await Guild.findOne({ _id: payload.GuildId.$oid }, "Name"))!;
|
||||||
|
const senderAccount = await getAccountForRequest(req);
|
||||||
|
if (!(await hasGuildPermission(guild, senderAccount._id.toString(), GuildPermission.Recruiter))) {
|
||||||
|
res.status(400).json("Invalid permission");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
await GuildMember.exists({
|
||||||
|
accountId: account._id,
|
||||||
|
guildId: payload.GuildId.$oid
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
res.status(400).json("User already invited to clan");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await GuildMember.insertOne({
|
||||||
|
accountId: account._id,
|
||||||
|
guildId: payload.GuildId.$oid,
|
||||||
|
status: 2 // outgoing invite
|
||||||
|
});
|
||||||
|
|
||||||
|
const senderInventory = await getInventory(senderAccount._id.toString(), "ActiveAvatarImageType");
|
||||||
|
await createMessage(account._id.toString(), [
|
||||||
|
{
|
||||||
|
sndr: getSuffixedName(senderAccount),
|
||||||
|
msg: "/Lotus/Language/Menu/Mailbox_ClanInvite_Body",
|
||||||
|
arg: [
|
||||||
|
{
|
||||||
|
Key: "clan",
|
||||||
|
Tag: guild.Name
|
||||||
|
}
|
||||||
|
],
|
||||||
|
sub: "/Lotus/Language/Menu/Mailbox_ClanInvite_Title",
|
||||||
|
icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
|
||||||
|
contextInfo: payload.GuildId.$oid,
|
||||||
|
highPriority: true,
|
||||||
|
acceptAction: "GUILD_INVITE",
|
||||||
|
declineAction: "GUILD_INVITE",
|
||||||
|
hasAccountAction: true
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const member: IGuildMemberClient = {
|
||||||
|
_id: { $oid: account._id.toString() },
|
||||||
|
DisplayName: account.DisplayName,
|
||||||
|
Rank: 7,
|
||||||
|
Status: 2
|
||||||
|
};
|
||||||
|
await fillInInventoryDataForGuildMember(member);
|
||||||
|
res.json({ NewMember: member });
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IAddToGuildRequest {
|
interface IAddToGuildRequest {
|
||||||
UserName?: string;
|
UserName: string;
|
||||||
GuildId: IOid;
|
GuildId: IOid;
|
||||||
RequestMsg?: string;
|
|
||||||
}
|
}
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
|
|
||||||
export const adoptPetController: RequestHandler = async (req, res) => {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const inventory = await getInventory(accountId, "KubrowPets");
|
|
||||||
const data = getJSONfromString<IAdoptPetRequest>(String(req.body));
|
|
||||||
const details = inventory.KubrowPets.id(data.petId)!.Details!;
|
|
||||||
details.Name = data.name;
|
|
||||||
await inventory.save();
|
|
||||||
res.json({
|
|
||||||
petId: data.petId,
|
|
||||||
newName: data.name
|
|
||||||
} satisfies IAdoptPetResponse);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IAdoptPetRequest {
|
|
||||||
petId: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IAdoptPetResponse {
|
|
||||||
petId: string;
|
|
||||||
newName: string;
|
|
||||||
}
|
|
@ -1,9 +1,9 @@
|
|||||||
import { fromOid, toOid } from "@/src/helpers/inventoryHelpers";
|
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "@/src/helpers/rivenHelper";
|
import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "@/src/helpers/rivenHelper";
|
||||||
import { addMiscItems, addMods, getInventory } from "@/src/services/inventoryService";
|
import { addMiscItems, addMods, getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getRandomElement, getRandomWeightedReward, getRandomWeightedRewardUc } from "@/src/services/rngService";
|
import { getRandomElement, getRandomWeightedReward, getRandomWeightedRewardUc } from "@/src/services/rngService";
|
||||||
import { IUpgradeFromClient } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { ExportBoosterPacks, ExportUpgrades, TRarity } from "warframe-public-export-plus";
|
import { ExportBoosterPacks, ExportUpgrades, TRarity } from "warframe-public-export-plus";
|
||||||
|
|
||||||
@ -24,11 +24,11 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
payload.Consumed.forEach(upgrade => {
|
payload.Consumed.forEach(upgrade => {
|
||||||
inventory.Upgrades.pull({ _id: fromOid(upgrade.ItemId) });
|
inventory.Upgrades.pull({ _id: upgrade.ItemId.$oid });
|
||||||
});
|
});
|
||||||
|
|
||||||
const rawRivenType = getRandomRawRivenType();
|
const rawRivenType = getRandomRawRivenType();
|
||||||
const rivenType = getRandomElement(rivenRawToRealWeighted[rawRivenType])!;
|
const rivenType = getRandomElement(rivenRawToRealWeighted[rawRivenType]);
|
||||||
const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
|
const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
|
||||||
|
|
||||||
const upgradeIndex =
|
const upgradeIndex =
|
||||||
@ -53,56 +53,33 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
|
|||||||
RARE: 0,
|
RARE: 0,
|
||||||
LEGENDARY: 0
|
LEGENDARY: 0
|
||||||
};
|
};
|
||||||
let forcedPolarity: string | undefined;
|
|
||||||
payload.Consumed.forEach(upgrade => {
|
payload.Consumed.forEach(upgrade => {
|
||||||
const meta = ExportUpgrades[upgrade.ItemType];
|
const meta = ExportUpgrades[upgrade.ItemType];
|
||||||
counts[meta.rarity] += upgrade.ItemCount;
|
counts[meta.rarity] += upgrade.ItemCount;
|
||||||
if (fromOid(upgrade.ItemId) != "000000000000000000000000") {
|
addMods(inventory, [
|
||||||
inventory.Upgrades.pull({ _id: fromOid(upgrade.ItemId) });
|
{
|
||||||
} else {
|
ItemType: upgrade.ItemType,
|
||||||
addMods(inventory, [
|
ItemCount: upgrade.ItemCount * -1
|
||||||
{
|
}
|
||||||
ItemType: upgrade.ItemType,
|
]);
|
||||||
ItemCount: upgrade.ItemCount * -1
|
});
|
||||||
}
|
|
||||||
]);
|
// Based on the table on https://wiki.warframe.com/w/Transmutation
|
||||||
}
|
const weights: Record<TRarity, number> = {
|
||||||
if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/AttackTransmuteCore") {
|
COMMON: counts.COMMON * 95 + counts.UNCOMMON * 15 + counts.RARE * 4,
|
||||||
forcedPolarity = "AP_ATTACK";
|
UNCOMMON: counts.COMMON * 4 + counts.UNCOMMON * 80 + counts.RARE * 10,
|
||||||
} else if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/DefenseTransmuteCore") {
|
RARE: counts.COMMON * 1 + counts.UNCOMMON * 5 + counts.RARE * 50,
|
||||||
forcedPolarity = "AP_DEFENSE";
|
LEGENDARY: 0
|
||||||
} else if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/TacticTransmuteCore") {
|
};
|
||||||
forcedPolarity = "AP_TACTIC";
|
|
||||||
|
const options: { uniqueName: string; rarity: TRarity }[] = [];
|
||||||
|
Object.entries(ExportUpgrades).forEach(([uniqueName, upgrade]) => {
|
||||||
|
if (upgrade.canBeTransmutation) {
|
||||||
|
options.push({ uniqueName, rarity: upgrade.rarity });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let newModType: string | undefined;
|
const newModType = getRandomWeightedReward(options, weights)!.uniqueName;
|
||||||
for (const specialModSet of specialModSets) {
|
|
||||||
if (specialModSet.indexOf(payload.Consumed[0].ItemType) != -1) {
|
|
||||||
newModType = getRandomElement(specialModSet);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!newModType) {
|
|
||||||
// Based on the table on https://wiki.warframe.com/w/Transmutation
|
|
||||||
const weights: Record<TRarity, number> = {
|
|
||||||
COMMON: counts.COMMON * 95 + counts.UNCOMMON * 15 + counts.RARE * 4,
|
|
||||||
UNCOMMON: counts.COMMON * 4 + counts.UNCOMMON * 80 + counts.RARE * 10,
|
|
||||||
RARE: counts.COMMON * 1 + counts.UNCOMMON * 5 + counts.RARE * 50,
|
|
||||||
LEGENDARY: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
const options: { uniqueName: string; rarity: TRarity }[] = [];
|
|
||||||
Object.entries(ExportUpgrades).forEach(([uniqueName, upgrade]) => {
|
|
||||||
if (upgrade.canBeTransmutation && (!forcedPolarity || upgrade.polarity == forcedPolarity)) {
|
|
||||||
options.push({ uniqueName, rarity: upgrade.rarity });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
newModType = getRandomWeightedReward(options, weights)!.uniqueName;
|
|
||||||
}
|
|
||||||
|
|
||||||
addMods(inventory, [
|
addMods(inventory, [
|
||||||
{
|
{
|
||||||
ItemType: newModType,
|
ItemType: newModType,
|
||||||
@ -128,41 +105,20 @@ const getRandomRawRivenType = (): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface IArtifactTransmutationRequest {
|
interface IArtifactTransmutationRequest {
|
||||||
Upgrade: IUpgradeFromClient;
|
Upgrade: IAgnosticUpgradeClient;
|
||||||
LevelDiff: number;
|
LevelDiff: number;
|
||||||
Consumed: IUpgradeFromClient[];
|
Consumed: IAgnosticUpgradeClient[];
|
||||||
Cost: number;
|
Cost: number;
|
||||||
FusionPointCost: number;
|
FusionPointCost: number;
|
||||||
RivenTransmute?: boolean;
|
RivenTransmute?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const specialModSets: string[][] = [
|
interface IAgnosticUpgradeClient {
|
||||||
[
|
ItemType: string;
|
||||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod",
|
ItemId: IOid;
|
||||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalTwoMod",
|
FromSKU: boolean;
|
||||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalThreeMod",
|
UpgradeFingerprint: string;
|
||||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalFourMod",
|
PendingRerollFingerprint: string;
|
||||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalFiveMod",
|
ItemCount: number;
|
||||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalSixMod",
|
LastAdded: IOid;
|
||||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalSevenMod",
|
}
|
||||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalEightMod",
|
|
||||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalWildcardMod"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusOneMod",
|
|
||||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusTwoMod",
|
|
||||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusThreeMod",
|
|
||||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusFourMod",
|
|
||||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusFiveMod",
|
|
||||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusSixMod",
|
|
||||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusSevenMod",
|
|
||||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusEightMod"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndSpeedOnUseMod",
|
|
||||||
"/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndWeaponDamageOnUseMod",
|
|
||||||
"/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusLargeOnSingleUseMod",
|
|
||||||
"/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusOnUseMod",
|
|
||||||
"/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusSmallOnSingleUseMod"
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
import { GuildAd } from "@/src/models/guildModel";
|
|
||||||
import { getGuildForRequestEx, hasGuildPermission } from "@/src/services/guildService";
|
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { GuildPermission } from "@/src/types/guildTypes";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
|
|
||||||
export const cancelGuildAdvertisementController: RequestHandler = async (req, res) => {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const inventory = await getInventory(accountId, "GuildId");
|
|
||||||
const guild = await getGuildForRequestEx(req, inventory);
|
|
||||||
if (!(await hasGuildPermission(guild, accountId, GuildPermission.Advertiser))) {
|
|
||||||
res.status(400).end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await GuildAd.deleteOne({ GuildId: guild._id });
|
|
||||||
|
|
||||||
res.end();
|
|
||||||
};
|
|
@ -15,12 +15,6 @@ export const changeDojoRootController: RequestHandler = async (req, res) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Example POST body: {"pivot":[0, 0, -64],"components":"{\"670429301ca0a63848ccc467\":{\"R\":[0,0,0],\"P\":[0,3,32]},\"6704254a1ca0a63848ccb33c\":{\"R\":[0,0,0],\"P\":[0,9.25,-32]},\"670429461ca0a63848ccc731\":{\"R\":[-90,0,0],\"P\":[-47.999992370605,3,16]}}"}
|
|
||||||
if (req.body) {
|
|
||||||
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
|
||||||
throw new Error("dojo reparent operation should not need deco repositioning"); // because we always provide SortId
|
|
||||||
}
|
|
||||||
|
|
||||||
const idToNode: Record<string, INode> = {};
|
const idToNode: Record<string, INode> = {};
|
||||||
guild.DojoComponents.forEach(x => {
|
guild.DojoComponents.forEach(x => {
|
||||||
idToNode[x._id.toString()] = {
|
idToNode[x._id.toString()] = {
|
||||||
@ -49,13 +43,23 @@ export const changeDojoRootController: RequestHandler = async (req, res) => {
|
|||||||
newRoot.component.pp = undefined;
|
newRoot.component.pp = undefined;
|
||||||
newRoot.parent = undefined;
|
newRoot.parent = undefined;
|
||||||
|
|
||||||
// Set/update SortId in top-to-bottom order
|
// Don't even ask me why this is needed because I don't know either
|
||||||
const stack: INode[] = [newRoot];
|
const stack: INode[] = [newRoot];
|
||||||
|
let i = 0;
|
||||||
|
const idMap: Record<string, Types.ObjectId> = {};
|
||||||
while (stack.length != 0) {
|
while (stack.length != 0) {
|
||||||
const top = stack.shift()!;
|
const top = stack.shift()!;
|
||||||
top.component.SortId = new Types.ObjectId();
|
idMap[top.component._id.toString()] = new Types.ObjectId(
|
||||||
|
(++i).toString(16).padStart(8, "0") + top.component._id.toString().substr(8)
|
||||||
|
);
|
||||||
top.children.forEach(x => stack.push(x));
|
top.children.forEach(x => stack.push(x));
|
||||||
}
|
}
|
||||||
|
guild.DojoComponents.forEach(x => {
|
||||||
|
x._id = idMap[x._id.toString()];
|
||||||
|
if (x.pi) {
|
||||||
|
x.pi = idMap[x.pi.toString()];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
logger.debug("New tree:\n" + treeToString(newRoot));
|
logger.debug("New tree:\n" + treeToString(newRoot));
|
||||||
|
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
export const checkDailyMissionBonusController: RequestHandler = async (req, res) => {
|
const checkDailyMissionBonusController: RequestHandler = (_req, res) => {
|
||||||
const account = await getAccountForRequest(req);
|
const data = Buffer.from([
|
||||||
const today = Math.trunc(Date.now() / 86400000) * 86400;
|
0x44, 0x61, 0x69, 0x6c, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x6f, 0x6e, 0x75, 0x73, 0x3a,
|
||||||
if (account.DailyFirstWinDate != today) {
|
0x31, 0x2d, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x50, 0x56, 0x50, 0x57, 0x69, 0x6e, 0x42, 0x6f, 0x6e, 0x75, 0x73,
|
||||||
res.send("DailyMissionBonus:1-DailyPVPWinBonus:1\n");
|
0x3a, 0x31, 0x0a
|
||||||
} else {
|
]);
|
||||||
res.send("DailyMissionBonus:0-DailyPVPWinBonus:1\n");
|
res.writeHead(200, {
|
||||||
}
|
"Content-Type": "text/html",
|
||||||
|
"Content-Length": data.length
|
||||||
|
});
|
||||||
|
res.end(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export { checkDailyMissionBonusController };
|
||||||
|
@ -4,34 +4,31 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { getRecipe } from "@/src/services/itemDataService";
|
import { getRecipe } from "@/src/services/itemDataService";
|
||||||
import { IOid, IOidWithLegacySupport } from "@/src/types/commonTypes";
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import {
|
import {
|
||||||
getInventory,
|
getInventory,
|
||||||
updateCurrency,
|
updateCurrency,
|
||||||
addItem,
|
addItem,
|
||||||
|
addMiscItems,
|
||||||
addRecipes,
|
addRecipes,
|
||||||
occupySlot,
|
occupySlot
|
||||||
combineInventoryChanges,
|
|
||||||
addKubrowPetPrint
|
|
||||||
} from "@/src/services/inventoryService";
|
} from "@/src/services/inventoryService";
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
import { InventorySlot, IPendingRecipeDatabase, Status } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IMiscItem, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { toOid2 } from "@/src/helpers/inventoryHelpers";
|
|
||||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
|
||||||
import { IRecipe } from "warframe-public-export-plus";
|
|
||||||
import { config } from "@/src/services/configService";
|
|
||||||
|
|
||||||
interface IClaimCompletedRecipeRequest {
|
export interface IClaimCompletedRecipeRequest {
|
||||||
RecipeIds: IOid[];
|
RecipeIds: IOid[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const claimCompletedRecipeController: RequestHandler = async (req, res) => {
|
export const claimCompletedRecipeController: RequestHandler = async (req, res) => {
|
||||||
const claimCompletedRecipeRequest = getJSONfromString<IClaimCompletedRecipeRequest>(String(req.body));
|
const claimCompletedRecipeRequest = getJSONfromString<IClaimCompletedRecipeRequest>(String(req.body));
|
||||||
const account = await getAccountForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await getInventory(account._id.toString());
|
if (!accountId) throw new Error("no account id");
|
||||||
|
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
const pendingRecipe = inventory.PendingRecipes.id(claimCompletedRecipeRequest.RecipeIds[0].$oid);
|
const pendingRecipe = inventory.PendingRecipes.id(claimCompletedRecipeRequest.RecipeIds[0].$oid);
|
||||||
if (!pendingRecipe) {
|
if (!pendingRecipe) {
|
||||||
throw new Error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$oid}`);
|
throw new Error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$oid}`);
|
||||||
@ -50,14 +47,39 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (req.query.cancel) {
|
if (req.query.cancel) {
|
||||||
const inventoryChanges: IInventoryChanges = {};
|
const inventoryChanges: IInventoryChanges = {
|
||||||
await refundRecipeIngredients(inventory, inventoryChanges, recipe, pendingRecipe);
|
...updateCurrency(inventory, recipe.buildPrice * -1, false)
|
||||||
|
};
|
||||||
|
|
||||||
|
const nonMiscItemIngredients = new Set();
|
||||||
|
for (const category of ["LongGuns", "Pistols", "Melee"] as const) {
|
||||||
|
if (pendingRecipe[category]) {
|
||||||
|
pendingRecipe[category].forEach(item => {
|
||||||
|
const index = inventory[category].push(item) - 1;
|
||||||
|
inventoryChanges[category] ??= [];
|
||||||
|
inventoryChanges[category].push(inventory[category][index].toJSON<IEquipmentClient>());
|
||||||
|
nonMiscItemIngredients.add(item.ItemType);
|
||||||
|
|
||||||
|
occupySlot(inventory, InventorySlot.WEAPONS, false);
|
||||||
|
inventoryChanges.WeaponBin ??= { Slots: 0 };
|
||||||
|
inventoryChanges.WeaponBin.Slots -= 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const miscItemChanges: IMiscItem[] = [];
|
||||||
|
recipe.ingredients.forEach(ingredient => {
|
||||||
|
if (!nonMiscItemIngredients.has(ingredient.ItemType)) {
|
||||||
|
miscItemChanges.push(ingredient);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
addMiscItems(inventory, miscItemChanges);
|
||||||
|
inventoryChanges.MiscItems = miscItemChanges;
|
||||||
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json(inventoryChanges); // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
|
res.json(inventoryChanges); // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
|
||||||
} else {
|
} else {
|
||||||
logger.debug("Claiming Recipe", { recipe, pendingRecipe });
|
logger.debug("Claiming Recipe", { recipe, pendingRecipe });
|
||||||
|
|
||||||
let BrandedSuits: undefined | IOidWithLegacySupport[];
|
|
||||||
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
|
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
|
||||||
inventory.PendingSpectreLoadouts ??= [];
|
inventory.PendingSpectreLoadouts ??= [];
|
||||||
inventory.SpectreLoadouts ??= [];
|
inventory.SpectreLoadouts ??= [];
|
||||||
@ -77,15 +99,9 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
|
|||||||
inventory.SpectreLoadouts.push(inventory.PendingSpectreLoadouts[pendingLoadoutIndex]);
|
inventory.SpectreLoadouts.push(inventory.PendingSpectreLoadouts[pendingLoadoutIndex]);
|
||||||
inventory.PendingSpectreLoadouts.splice(pendingLoadoutIndex, 1);
|
inventory.PendingSpectreLoadouts.splice(pendingLoadoutIndex, 1);
|
||||||
}
|
}
|
||||||
} else if (recipe.secretIngredientAction == "SIA_UNBRAND") {
|
|
||||||
inventory.BrandedSuits!.splice(
|
|
||||||
inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)),
|
|
||||||
1
|
|
||||||
);
|
|
||||||
BrandedSuits = [toOid2(pendingRecipe.SuitToUnbrand!, account.BuildLabel)];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let InventoryChanges: IInventoryChanges = {};
|
let InventoryChanges = {};
|
||||||
if (recipe.consumeOnUse) {
|
if (recipe.consumeOnUse) {
|
||||||
addRecipes(inventory, [
|
addRecipes(inventory, [
|
||||||
{
|
{
|
||||||
@ -95,87 +111,16 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
if (req.query.rush) {
|
if (req.query.rush) {
|
||||||
const end = Math.trunc(pendingRecipe.CompletionDate.getTime() / 1000);
|
|
||||||
const start = end - recipe.buildTime;
|
|
||||||
const secondsElapsed = Math.trunc(Date.now() / 1000) - start;
|
|
||||||
const progress = secondsElapsed / recipe.buildTime;
|
|
||||||
logger.debug(`rushing recipe at ${Math.trunc(progress * 100)}% completion`);
|
|
||||||
const cost = Math.round(recipe.skipBuildTimePrice * (1 - (progress - 0.5)));
|
|
||||||
InventoryChanges = {
|
InventoryChanges = {
|
||||||
...InventoryChanges,
|
...InventoryChanges,
|
||||||
...updateCurrency(inventory, cost, true)
|
...updateCurrency(inventory, recipe.skipBuildTimePrice, true)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
InventoryChanges = {
|
||||||
if (recipe.secretIngredientAction == "SIA_CREATE_KUBROW") {
|
...InventoryChanges,
|
||||||
const pet = inventory.KubrowPets.id(pendingRecipe.KubrowPet!)!;
|
...(await addItem(inventory, recipe.resultType, recipe.num, false)).InventoryChanges
|
||||||
if (pet.Details!.HatchDate!.getTime() > Date.now()) {
|
};
|
||||||
pet.Details!.HatchDate = new Date();
|
|
||||||
}
|
|
||||||
let canSetActive = true;
|
|
||||||
for (const pet of inventory.KubrowPets) {
|
|
||||||
if (pet.Details!.Status == Status.StatusAvailable) {
|
|
||||||
canSetActive = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pet.Details!.Status = canSetActive ? Status.StatusAvailable : Status.StatusStasis;
|
|
||||||
} else if (recipe.secretIngredientAction == "SIA_DISTILL_PRINT") {
|
|
||||||
const pet = inventory.KubrowPets.id(pendingRecipe.KubrowPet!)!;
|
|
||||||
addKubrowPetPrint(inventory, pet, InventoryChanges);
|
|
||||||
} else if (recipe.secretIngredientAction != "SIA_UNBRAND") {
|
|
||||||
InventoryChanges = {
|
|
||||||
...InventoryChanges,
|
|
||||||
...(await addItem(
|
|
||||||
inventory,
|
|
||||||
recipe.resultType,
|
|
||||||
recipe.num,
|
|
||||||
false,
|
|
||||||
undefined,
|
|
||||||
pendingRecipe.TargetFingerprint
|
|
||||||
))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
config.claimingBlueprintRefundsIngredients &&
|
|
||||||
recipe.secretIngredientAction != "SIA_CREATE_KUBROW" // Can't refund the egg
|
|
||||||
) {
|
|
||||||
await refundRecipeIngredients(inventory, InventoryChanges, recipe, pendingRecipe);
|
|
||||||
}
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({ InventoryChanges, BrandedSuits });
|
res.json({ InventoryChanges });
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const refundRecipeIngredients = async (
|
|
||||||
inventory: TInventoryDatabaseDocument,
|
|
||||||
inventoryChanges: IInventoryChanges,
|
|
||||||
recipe: IRecipe,
|
|
||||||
pendingRecipe: IPendingRecipeDatabase
|
|
||||||
): Promise<void> => {
|
|
||||||
updateCurrency(inventory, recipe.buildPrice * -1, false, inventoryChanges);
|
|
||||||
|
|
||||||
const equipmentIngredients = new Set();
|
|
||||||
for (const category of ["LongGuns", "Pistols", "Melee"] as const) {
|
|
||||||
if (pendingRecipe[category]) {
|
|
||||||
pendingRecipe[category].forEach(item => {
|
|
||||||
const index = inventory[category].push(item) - 1;
|
|
||||||
inventoryChanges[category] ??= [];
|
|
||||||
inventoryChanges[category].push(inventory[category][index].toJSON<IEquipmentClient>());
|
|
||||||
equipmentIngredients.add(item.ItemType);
|
|
||||||
|
|
||||||
occupySlot(inventory, InventorySlot.WEAPONS, false);
|
|
||||||
inventoryChanges.WeaponBin ??= { Slots: 0 };
|
|
||||||
inventoryChanges.WeaponBin.Slots -= 1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const ingredient of recipe.ingredients) {
|
|
||||||
if (!equipmentIngredients.has(ingredient.ItemType)) {
|
|
||||||
combineInventoryChanges(
|
|
||||||
inventoryChanges,
|
|
||||||
await addItem(inventory, ingredient.ItemType, ingredient.ItemCount)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
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 { addFusionPoints, getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ export const claimLibraryDailyTaskRewardController: RequestHandler = async (req,
|
|||||||
}
|
}
|
||||||
syndicate.Standing += rewardStanding;
|
syndicate.Standing += rewardStanding;
|
||||||
|
|
||||||
addFusionPoints(inventory, 80 * rewardQuantity);
|
inventory.FusionPoints += 80 * rewardQuantity;
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
|
@ -7,8 +7,6 @@ export const clearDialogueHistoryController: RequestHandler = async (req, res) =
|
|||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
const request = JSON.parse(String(req.body)) as IClearDialogueRequest;
|
const request = JSON.parse(String(req.body)) as IClearDialogueRequest;
|
||||||
if (inventory.DialogueHistory && inventory.DialogueHistory.Dialogues) {
|
if (inventory.DialogueHistory && inventory.DialogueHistory.Dialogues) {
|
||||||
inventory.DialogueHistory.Resets ??= 0;
|
|
||||||
inventory.DialogueHistory.Resets += 1;
|
|
||||||
for (const dialogueName of request.Dialogues) {
|
for (const dialogueName of request.Dialogues) {
|
||||||
const index = inventory.DialogueHistory.Dialogues.findIndex(x => x.DialogueName == dialogueName);
|
const index = inventory.DialogueHistory.Dialogues.findIndex(x => x.DialogueName == dialogueName);
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
import { RequestHandler } from "express";
|
|
||||||
|
|
||||||
// example req.body: {"NewEpisodeReward":true,"crossPlaySetting":"ENABLED"}
|
|
||||||
export const clearNewEpisodeRewardController: RequestHandler = (_req, res) => {
|
|
||||||
res.status(200).end();
|
|
||||||
};
|
|
@ -1,37 +0,0 @@
|
|||||||
import { checkCalendarChallengeCompletion, getCalendarProgress, getInventory } from "@/src/services/inventoryService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
|
||||||
import { getWorldState } from "@/src/services/worldStateService";
|
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
|
|
||||||
// GET request; query parameters: CompletedEventIdx=0&Iteration=4&Version=19&Season=CST_SUMMER
|
|
||||||
export const completeCalendarEventController: RequestHandler = async (req, res) => {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const inventory = await getInventory(accountId);
|
|
||||||
const calendarProgress = getCalendarProgress(inventory);
|
|
||||||
const currentSeason = getWorldState().KnownCalendarSeasons[0];
|
|
||||||
let inventoryChanges: IInventoryChanges = {};
|
|
||||||
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 = dayIndex;
|
|
||||||
checkCalendarChallengeCompletion(calendarProgress, currentSeason);
|
|
||||||
await inventory.save();
|
|
||||||
res.json({
|
|
||||||
InventoryChanges: inventoryChanges,
|
|
||||||
CalendarProgress: inventory.CalendarProgress
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,37 +0,0 @@
|
|||||||
import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
|
|
||||||
import { getAllianceClient } from "@/src/services/guildService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
|
|
||||||
export const confirmAllianceInvitationController: RequestHandler = async (req, res) => {
|
|
||||||
// Check requester is a warlord in their guild
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const guildMember = (await GuildMember.findOne({ accountId, status: 0 }))!;
|
|
||||||
if (guildMember.rank > 1) {
|
|
||||||
res.status(400).json({ Error: 104 });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const allianceMember = await AllianceMember.findOne({
|
|
||||||
allianceId: req.query.allianceId,
|
|
||||||
guildId: guildMember.guildId
|
|
||||||
});
|
|
||||||
if (!allianceMember || !allianceMember.Pending) {
|
|
||||||
res.status(400);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
allianceMember.Pending = false;
|
|
||||||
|
|
||||||
const guild = (await Guild.findById(guildMember.guildId))!;
|
|
||||||
guild.AllianceId = allianceMember.allianceId;
|
|
||||||
|
|
||||||
await Promise.all([allianceMember.save(), guild.save()]);
|
|
||||||
|
|
||||||
// Give client the new alliance data which uses "AllianceId" instead of "_id" in this response
|
|
||||||
const alliance = (await Alliance.findById(allianceMember.allianceId))!;
|
|
||||||
const { _id, ...rest } = await getAllianceClient(alliance, guild);
|
|
||||||
res.json({
|
|
||||||
AllianceId: _id,
|
|
||||||
...rest
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,58 +1,26 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|
||||||
import { Guild, GuildMember } from "@/src/models/guildModel";
|
import { Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
import { Account } from "@/src/models/loginModel";
|
import { getGuildClient, updateInventoryForConfirmedGuildJoin } from "@/src/services/guildService";
|
||||||
import {
|
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||||
deleteGuild,
|
|
||||||
getGuildClient,
|
|
||||||
giveClanKey,
|
|
||||||
hasGuildPermission,
|
|
||||||
removeDojoKeyItems
|
|
||||||
} from "@/src/services/guildService";
|
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
|
||||||
import { getAccountForRequest, getAccountIdForRequest, getSuffixedName } from "@/src/services/loginService";
|
|
||||||
import { GuildPermission } from "@/src/types/guildTypes";
|
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
|
|
||||||
// GET request: A player accepting an invite they got in their inbox.
|
export const confirmGuildInvitationController: RequestHandler = async (req, res) => {
|
||||||
export const confirmGuildInvitationGetController: RequestHandler = async (req, res) => {
|
|
||||||
const account = await getAccountForRequest(req);
|
const account = await getAccountForRequest(req);
|
||||||
const invitedGuildMember = await GuildMember.findOne({
|
const guildMember = await GuildMember.findOne({
|
||||||
accountId: account._id,
|
accountId: account._id,
|
||||||
guildId: req.query.clanId as string
|
guildId: req.query.clanId as string
|
||||||
});
|
});
|
||||||
if (invitedGuildMember && invitedGuildMember.status == 2) {
|
if (guildMember) {
|
||||||
let inventoryChanges: IInventoryChanges = {};
|
guildMember.status = 0;
|
||||||
|
await guildMember.save();
|
||||||
|
|
||||||
// If this account is already in a guild, we need to do cleanup first.
|
await updateInventoryForConfirmedGuildJoin(
|
||||||
const guildMember = await GuildMember.findOneAndDelete({ accountId: account._id, status: 0 });
|
account._id.toString(),
|
||||||
if (guildMember) {
|
new Types.ObjectId(req.query.clanId as string)
|
||||||
const inventory = await getInventory(account._id.toString(), "LevelKeys Recipes");
|
);
|
||||||
inventoryChanges = removeDojoKeyItems(inventory);
|
|
||||||
await inventory.save();
|
|
||||||
|
|
||||||
if (guildMember.rank == 0) {
|
const guild = (await Guild.findOne({ _id: req.query.clanId as string }))!;
|
||||||
await deleteGuild(guildMember.guildId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now that we're sure this account is not in a guild right now, we can just proceed with the normal updates.
|
|
||||||
invitedGuildMember.status = 0;
|
|
||||||
await invitedGuildMember.save();
|
|
||||||
|
|
||||||
// Remove pending applications for this account
|
|
||||||
await GuildMember.deleteMany({ accountId: account._id, status: 1 });
|
|
||||||
|
|
||||||
// Update inventory of new member
|
|
||||||
const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes");
|
|
||||||
inventory.GuildId = new Types.ObjectId(req.query.clanId as string);
|
|
||||||
giveClanKey(inventory, inventoryChanges);
|
|
||||||
await inventory.save();
|
|
||||||
|
|
||||||
const guild = (await Guild.findById(req.query.clanId as string))!;
|
|
||||||
|
|
||||||
// Add join to clan log
|
|
||||||
guild.RosterActivity ??= [];
|
guild.RosterActivity ??= [];
|
||||||
guild.RosterActivity.push({
|
guild.RosterActivity.push({
|
||||||
dateTime: new Date(),
|
dateTime: new Date(),
|
||||||
@ -62,57 +30,17 @@ export const confirmGuildInvitationGetController: RequestHandler = async (req, r
|
|||||||
await guild.save();
|
await guild.save();
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
...(await getGuildClient(guild, account)),
|
...(await getGuildClient(guild, account._id.toString())),
|
||||||
InventoryChanges: inventoryChanges
|
InventoryChanges: {
|
||||||
|
Recipes: [
|
||||||
|
{
|
||||||
|
ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
|
||||||
|
ItemCount: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
res.end();
|
res.end();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// POST request: Clan representative accepting invite(s).
|
|
||||||
export const confirmGuildInvitationPostController: RequestHandler = async (req, res) => {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const guild = (await Guild.findById(req.query.clanId as string, "Ranks RosterActivity"))!;
|
|
||||||
if (!(await hasGuildPermission(guild, accountId, GuildPermission.Recruiter))) {
|
|
||||||
res.status(400).json("Invalid permission");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const payload = getJSONfromString<{ userId: string }>(String(req.body));
|
|
||||||
const filter: { accountId?: string; status: number } = { status: 1 };
|
|
||||||
if (payload.userId != "all") {
|
|
||||||
filter.accountId = payload.userId;
|
|
||||||
}
|
|
||||||
const guildMembers = await GuildMember.find(filter);
|
|
||||||
const newMembers: string[] = [];
|
|
||||||
for (const guildMember of guildMembers) {
|
|
||||||
guildMember.status = 0;
|
|
||||||
guildMember.RequestMsg = undefined;
|
|
||||||
guildMember.RequestExpiry = undefined;
|
|
||||||
await guildMember.save();
|
|
||||||
|
|
||||||
// Remove other pending applications for this account
|
|
||||||
await GuildMember.deleteMany({ accountId: guildMember.accountId, status: 1 });
|
|
||||||
|
|
||||||
// Update inventory of new member
|
|
||||||
const inventory = await getInventory(guildMember.accountId.toString(), "GuildId LevelKeys Recipes");
|
|
||||||
inventory.GuildId = new Types.ObjectId(req.query.clanId as string);
|
|
||||||
giveClanKey(inventory);
|
|
||||||
await inventory.save();
|
|
||||||
|
|
||||||
// Add join to clan log
|
|
||||||
const account = (await Account.findOne({ _id: guildMember.accountId }))!;
|
|
||||||
guild.RosterActivity ??= [];
|
|
||||||
guild.RosterActivity.push({
|
|
||||||
dateTime: new Date(),
|
|
||||||
entryType: 6,
|
|
||||||
details: getSuffixedName(account)
|
|
||||||
});
|
|
||||||
|
|
||||||
newMembers.push(account._id.toString());
|
|
||||||
}
|
|
||||||
await guild.save();
|
|
||||||
res.json({
|
|
||||||
NewMembers: newMembers
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { Guild } from "@/src/models/guildModel";
|
import { Guild } from "@/src/models/guildModel";
|
||||||
import { checkClanAscensionHasRequiredContributors } from "@/src/services/guildService";
|
import { config } from "@/src/services/configService";
|
||||||
import { addFusionPoints, getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
@ -10,7 +10,7 @@ import { Types } from "mongoose";
|
|||||||
export const contributeGuildClassController: RequestHandler = async (req, res) => {
|
export const contributeGuildClassController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const payload = getJSONfromString<IContributeGuildClassRequest>(String(req.body));
|
const payload = getJSONfromString<IContributeGuildClassRequest>(String(req.body));
|
||||||
const guild = (await Guild.findById(payload.GuildId))!;
|
const guild = (await Guild.findOne({ _id: payload.GuildId }))!;
|
||||||
|
|
||||||
// First contributor initiates ceremony and locks the pending class.
|
// First contributor initiates ceremony and locks the pending class.
|
||||||
if (!guild.CeremonyContributors) {
|
if (!guild.CeremonyContributors) {
|
||||||
@ -30,13 +30,18 @@ export const contributeGuildClassController: RequestHandler = async (req, res) =
|
|||||||
|
|
||||||
guild.CeremonyContributors.push(new Types.ObjectId(accountId));
|
guild.CeremonyContributors.push(new Types.ObjectId(accountId));
|
||||||
|
|
||||||
await checkClanAscensionHasRequiredContributors(guild);
|
// Once required contributor count is hit, the class is committed and there's 72 hours to claim endo.
|
||||||
|
if (guild.CeremonyContributors.length == payload.RequiredContributors) {
|
||||||
|
guild.Class = guild.CeremonyClass!;
|
||||||
|
guild.CeremonyClass = undefined;
|
||||||
|
guild.CeremonyResetDate = new Date(Date.now() + (config.fastClanAscension ? 5_000 : 72 * 3600_000));
|
||||||
|
}
|
||||||
|
|
||||||
await guild.save();
|
await guild.save();
|
||||||
|
|
||||||
// Either way, endo is given to the contributor.
|
// Either way, endo is given to the contributor.
|
||||||
const inventory = await getInventory(accountId, "FusionPoints");
|
const inventory = await getInventory(accountId, "FusionPoints");
|
||||||
addFusionPoints(inventory, guild.CeremonyEndo!);
|
inventory.FusionPoints += guild.CeremonyEndo!;
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
|
import { TGuildDatabaseDocument } from "@/src/models/guildModel";
|
||||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
import {
|
import {
|
||||||
addGuildMemberMiscItemContribution,
|
|
||||||
getDojoClient,
|
getDojoClient,
|
||||||
getGuildForRequestEx,
|
getGuildForRequestEx,
|
||||||
hasAccessToDojo,
|
hasAccessToDojo,
|
||||||
@ -11,7 +10,7 @@ import {
|
|||||||
} from "@/src/services/guildService";
|
} from "@/src/services/guildService";
|
||||||
import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { IDojoContributable, IGuildMemberDatabase } from "@/src/types/guildTypes";
|
import { IDojoContributable } from "@/src/types/guildTypes";
|
||||||
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
@ -36,10 +35,6 @@ export const contributeToDojoComponentController: RequestHandler = async (req, r
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const guild = await getGuildForRequestEx(req, inventory);
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
const guildMember = (await GuildMember.findOne(
|
|
||||||
{ accountId, guildId: guild._id },
|
|
||||||
"RegularCreditsContributed MiscItemsContributed"
|
|
||||||
))!;
|
|
||||||
const request = JSON.parse(String(req.body)) as IContributeToDojoComponentRequest;
|
const request = JSON.parse(String(req.body)) as IContributeToDojoComponentRequest;
|
||||||
const component = guild.DojoComponents.id(request.ComponentId)!;
|
const component = guild.DojoComponents.id(request.ComponentId)!;
|
||||||
|
|
||||||
@ -50,7 +45,7 @@ export const contributeToDojoComponentController: RequestHandler = async (req, r
|
|||||||
throw new Error("attempt to contribute to a deco in an unfinished room?!");
|
throw new Error("attempt to contribute to a deco in an unfinished room?!");
|
||||||
}
|
}
|
||||||
const meta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!;
|
const meta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!;
|
||||||
processContribution(guild, guildMember, request, inventory, inventoryChanges, meta, component);
|
processContribution(guild, request, inventory, inventoryChanges, meta, component);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
if (component.CompletionTime) {
|
if (component.CompletionTime) {
|
||||||
setDojoRoomLogFunded(guild, component);
|
setDojoRoomLogFunded(guild, component);
|
||||||
@ -60,11 +55,12 @@ export const contributeToDojoComponentController: RequestHandler = async (req, r
|
|||||||
if (request.DecoId) {
|
if (request.DecoId) {
|
||||||
const deco = component.Decos!.find(x => x._id.equals(request.DecoId))!;
|
const deco = component.Decos!.find(x => x._id.equals(request.DecoId))!;
|
||||||
const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == deco.Type)!;
|
const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == deco.Type)!;
|
||||||
processContribution(guild, guildMember, request, inventory, inventoryChanges, meta, deco);
|
processContribution(guild, request, inventory, inventoryChanges, meta, deco);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
|
await guild.save();
|
||||||
|
await inventory.save();
|
||||||
res.json({
|
res.json({
|
||||||
...(await getDojoClient(guild, 0, component._id)),
|
...(await getDojoClient(guild, 0, component._id)),
|
||||||
InventoryChanges: inventoryChanges
|
InventoryChanges: inventoryChanges
|
||||||
@ -73,7 +69,6 @@ export const contributeToDojoComponentController: RequestHandler = async (req, r
|
|||||||
|
|
||||||
const processContribution = (
|
const processContribution = (
|
||||||
guild: TGuildDatabaseDocument,
|
guild: TGuildDatabaseDocument,
|
||||||
guildMember: IGuildMemberDatabase,
|
|
||||||
request: IContributeToDojoComponentRequest,
|
request: IContributeToDojoComponentRequest,
|
||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
inventoryChanges: IInventoryChanges,
|
inventoryChanges: IInventoryChanges,
|
||||||
@ -85,18 +80,15 @@ const processContribution = (
|
|||||||
component.RegularCredits += request.RegularCredits;
|
component.RegularCredits += request.RegularCredits;
|
||||||
inventoryChanges.RegularCredits = -request.RegularCredits;
|
inventoryChanges.RegularCredits = -request.RegularCredits;
|
||||||
updateCurrency(inventory, request.RegularCredits, false);
|
updateCurrency(inventory, request.RegularCredits, false);
|
||||||
|
|
||||||
guildMember.RegularCreditsContributed ??= 0;
|
|
||||||
guildMember.RegularCreditsContributed += request.RegularCredits;
|
|
||||||
}
|
}
|
||||||
if (request.VaultCredits) {
|
if (request.VaultCredits) {
|
||||||
component.RegularCredits += request.VaultCredits;
|
component.RegularCredits += request.VaultCredits;
|
||||||
guild.VaultRegularCredits! -= request.VaultCredits;
|
guild.VaultRegularCredits! -= request.VaultCredits;
|
||||||
}
|
}
|
||||||
if (component.RegularCredits > scaleRequiredCount(guild.Tier, meta.price)) {
|
if (component.RegularCredits > scaleRequiredCount(meta.price)) {
|
||||||
guild.VaultRegularCredits ??= 0;
|
guild.VaultRegularCredits ??= 0;
|
||||||
guild.VaultRegularCredits += component.RegularCredits - scaleRequiredCount(guild.Tier, meta.price);
|
guild.VaultRegularCredits += component.RegularCredits - scaleRequiredCount(meta.price);
|
||||||
component.RegularCredits = scaleRequiredCount(guild.Tier, meta.price);
|
component.RegularCredits = scaleRequiredCount(meta.price);
|
||||||
}
|
}
|
||||||
|
|
||||||
component.MiscItems ??= [];
|
component.MiscItems ??= [];
|
||||||
@ -107,10 +99,10 @@ const processContribution = (
|
|||||||
const ingredientMeta = meta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
|
const ingredientMeta = meta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
|
||||||
if (
|
if (
|
||||||
componentMiscItem.ItemCount + ingredientContribution.ItemCount >
|
componentMiscItem.ItemCount + ingredientContribution.ItemCount >
|
||||||
scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount)
|
scaleRequiredCount(ingredientMeta.ItemCount)
|
||||||
) {
|
) {
|
||||||
ingredientContribution.ItemCount =
|
ingredientContribution.ItemCount =
|
||||||
scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
|
scaleRequiredCount(ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
|
||||||
}
|
}
|
||||||
componentMiscItem.ItemCount += ingredientContribution.ItemCount;
|
componentMiscItem.ItemCount += ingredientContribution.ItemCount;
|
||||||
} else {
|
} else {
|
||||||
@ -128,10 +120,10 @@ const processContribution = (
|
|||||||
const ingredientMeta = meta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
|
const ingredientMeta = meta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
|
||||||
if (
|
if (
|
||||||
componentMiscItem.ItemCount + ingredientContribution.ItemCount >
|
componentMiscItem.ItemCount + ingredientContribution.ItemCount >
|
||||||
scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount)
|
scaleRequiredCount(ingredientMeta.ItemCount)
|
||||||
) {
|
) {
|
||||||
ingredientContribution.ItemCount =
|
ingredientContribution.ItemCount =
|
||||||
scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
|
scaleRequiredCount(ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
|
||||||
}
|
}
|
||||||
componentMiscItem.ItemCount += ingredientContribution.ItemCount;
|
componentMiscItem.ItemCount += ingredientContribution.ItemCount;
|
||||||
} else {
|
} else {
|
||||||
@ -141,21 +133,16 @@ const processContribution = (
|
|||||||
ItemType: ingredientContribution.ItemType,
|
ItemType: ingredientContribution.ItemType,
|
||||||
ItemCount: ingredientContribution.ItemCount * -1
|
ItemCount: ingredientContribution.ItemCount * -1
|
||||||
});
|
});
|
||||||
|
|
||||||
addGuildMemberMiscItemContribution(guildMember, ingredientContribution);
|
|
||||||
}
|
}
|
||||||
addMiscItems(inventory, miscItemChanges);
|
addMiscItems(inventory, miscItemChanges);
|
||||||
inventoryChanges.MiscItems = miscItemChanges;
|
inventoryChanges.MiscItems = miscItemChanges;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (component.RegularCredits >= scaleRequiredCount(guild.Tier, meta.price)) {
|
if (component.RegularCredits >= scaleRequiredCount(meta.price)) {
|
||||||
let fullyFunded = true;
|
let fullyFunded = true;
|
||||||
for (const ingredient of meta.ingredients) {
|
for (const ingredient of meta.ingredients) {
|
||||||
const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredient.ItemType);
|
const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredient.ItemType);
|
||||||
if (
|
if (!componentMiscItem || componentMiscItem.ItemCount < scaleRequiredCount(ingredient.ItemCount)) {
|
||||||
!componentMiscItem ||
|
|
||||||
componentMiscItem.ItemCount < scaleRequiredCount(guild.Tier, ingredient.ItemCount)
|
|
||||||
) {
|
|
||||||
fullyFunded = false;
|
fullyFunded = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1,104 +1,43 @@
|
|||||||
import {
|
import { getGuildForRequestEx } from "@/src/services/guildService";
|
||||||
Alliance,
|
import { addFusionTreasures, addMiscItems, addShipDecorations, getInventory } from "@/src/services/inventoryService";
|
||||||
Guild,
|
|
||||||
GuildMember,
|
|
||||||
TGuildDatabaseDocument,
|
|
||||||
TGuildMemberDatabaseDocument
|
|
||||||
} from "@/src/models/guildModel";
|
|
||||||
import {
|
|
||||||
addGuildMemberMiscItemContribution,
|
|
||||||
addGuildMemberShipDecoContribution,
|
|
||||||
addVaultFusionTreasures,
|
|
||||||
addVaultMiscItems,
|
|
||||||
addVaultShipDecos,
|
|
||||||
getGuildForRequestEx
|
|
||||||
} from "@/src/services/guildService";
|
|
||||||
import {
|
|
||||||
addFusionTreasures,
|
|
||||||
addMiscItems,
|
|
||||||
addShipDecorations,
|
|
||||||
getInventory,
|
|
||||||
updateCurrency
|
|
||||||
} from "@/src/services/inventoryService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { IFusionTreasure, IMiscItem, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IFusionTreasure, IMiscItem, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
export const contributeToVaultController: RequestHandler = async (req, res) => {
|
export const contributeToVaultController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await getInventory(accountId, "GuildId RegularCredits MiscItems ShipDecorations FusionTreasures");
|
const inventory = await getInventory(accountId);
|
||||||
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
const request = JSON.parse(String(req.body)) as IContributeToVaultRequest;
|
const request = JSON.parse(String(req.body)) as IContributeToVaultRequest;
|
||||||
|
|
||||||
if (request.Alliance) {
|
|
||||||
const guild = await getGuildForRequestEx(req, inventory);
|
|
||||||
const alliance = (await Alliance.findById(guild.AllianceId!))!;
|
|
||||||
alliance.VaultRegularCredits ??= 0;
|
|
||||||
alliance.VaultRegularCredits += request.RegularCredits;
|
|
||||||
if (request.FromVault) {
|
|
||||||
guild.VaultRegularCredits! -= request.RegularCredits;
|
|
||||||
await Promise.all([guild.save(), alliance.save()]);
|
|
||||||
} else {
|
|
||||||
updateCurrency(inventory, request.RegularCredits, false);
|
|
||||||
await Promise.all([inventory.save(), alliance.save()]);
|
|
||||||
}
|
|
||||||
res.end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let guild: TGuildDatabaseDocument;
|
|
||||||
let guildMember: TGuildMemberDatabaseDocument | undefined;
|
|
||||||
if (request.GuildVault) {
|
|
||||||
guild = (await Guild.findById(request.GuildVault))!;
|
|
||||||
} else {
|
|
||||||
guild = await getGuildForRequestEx(req, inventory);
|
|
||||||
guildMember = (await GuildMember.findOne(
|
|
||||||
{ accountId, guildId: guild._id },
|
|
||||||
"RegularCreditsContributed MiscItemsContributed ShipDecorationsContributed"
|
|
||||||
))!;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.RegularCredits) {
|
if (request.RegularCredits) {
|
||||||
updateCurrency(inventory, request.RegularCredits, false);
|
|
||||||
|
|
||||||
guild.VaultRegularCredits ??= 0;
|
guild.VaultRegularCredits ??= 0;
|
||||||
guild.VaultRegularCredits += request.RegularCredits;
|
guild.VaultRegularCredits += request.RegularCredits;
|
||||||
|
|
||||||
if (guildMember) {
|
|
||||||
guildMember.RegularCreditsContributed ??= 0;
|
|
||||||
guildMember.RegularCreditsContributed += request.RegularCredits;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (request.MiscItems.length) {
|
if (request.MiscItems.length) {
|
||||||
addVaultMiscItems(guild, request.MiscItems);
|
guild.VaultMiscItems ??= [];
|
||||||
for (const item of request.MiscItems) {
|
for (const item of request.MiscItems) {
|
||||||
if (guildMember) {
|
guild.VaultMiscItems.push(item);
|
||||||
addGuildMemberMiscItemContribution(guildMember, item);
|
|
||||||
}
|
|
||||||
addMiscItems(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
|
addMiscItems(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (request.ShipDecorations.length) {
|
if (request.ShipDecorations.length) {
|
||||||
addVaultShipDecos(guild, request.ShipDecorations);
|
guild.VaultShipDecorations ??= [];
|
||||||
for (const item of request.ShipDecorations) {
|
for (const item of request.ShipDecorations) {
|
||||||
if (guildMember) {
|
guild.VaultShipDecorations.push(item);
|
||||||
addGuildMemberShipDecoContribution(guildMember, item);
|
|
||||||
}
|
|
||||||
addShipDecorations(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
|
addShipDecorations(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (request.FusionTreasures.length) {
|
if (request.FusionTreasures.length) {
|
||||||
addVaultFusionTreasures(guild, request.FusionTreasures);
|
guild.VaultFusionTreasures ??= [];
|
||||||
for (const item of request.FusionTreasures) {
|
for (const item of request.FusionTreasures) {
|
||||||
|
guild.VaultFusionTreasures.push(item);
|
||||||
addFusionTreasures(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
|
addFusionTreasures(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const promises: Promise<unknown>[] = [guild.save(), inventory.save()];
|
await guild.save();
|
||||||
if (guildMember) {
|
await inventory.save();
|
||||||
promises.push(guildMember.save());
|
|
||||||
}
|
|
||||||
await Promise.all(promises);
|
|
||||||
|
|
||||||
res.end();
|
res.end();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -107,7 +46,4 @@ interface IContributeToVaultRequest {
|
|||||||
MiscItems: IMiscItem[];
|
MiscItems: IMiscItem[];
|
||||||
ShipDecorations: ITypeCount[];
|
ShipDecorations: ITypeCount[];
|
||||||
FusionTreasures: IFusionTreasure[];
|
FusionTreasures: IFusionTreasure[];
|
||||||
Alliance?: boolean;
|
|
||||||
FromVault?: boolean;
|
|
||||||
GuildVault?: string;
|
|
||||||
}
|
}
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|
||||||
import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
|
|
||||||
import { getAllianceClient } from "@/src/services/guildService";
|
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { GuildPermission } from "@/src/types/guildTypes";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
|
|
||||||
export const createAllianceController: RequestHandler = async (req, res) => {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const inventory = await getInventory(accountId, "GuildId");
|
|
||||||
const guild = (await Guild.findById(inventory.GuildId!, "Name Tier AllianceId"))!;
|
|
||||||
if (guild.AllianceId) {
|
|
||||||
res.status(400).send("Guild is already in an alliance").end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const guildMember = (await GuildMember.findOne({ guildId: guild._id, accountId }, "rank"))!;
|
|
||||||
if (guildMember.rank > 1) {
|
|
||||||
res.status(400).send("Invalid permission").end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const data = getJSONfromString<ICreateAllianceRequest>(String(req.body));
|
|
||||||
const alliance = new Alliance({ Name: data.allianceName });
|
|
||||||
try {
|
|
||||||
await alliance.save();
|
|
||||||
} catch (e) {
|
|
||||||
res.status(400).send("Alliance name already in use").end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
guild.AllianceId = alliance._id;
|
|
||||||
await Promise.all([
|
|
||||||
guild.save(),
|
|
||||||
AllianceMember.insertOne({
|
|
||||||
allianceId: alliance._id,
|
|
||||||
guildId: guild._id,
|
|
||||||
Pending: false,
|
|
||||||
Permissions:
|
|
||||||
GuildPermission.Ruler |
|
|
||||||
GuildPermission.Promoter |
|
|
||||||
GuildPermission.Recruiter |
|
|
||||||
GuildPermission.Treasurer |
|
|
||||||
GuildPermission.ChatModerator
|
|
||||||
})
|
|
||||||
]);
|
|
||||||
res.json(await getAllianceClient(alliance, guild));
|
|
||||||
};
|
|
||||||
|
|
||||||
interface ICreateAllianceRequest {
|
|
||||||
allianceName: string;
|
|
||||||
}
|
|
@ -1,18 +1,17 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { Guild, GuildMember } from "@/src/models/guildModel";
|
import { Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
import { createUniqueClanName, getGuildClient, giveClanKey } from "@/src/services/guildService";
|
import {
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
createUniqueClanName,
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
getGuildClient,
|
||||||
|
updateInventoryForConfirmedGuildJoin
|
||||||
|
} from "@/src/services/guildService";
|
||||||
|
|
||||||
export const createGuildController: RequestHandler = async (req, res) => {
|
export const createGuildController: RequestHandler = async (req, res) => {
|
||||||
const account = await getAccountForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const payload = getJSONfromString<ICreateGuildRequest>(String(req.body));
|
const payload = getJSONfromString<ICreateGuildRequest>(String(req.body));
|
||||||
|
|
||||||
// Remove pending applications for this account
|
|
||||||
await GuildMember.deleteMany({ accountId: account._id, status: 1 });
|
|
||||||
|
|
||||||
// Create guild on database
|
// Create guild on database
|
||||||
const guild = new Guild({
|
const guild = new Guild({
|
||||||
Name: await createUniqueClanName(payload.guildName)
|
Name: await createUniqueClanName(payload.guildName)
|
||||||
@ -21,22 +20,15 @@ export const createGuildController: RequestHandler = async (req, res) => {
|
|||||||
|
|
||||||
// Create guild member on database
|
// Create guild member on database
|
||||||
await GuildMember.insertOne({
|
await GuildMember.insertOne({
|
||||||
accountId: account._id,
|
accountId: accountId,
|
||||||
guildId: guild._id,
|
guildId: guild._id,
|
||||||
status: 0,
|
status: 0,
|
||||||
rank: 0
|
rank: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes");
|
await updateInventoryForConfirmedGuildJoin(accountId, guild._id);
|
||||||
inventory.GuildId = guild._id;
|
|
||||||
const inventoryChanges: IInventoryChanges = {};
|
|
||||||
giveClanKey(inventory, inventoryChanges);
|
|
||||||
await inventory.save();
|
|
||||||
|
|
||||||
res.json({
|
res.json(await getGuildClient(guild, accountId));
|
||||||
...(await getGuildClient(guild, account)),
|
|
||||||
InventoryChanges: inventoryChanges
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ICreateGuildRequest {
|
interface ICreateGuildRequest {
|
||||||
|
@ -4,15 +4,9 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
|
|||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
|
||||||
export const creditsController: RequestHandler = async (req, res) => {
|
export const creditsController: RequestHandler = async (req, res) => {
|
||||||
const inventory = (
|
const accountId = await getAccountIdForRequest(req);
|
||||||
await Promise.all([
|
|
||||||
getAccountIdForRequest(req),
|
const inventory = await getInventory(accountId, "RegularCredits TradesRemaining PremiumCreditsFree PremiumCredits");
|
||||||
getInventory(
|
|
||||||
req.query.accountId as string,
|
|
||||||
"RegularCredits TradesRemaining PremiumCreditsFree PremiumCredits"
|
|
||||||
)
|
|
||||||
])
|
|
||||||
)[1];
|
|
||||||
|
|
||||||
const response = {
|
const response = {
|
||||||
RegularCredits: inventory.RegularCredits,
|
RegularCredits: inventory.RegularCredits,
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|
||||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { ICrewMemberClient } from "@/src/types/inventoryTypes/inventoryTypes";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
import { Types } from "mongoose";
|
|
||||||
|
|
||||||
export const crewMembersController: RequestHandler = async (req, res) => {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const inventory = await getInventory(accountId, "CrewMembers NemesisHistory");
|
|
||||||
const data = getJSONfromString<ICrewMembersRequest>(String(req.body));
|
|
||||||
if (data.crewMember.SecondInCommand) {
|
|
||||||
clearOnCall(inventory);
|
|
||||||
}
|
|
||||||
if (data.crewMember.ItemId.$oid == "000000000000000000000000") {
|
|
||||||
const convertedNemesis = inventory.NemesisHistory!.find(x => x.fp == data.crewMember.NemesisFingerprint)!;
|
|
||||||
convertedNemesis.SecondInCommand = data.crewMember.SecondInCommand;
|
|
||||||
} else {
|
|
||||||
const dbCrewMember = inventory.CrewMembers.id(data.crewMember.ItemId.$oid)!;
|
|
||||||
dbCrewMember.AssignedRole = data.crewMember.AssignedRole;
|
|
||||||
dbCrewMember.SkillEfficiency = data.crewMember.SkillEfficiency;
|
|
||||||
dbCrewMember.WeaponConfigIdx = data.crewMember.WeaponConfigIdx;
|
|
||||||
dbCrewMember.WeaponId = new Types.ObjectId(data.crewMember.WeaponId.$oid);
|
|
||||||
dbCrewMember.Configs = data.crewMember.Configs;
|
|
||||||
dbCrewMember.SecondInCommand = data.crewMember.SecondInCommand;
|
|
||||||
}
|
|
||||||
await inventory.save();
|
|
||||||
res.json({
|
|
||||||
crewMemberId: data.crewMember.ItemId.$oid,
|
|
||||||
NemesisFingerprint: data.crewMember.NemesisFingerprint
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
interface ICrewMembersRequest {
|
|
||||||
crewMember: ICrewMemberClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
const clearOnCall = (inventory: TInventoryDatabaseDocument): void => {
|
|
||||||
for (const cm of inventory.CrewMembers) {
|
|
||||||
if (cm.SecondInCommand) {
|
|
||||||
cm.SecondInCommand = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (inventory.NemesisHistory) {
|
|
||||||
for (const cm of inventory.NemesisHistory) {
|
|
||||||
if (cm.SecondInCommand) {
|
|
||||||
cm.SecondInCommand = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,107 +0,0 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|
||||||
import { addMiscItems, freeUpSlot, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { IOid } from "@/src/types/commonTypes";
|
|
||||||
import { ICrewShipComponentFingerprint, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
import { ExportCustoms, ExportDojoRecipes } from "warframe-public-export-plus";
|
|
||||||
|
|
||||||
export const crewShipFusionController: RequestHandler = async (req, res) => {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const inventory = await getInventory(accountId);
|
|
||||||
const payload = getJSONfromString<ICrewShipFusionRequest>(String(req.body));
|
|
||||||
|
|
||||||
const isWeapon = inventory.CrewShipWeapons.id(payload.PartA.$oid);
|
|
||||||
const itemA = isWeapon ?? inventory.CrewShipWeaponSkins.id(payload.PartA.$oid)!;
|
|
||||||
const category = isWeapon ? "CrewShipWeapons" : "CrewShipWeaponSkins";
|
|
||||||
const salvageCategory = isWeapon ? "CrewShipSalvagedWeapons" : "CrewShipSalvagedWeaponSkins";
|
|
||||||
const itemB = inventory[payload.SourceRecipe ? salvageCategory : category].id(payload.PartB.$oid)!;
|
|
||||||
const tierA = itemA.ItemType.charCodeAt(itemA.ItemType.length - 1) - 65;
|
|
||||||
const tierB = itemB.ItemType.charCodeAt(itemB.ItemType.length - 1) - 65;
|
|
||||||
|
|
||||||
const inventoryChanges: IInventoryChanges = {};
|
|
||||||
|
|
||||||
// Charge partial repair cost if fusing with an identified but unrepaired part
|
|
||||||
if (payload.SourceRecipe) {
|
|
||||||
const recipe = ExportDojoRecipes.research[payload.SourceRecipe];
|
|
||||||
updateCurrency(inventory, Math.round(recipe.price * 0.4), false, inventoryChanges);
|
|
||||||
const miscItemChanges = recipe.ingredients.map(x => ({ ...x, ItemCount: Math.round(x.ItemCount * -0.4) }));
|
|
||||||
addMiscItems(inventory, miscItemChanges);
|
|
||||||
inventoryChanges.MiscItems = miscItemChanges;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove inferior item
|
|
||||||
if (payload.SourceRecipe) {
|
|
||||||
inventory[salvageCategory].pull({ _id: payload.PartB.$oid });
|
|
||||||
inventoryChanges.RemovedIdItems = [{ ItemId: payload.PartB }];
|
|
||||||
} else {
|
|
||||||
const inferiorId = tierA < tierB ? payload.PartA : payload.PartB;
|
|
||||||
inventory[category].pull({ _id: inferiorId.$oid });
|
|
||||||
inventoryChanges.RemovedIdItems = [{ ItemId: inferiorId }];
|
|
||||||
freeUpSlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS);
|
|
||||||
inventoryChanges[InventorySlot.RJ_COMPONENT_AND_ARMAMENTS] = { count: -1, platinum: 0, Slots: 1 };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upgrade superior item
|
|
||||||
const superiorItem = tierA < tierB ? itemB : itemA;
|
|
||||||
const inferiorItem = tierA < tierB ? itemA : itemB;
|
|
||||||
const fingerprint: ICrewShipComponentFingerprint = JSON.parse(
|
|
||||||
superiorItem.UpgradeFingerprint!
|
|
||||||
) as ICrewShipComponentFingerprint;
|
|
||||||
const inferiorFingerprint: ICrewShipComponentFingerprint = inferiorItem.UpgradeFingerprint
|
|
||||||
? (JSON.parse(inferiorItem.UpgradeFingerprint) as ICrewShipComponentFingerprint)
|
|
||||||
: { compat: "", buffs: [] };
|
|
||||||
if (isWeapon) {
|
|
||||||
for (let i = 0; i != fingerprint.buffs.length; ++i) {
|
|
||||||
const buffA = fingerprint.buffs[i];
|
|
||||||
const buffB = i < inferiorFingerprint.buffs.length ? inferiorFingerprint.buffs[i] : undefined;
|
|
||||||
const fvalA = buffA.Value / 0x3fffffff;
|
|
||||||
const fvalB = (buffB?.Value ?? 0) / 0x3fffffff;
|
|
||||||
const percA = 0.3 + fvalA * (0.6 - 0.3);
|
|
||||||
const percB = 0.3 + fvalB * (0.6 - 0.3);
|
|
||||||
const newPerc = Math.min(0.6, Math.max(percA, percB) * FUSE_MULTIPLIERS[Math.abs(tierA - tierB)]);
|
|
||||||
const newFval = (newPerc - 0.3) / (0.6 - 0.3);
|
|
||||||
buffA.Value = Math.trunc(newFval * 0x3fffffff);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const superiorMeta = ExportCustoms[superiorItem.ItemType].randomisedUpgrades ?? [];
|
|
||||||
const inferiorMeta = ExportCustoms[inferiorItem.ItemType].randomisedUpgrades ?? [];
|
|
||||||
for (let i = 0; i != inferiorFingerprint.buffs.length; ++i) {
|
|
||||||
const buffA = fingerprint.buffs[i];
|
|
||||||
const buffB = inferiorFingerprint.buffs[i];
|
|
||||||
const fvalA = buffA.Value / 0x3fffffff;
|
|
||||||
const fvalB = buffB.Value / 0x3fffffff;
|
|
||||||
const rangeA = superiorMeta[i].range;
|
|
||||||
const rangeB = inferiorMeta[i].range;
|
|
||||||
const percA = rangeA[0] + fvalA * (rangeA[1] - rangeA[0]);
|
|
||||||
const percB = rangeB[0] + fvalB * (rangeB[1] - rangeB[0]);
|
|
||||||
const newPerc = Math.min(rangeA[1], Math.max(percA, percB) * FUSE_MULTIPLIERS[Math.abs(tierA - tierB)]);
|
|
||||||
const newFval = (newPerc - rangeA[0]) / (rangeA[1] - rangeA[0]);
|
|
||||||
buffA.Value = Math.trunc(newFval * 0x3fffffff);
|
|
||||||
}
|
|
||||||
if (inferiorFingerprint.SubroutineIndex) {
|
|
||||||
const useSuperiorSubroutine = tierA < tierB ? !payload.UseSubroutineA : payload.UseSubroutineA;
|
|
||||||
if (!useSuperiorSubroutine) {
|
|
||||||
fingerprint.SubroutineIndex = inferiorFingerprint.SubroutineIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
superiorItem.UpgradeFingerprint = JSON.stringify(fingerprint);
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
inventoryChanges[category] = [superiorItem.toJSON() as any];
|
|
||||||
|
|
||||||
await inventory.save();
|
|
||||||
res.json({
|
|
||||||
InventoryChanges: inventoryChanges
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
interface ICrewShipFusionRequest {
|
|
||||||
PartA: IOid;
|
|
||||||
PartB: IOid;
|
|
||||||
SourceRecipe: string;
|
|
||||||
UseSubroutineA: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const FUSE_MULTIPLIERS = [1.1, 1.05, 1.02];
|
|
@ -1,84 +0,0 @@
|
|||||||
import {
|
|
||||||
addCrewShipSalvagedWeaponSkin,
|
|
||||||
addCrewShipRawSalvage,
|
|
||||||
getInventory,
|
|
||||||
addEquipment
|
|
||||||
} from "@/src/services/inventoryService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
import { ICrewShipComponentFingerprint, IInnateDamageFingerprint } from "@/src/types/inventoryTypes/inventoryTypes";
|
|
||||||
import { ExportCustoms, ExportRailjackWeapons, ExportUpgrades } from "warframe-public-export-plus";
|
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
|
||||||
import { getRandomInt } from "@/src/services/rngService";
|
|
||||||
import { IFingerprintStat } from "@/src/helpers/rivenHelper";
|
|
||||||
import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
|
||||||
|
|
||||||
export const crewShipIdentifySalvageController: RequestHandler = async (req, res) => {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const inventory = await getInventory(
|
|
||||||
accountId,
|
|
||||||
"CrewShipSalvagedWeaponSkins CrewShipSalvagedWeapons CrewShipRawSalvage"
|
|
||||||
);
|
|
||||||
const payload = getJSONfromString<ICrewShipIdentifySalvageRequest>(String(req.body));
|
|
||||||
|
|
||||||
const inventoryChanges: IInventoryChanges = {};
|
|
||||||
if (payload.ItemType in ExportCustoms) {
|
|
||||||
const meta = ExportCustoms[payload.ItemType];
|
|
||||||
let upgradeFingerprint: ICrewShipComponentFingerprint = { compat: payload.ItemType, buffs: [] };
|
|
||||||
if (meta.subroutines) {
|
|
||||||
upgradeFingerprint = {
|
|
||||||
SubroutineIndex: getRandomInt(0, meta.subroutines.length - 1),
|
|
||||||
...upgradeFingerprint
|
|
||||||
};
|
|
||||||
}
|
|
||||||
for (const upgrade of meta.randomisedUpgrades!) {
|
|
||||||
upgradeFingerprint.buffs.push({ Tag: upgrade.tag, Value: Math.trunc(Math.random() * 0x40000000) });
|
|
||||||
}
|
|
||||||
addCrewShipSalvagedWeaponSkin(
|
|
||||||
inventory,
|
|
||||||
payload.ItemType,
|
|
||||||
JSON.stringify(upgradeFingerprint),
|
|
||||||
inventoryChanges
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const meta = ExportRailjackWeapons[payload.ItemType];
|
|
||||||
let defaultOverwrites: Partial<IEquipmentDatabase> | undefined;
|
|
||||||
if (meta.defaultUpgrades?.[0]) {
|
|
||||||
const upgradeType = meta.defaultUpgrades[0].ItemType;
|
|
||||||
const upgradeMeta = ExportUpgrades[upgradeType];
|
|
||||||
const buffs: IFingerprintStat[] = [];
|
|
||||||
for (const buff of upgradeMeta.upgradeEntries!) {
|
|
||||||
buffs.push({
|
|
||||||
Tag: buff.tag,
|
|
||||||
Value: Math.trunc(Math.random() * 0x40000000)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
defaultOverwrites = {
|
|
||||||
UpgradeType: upgradeType,
|
|
||||||
UpgradeFingerprint: JSON.stringify({
|
|
||||||
compat: payload.ItemType,
|
|
||||||
buffs
|
|
||||||
} satisfies IInnateDamageFingerprint)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
addEquipment(inventory, "CrewShipSalvagedWeapons", payload.ItemType, defaultOverwrites, inventoryChanges);
|
|
||||||
}
|
|
||||||
|
|
||||||
inventoryChanges.CrewShipRawSalvage = [
|
|
||||||
{
|
|
||||||
ItemType: payload.ItemType,
|
|
||||||
ItemCount: -1
|
|
||||||
}
|
|
||||||
];
|
|
||||||
addCrewShipRawSalvage(inventory, inventoryChanges.CrewShipRawSalvage);
|
|
||||||
|
|
||||||
await inventory.save();
|
|
||||||
res.json({
|
|
||||||
InventoryChanges: inventoryChanges
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
interface ICrewShipIdentifySalvageRequest {
|
|
||||||
ItemType: string;
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|
||||||
import { Guild } from "@/src/models/guildModel";
|
|
||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
|
||||||
import { logger } from "@/src/utils/logger";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
|
|
||||||
export const customObstacleCourseLeaderboardController: RequestHandler = async (req, res) => {
|
|
||||||
const data = getJSONfromString<ICustomObstacleCourseLeaderboardRequest>(String(req.body));
|
|
||||||
const guild = (await Guild.findById(data.g, "DojoComponents"))!;
|
|
||||||
const component = guild.DojoComponents.id(data.c)!;
|
|
||||||
if (req.query.act == "f") {
|
|
||||||
res.json({
|
|
||||||
results: component.Leaderboard ?? []
|
|
||||||
});
|
|
||||||
} else if (req.query.act == "p") {
|
|
||||||
const account = await getAccountForRequest(req);
|
|
||||||
component.Leaderboard ??= [];
|
|
||||||
const entry = component.Leaderboard.find(x => x.n == account.DisplayName);
|
|
||||||
if (entry) {
|
|
||||||
entry.s = data.s!;
|
|
||||||
} else {
|
|
||||||
component.Leaderboard.push({
|
|
||||||
s: data.s!,
|
|
||||||
n: account.DisplayName,
|
|
||||||
r: 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
component.Leaderboard.sort((a, b) => a.s - b.s); // In this case, the score is the time in milliseconds, so smaller is better.
|
|
||||||
if (component.Leaderboard.length > 10) {
|
|
||||||
component.Leaderboard.shift();
|
|
||||||
}
|
|
||||||
let r = 0;
|
|
||||||
for (const entry of component.Leaderboard) {
|
|
||||||
entry.r = ++r;
|
|
||||||
}
|
|
||||||
await guild.save();
|
|
||||||
res.status(200).end();
|
|
||||||
} else {
|
|
||||||
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
|
||||||
throw new Error(`unknown customObstacleCourseLeaderboard act: ${String(req.query.act)}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
interface ICustomObstacleCourseLeaderboardRequest {
|
|
||||||
g: string;
|
|
||||||
c: string;
|
|
||||||
s?: number; // act=p
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
import { AllianceMember, GuildMember } from "@/src/models/guildModel";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
|
|
||||||
export const declineAllianceInviteController: RequestHandler = async (req, res) => {
|
|
||||||
// Check requester is a warlord in their guild
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const guildMember = (await GuildMember.findOne({ accountId, status: 0 }))!;
|
|
||||||
if (guildMember.rank > 1) {
|
|
||||||
res.status(400).json({ Error: 104 });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await AllianceMember.deleteOne({ allianceId: req.query.allianceId, guildId: guildMember.guildId });
|
|
||||||
|
|
||||||
res.end();
|
|
||||||
};
|
|
@ -1,67 +0,0 @@
|
|||||||
import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
|
|
||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
|
||||||
import { GuildPermission } from "@/src/types/guildTypes";
|
|
||||||
import { parallelForeach } from "@/src/utils/async-utils";
|
|
||||||
import { logger } from "@/src/utils/logger";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
|
|
||||||
export const divvyAllianceVaultController: RequestHandler = async (req, res) => {
|
|
||||||
// Afaict, there's no way to put anything other than credits in the alliance vault (anymore?), so just no-op if this is not a request to divvy credits.
|
|
||||||
if (req.query.credits == "1") {
|
|
||||||
// Check requester is a warlord in their guild
|
|
||||||
const account = await getAccountForRequest(req);
|
|
||||||
const guildMember = (await GuildMember.findOne({ accountId: account._id, status: 0 }))!;
|
|
||||||
if (guildMember.rank > 1) {
|
|
||||||
res.status(400).end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check guild has treasurer permissions in the alliance
|
|
||||||
const allianceMember = (await AllianceMember.findOne({
|
|
||||||
allianceId: req.query.allianceId,
|
|
||||||
guildId: guildMember.guildId
|
|
||||||
}))!;
|
|
||||||
if (!(allianceMember.Permissions & GuildPermission.Treasurer)) {
|
|
||||||
res.status(400).end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const allianceMembers = await AllianceMember.find({ allianceId: req.query.allianceId });
|
|
||||||
const memberCounts: Record<string, number> = {};
|
|
||||||
let totalMembers = 0;
|
|
||||||
await parallelForeach(allianceMembers, async allianceMember => {
|
|
||||||
const memberCount = await GuildMember.countDocuments({
|
|
||||||
guildId: allianceMember.guildId
|
|
||||||
});
|
|
||||||
memberCounts[allianceMember.guildId.toString()] = memberCount;
|
|
||||||
totalMembers += memberCount;
|
|
||||||
});
|
|
||||||
logger.debug(`alliance has ${totalMembers} members between all its clans`);
|
|
||||||
|
|
||||||
const alliance = (await Alliance.findById(allianceMember.allianceId, "VaultRegularCredits"))!;
|
|
||||||
if (alliance.VaultRegularCredits) {
|
|
||||||
let creditsHandedOutInTotal = 0;
|
|
||||||
await parallelForeach(allianceMembers, async allianceMember => {
|
|
||||||
const memberCount = memberCounts[allianceMember.guildId.toString()];
|
|
||||||
const cutPercentage = memberCount / totalMembers;
|
|
||||||
const creditsToHandOut = Math.trunc(alliance.VaultRegularCredits! * cutPercentage);
|
|
||||||
logger.debug(
|
|
||||||
`${allianceMember.guildId.toString()} has ${memberCount} member(s) = ${Math.trunc(cutPercentage * 100)}% of alliance; giving ${creditsToHandOut} credit(s)`
|
|
||||||
);
|
|
||||||
if (creditsToHandOut != 0) {
|
|
||||||
await Guild.updateOne(
|
|
||||||
{ _id: allianceMember.guildId },
|
|
||||||
{ $inc: { VaultRegularCredits: creditsToHandOut } }
|
|
||||||
);
|
|
||||||
creditsHandedOutInTotal += creditsToHandOut;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
alliance.VaultRegularCredits -= creditsHandedOutInTotal;
|
|
||||||
logger.debug(
|
|
||||||
`handed out ${creditsHandedOutInTotal} credits; alliance vault now has ${alliance.VaultRegularCredits} credit(s)`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await alliance.save();
|
|
||||||
}
|
|
||||||
res.end();
|
|
||||||
};
|
|
@ -1,4 +1,3 @@
|
|||||||
import { GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
|
|
||||||
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, scaleRequiredCount } from "@/src/services/guildService";
|
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, scaleRequiredCount } from "@/src/services/guildService";
|
||||||
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
@ -36,10 +35,10 @@ export const dojoComponentRushController: RequestHandler = async (req, res) => {
|
|||||||
if (request.DecoId) {
|
if (request.DecoId) {
|
||||||
const deco = component.Decos!.find(x => x._id.equals(request.DecoId))!;
|
const deco = component.Decos!.find(x => x._id.equals(request.DecoId))!;
|
||||||
const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == deco.Type)!;
|
const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == deco.Type)!;
|
||||||
processContribution(guild, deco, meta, platinumDonated);
|
processContribution(deco, meta, platinumDonated);
|
||||||
} else {
|
} else {
|
||||||
const meta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!;
|
const meta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!;
|
||||||
processContribution(guild, component, meta, platinumDonated);
|
processContribution(component, meta, platinumDonated);
|
||||||
|
|
||||||
const entry = guild.RoomChanges?.find(x => x.componentId.equals(component._id));
|
const entry = guild.RoomChanges?.find(x => x.componentId.equals(component._id));
|
||||||
if (entry) {
|
if (entry) {
|
||||||
@ -47,25 +46,16 @@ export const dojoComponentRushController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const guildMember = (await GuildMember.findOne({ accountId, guildId: guild._id }, "PremiumCreditsContributed"))!;
|
await guild.save();
|
||||||
guildMember.PremiumCreditsContributed ??= 0;
|
await inventory.save();
|
||||||
guildMember.PremiumCreditsContributed += request.Amount;
|
|
||||||
|
|
||||||
await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
|
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
...(await getDojoClient(guild, 0, component._id)),
|
...(await getDojoClient(guild, 0, component._id)),
|
||||||
InventoryChanges: inventoryChanges
|
InventoryChanges: inventoryChanges
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const processContribution = (
|
const processContribution = (component: IDojoContributable, meta: IDojoBuild, platinumDonated: number): void => {
|
||||||
guild: TGuildDatabaseDocument,
|
const fullPlatinumCost = scaleRequiredCount(meta.skipTimePrice);
|
||||||
component: IDojoContributable,
|
|
||||||
meta: IDojoBuild,
|
|
||||||
platinumDonated: number
|
|
||||||
): void => {
|
|
||||||
const fullPlatinumCost = scaleRequiredCount(guild.Tier, meta.skipTimePrice);
|
|
||||||
const fullDurationSeconds = meta.time;
|
const fullDurationSeconds = meta.time;
|
||||||
const secondsPerPlatinum = fullDurationSeconds / fullPlatinumCost;
|
const secondsPerPlatinum = fullDurationSeconds / fullPlatinumCost;
|
||||||
component.CompletionTime = new Date(
|
component.CompletionTime = new Date(
|
||||||
|
@ -1,11 +1,5 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
// Arbiter Dojo endpoints, not really used by us as we don't provide a ContentURL.
|
|
||||||
|
|
||||||
export const dojoController: RequestHandler = (_req, res) => {
|
export const dojoController: RequestHandler = (_req, res) => {
|
||||||
res.json("-1"); // Tell client to use authorised request.
|
res.json("-1"); // Tell client to use authorised request.
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setDojoURLController: RequestHandler = (_req, res) => {
|
|
||||||
res.end();
|
|
||||||
};
|
|
||||||
|
@ -55,7 +55,7 @@ export const dronesController: RequestHandler = async (req, res) => {
|
|||||||
? new Date()
|
? new Date()
|
||||||
: new Date(Date.now() + getRandomInt(3 * 3600 * 1000, 4 * 3600 * 1000));
|
: new Date(Date.now() + getRandomInt(3 * 3600 * 1000, 4 * 3600 * 1000));
|
||||||
drone.PendingDamage =
|
drone.PendingDamage =
|
||||||
!config.noResourceExtractorDronesDamage && Math.random() < system.damageChance
|
Math.random() < system.damageChance
|
||||||
? getRandomInt(system.droneDamage.minValue, system.droneDamage.maxValue)
|
? getRandomInt(system.droneDamage.minValue, system.droneDamage.maxValue)
|
||||||
: 0;
|
: 0;
|
||||||
const resource = getRandomWeightedRewardUc(system.resources, droneMeta.probabilities)!;
|
const resource = getRandomWeightedRewardUc(system.resources, droneMeta.probabilities)!;
|
||||||
|
@ -1,529 +1,60 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { IEndlessXpReward, IInventoryClient, TEndlessXpCategory } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { TEndlessXpCategory } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
|
||||||
import { ExportRewards, ICountedStoreItem } from "warframe-public-export-plus";
|
|
||||||
import { getRandomElement } from "@/src/services/rngService";
|
|
||||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
|
||||||
|
|
||||||
export const endlessXpController: RequestHandler = async (req, res) => {
|
export const endlessXpController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
const payload = getJSONfromString<IEndlessXpRequest>(String(req.body));
|
const payload = getJSONfromString<IEndlessXpRequest>(String(req.body));
|
||||||
if (payload.Mode == "r") {
|
|
||||||
const inventory = await getInventory(accountId, "EndlessXP");
|
|
||||||
inventory.EndlessXP ??= [];
|
|
||||||
let entry = inventory.EndlessXP.find(x => x.Category == payload.Category);
|
|
||||||
if (!entry) {
|
|
||||||
entry = {
|
|
||||||
Category: payload.Category,
|
|
||||||
Earn: 0,
|
|
||||||
Claim: 0,
|
|
||||||
Choices: payload.Choices,
|
|
||||||
PendingRewards: []
|
|
||||||
};
|
|
||||||
inventory.EndlessXP.push(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
const weekStart = 1734307200_000 + Math.trunc((Date.now() - 1734307200_000) / 604800000) * 604800000;
|
inventory.EndlessXP ??= [];
|
||||||
const weekEnd = weekStart + 604800000;
|
const entry = inventory.EndlessXP.find(x => x.Category == payload.Category);
|
||||||
|
if (entry) {
|
||||||
entry.Earn = 0;
|
|
||||||
entry.Claim = 0;
|
|
||||||
entry.BonusAvailable = new Date(weekStart);
|
|
||||||
entry.Expiry = new Date(weekEnd);
|
|
||||||
entry.Choices = payload.Choices;
|
entry.Choices = payload.Choices;
|
||||||
entry.PendingRewards =
|
|
||||||
payload.Category == "EXC_HARD"
|
|
||||||
? generateHardModeRewards(payload.Choices)
|
|
||||||
: generateNormalModeRewards(payload.Choices);
|
|
||||||
|
|
||||||
await inventory.save();
|
|
||||||
res.json({
|
|
||||||
NewProgress: inventory.toJSON<IInventoryClient>().EndlessXP!.find(x => x.Category == payload.Category)!
|
|
||||||
});
|
|
||||||
} else if (payload.Mode == "c") {
|
|
||||||
const inventory = await getInventory(accountId);
|
|
||||||
const entry = inventory.EndlessXP!.find(x => x.Category == payload.Category)!;
|
|
||||||
const inventoryChanges: IInventoryChanges = {};
|
|
||||||
for (const reward of entry.PendingRewards) {
|
|
||||||
if (entry.Claim < reward.RequiredTotalXp && reward.RequiredTotalXp <= entry.Earn) {
|
|
||||||
combineInventoryChanges(
|
|
||||||
inventoryChanges,
|
|
||||||
(
|
|
||||||
await handleStoreItemAcquisition(
|
|
||||||
reward.Rewards[0].StoreItem,
|
|
||||||
inventory,
|
|
||||||
reward.Rewards[0].ItemCount
|
|
||||||
)
|
|
||||||
).InventoryChanges
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
entry.Claim = entry.Earn;
|
|
||||||
await inventory.save();
|
|
||||||
res.json({
|
|
||||||
InventoryChanges: inventoryChanges,
|
|
||||||
ClaimedXp: entry.Claim
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
inventory.EndlessXP.push({
|
||||||
throw new Error(`unexpected endlessXp mode: ${payload.Mode}`);
|
Category: payload.Category,
|
||||||
|
Choices: payload.Choices
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
await inventory.save();
|
||||||
|
|
||||||
type IEndlessXpRequest =
|
res.json({
|
||||||
| {
|
NewProgress: {
|
||||||
Mode: "r";
|
Category: payload.Category,
|
||||||
Category: TEndlessXpCategory;
|
Earn: 0,
|
||||||
Choices: string[];
|
Claim: 0,
|
||||||
}
|
BonusAvailable: {
|
||||||
| {
|
$date: {
|
||||||
Mode: "c" | "something else";
|
$numberLong: "9999999999999"
|
||||||
Category: TEndlessXpCategory;
|
|
||||||
};
|
|
||||||
|
|
||||||
const generateRandomRewards = (deckName: string): ICountedStoreItem[] => {
|
|
||||||
const reward = getRandomElement(ExportRewards[deckName][0])!;
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
StoreItem: reward.type,
|
|
||||||
ItemCount: reward.itemCount
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
const normalModeChosenRewards: Record<string, string[]> = {
|
|
||||||
Excalibur: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Excalibur/RadialJavelinAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburBlueprint"
|
|
||||||
],
|
|
||||||
Trinity: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinityHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinityChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Trinity/EnergyVampireAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinitySystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinityBlueprint"
|
|
||||||
],
|
|
||||||
Ember: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Ember/WorldOnFireAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberBlueprint"
|
|
||||||
],
|
|
||||||
Loki: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKIHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKIChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Loki/InvisibilityAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKISystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKIBlueprint"
|
|
||||||
],
|
|
||||||
Mag: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Mag/CrushAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagBlueprint"
|
|
||||||
],
|
|
||||||
Rhino: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Rhino/RhinoChargeAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoBlueprint"
|
|
||||||
],
|
|
||||||
Ash: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Ninja/GlaiveAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshBlueprint"
|
|
||||||
],
|
|
||||||
Frost: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Frost/IceShieldAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostBlueprint"
|
|
||||||
],
|
|
||||||
Nyx: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Jade/SelfBulletAttractorAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxBlueprint"
|
|
||||||
],
|
|
||||||
Saryn: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Saryn/PoisonAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynBlueprint"
|
|
||||||
],
|
|
||||||
Vauban: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Trapper/LevTrapAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperBlueprint"
|
|
||||||
],
|
|
||||||
Nova: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/AntiMatter/MolecularPrimeAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaBlueprint"
|
|
||||||
],
|
|
||||||
Nekros: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Necro/CloneTheDeadAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroBlueprint"
|
|
||||||
],
|
|
||||||
Valkyr: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Berserker/IntimidateAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerBlueprint"
|
|
||||||
],
|
|
||||||
Oberon: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Paladin/RegenerationAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinBlueprint"
|
|
||||||
],
|
|
||||||
Hydroid: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Pirate/CannonBarrageAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidBlueprint"
|
|
||||||
],
|
|
||||||
Mirage: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Harlequin/LightAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinBlueprint"
|
|
||||||
],
|
|
||||||
Limbo: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Magician/TearInSpaceAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianBlueprint"
|
|
||||||
],
|
|
||||||
Mesa: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Cowgirl/GunFuPvPAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerBlueprint"
|
|
||||||
],
|
|
||||||
Chroma: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Dragon/DragonLuckAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaBlueprint"
|
|
||||||
],
|
|
||||||
Atlas: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Brawler/BrawlerPassiveAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerBlueprint"
|
|
||||||
],
|
|
||||||
Ivara: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Ranger/RangerStealAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerBlueprint"
|
|
||||||
],
|
|
||||||
Inaros: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummyHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummyChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Sandman/SandmanSwarmAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummySystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummyBlueprint"
|
|
||||||
],
|
|
||||||
Titania: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairyHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairyChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Fairy/FairyFlightAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairySystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairyBlueprint"
|
|
||||||
],
|
|
||||||
Nidus: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Infestation/InfestPodsAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusBlueprint"
|
|
||||||
],
|
|
||||||
Octavia: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Bard/BardCharmAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaBlueprint"
|
|
||||||
],
|
|
||||||
Harrow: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Priest/PriestPactAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestBlueprint"
|
|
||||||
],
|
|
||||||
Gara: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Glass/GlassFragmentAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassBlueprint"
|
|
||||||
],
|
|
||||||
Khora: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Khora/KhoraCrackAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraBlueprint"
|
|
||||||
],
|
|
||||||
Revenant: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Revenant/RevenantMarkAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantBlueprint"
|
|
||||||
],
|
|
||||||
Garuda: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Garuda/GarudaUnstoppableAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaBlueprint"
|
|
||||||
],
|
|
||||||
Baruuk: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/Pacifist/PacifistFistAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistBlueprint"
|
|
||||||
],
|
|
||||||
Hildryn: [
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeHelmetBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeChassisBlueprint",
|
|
||||||
"/Lotus/StoreItems/Powersuits/IronFrame/IronFrameStripAugmentCard",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeSystemsBlueprint",
|
|
||||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeBlueprint"
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
const generateNormalModeRewards = (choices: string[]): IEndlessXpReward[] => {
|
|
||||||
const choiceRewards = normalModeChosenRewards[choices[0]];
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 190,
|
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalSilverRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 400,
|
|
||||||
Rewards: [
|
|
||||||
{
|
|
||||||
StoreItem: choiceRewards[0],
|
|
||||||
ItemCount: 1
|
|
||||||
}
|
}
|
||||||
]
|
},
|
||||||
},
|
Expiry: {
|
||||||
{
|
$date: {
|
||||||
RequiredTotalXp: 630,
|
$numberLong: "9999999999999"
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalSilverRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 890,
|
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalMODRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 1190,
|
|
||||||
Rewards: [
|
|
||||||
{
|
|
||||||
StoreItem: choiceRewards[1],
|
|
||||||
ItemCount: 1
|
|
||||||
}
|
}
|
||||||
]
|
},
|
||||||
},
|
Choices: payload.Choices,
|
||||||
{
|
PendingRewards: [
|
||||||
RequiredTotalXp: 1540,
|
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalGoldRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 1950,
|
|
||||||
Rewards: [
|
|
||||||
{
|
{
|
||||||
StoreItem: choiceRewards[2],
|
RequiredTotalXp: 190,
|
||||||
ItemCount: 1
|
Rewards: [
|
||||||
}
|
{
|
||||||
]
|
StoreItem: "/Lotus/StoreItems/Upgrades/Mods/Aura/PlayerHealthAuraMod",
|
||||||
},
|
ItemCount: 1
|
||||||
{
|
}
|
||||||
RequiredTotalXp: 2430,
|
]
|
||||||
Rewards: [
|
|
||||||
{
|
|
||||||
StoreItem: choiceRewards[3],
|
|
||||||
ItemCount: 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 2990,
|
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalArcaneRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 3640,
|
|
||||||
Rewards: [
|
|
||||||
{
|
|
||||||
StoreItem: choiceRewards[4],
|
|
||||||
ItemCount: 1
|
|
||||||
}
|
}
|
||||||
|
// ...
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
];
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const hardModeChosenRewards: Record<string, string> = {
|
interface IEndlessXpRequest {
|
||||||
Braton: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BratonIncarnonUnlocker",
|
Mode: string; // "r"
|
||||||
Lato: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/LatoIncarnonUnlocker",
|
Category: TEndlessXpCategory;
|
||||||
Skana: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/SkanaIncarnonUnlocker",
|
Choices: string[];
|
||||||
Paris: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/ParisIncarnonUnlocker",
|
}
|
||||||
Kunai: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/KunaiIncarnonUnlocker",
|
|
||||||
Boar: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BoarIncarnonUnlocker",
|
|
||||||
Gammacor: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/GammacorIncarnonUnlocker",
|
|
||||||
Anku: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/AnkuIncarnonUnlocker",
|
|
||||||
Gorgon: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/GorgonIncarnonUnlocker",
|
|
||||||
Angstrum: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/AngstrumIncarnonUnlocker",
|
|
||||||
Bo: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/BoIncarnonUnlocker",
|
|
||||||
Latron: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/LatronIncarnonUnlocker",
|
|
||||||
Furis: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/FurisIncarnonUnlocker",
|
|
||||||
Furax: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/FuraxIncarnonUnlocker",
|
|
||||||
Strun: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/StrunIncarnonUnlocker",
|
|
||||||
Lex: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/LexIncarnonUnlocker",
|
|
||||||
Magistar: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/MagistarIncarnonUnlocker",
|
|
||||||
Boltor: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BoltorIncarnonUnlocker",
|
|
||||||
Bronco: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/BroncoIncarnonUnlocker",
|
|
||||||
CeramicDagger: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/CeramicDaggerIncarnonUnlocker",
|
|
||||||
Torid: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/ToridIncarnonUnlocker",
|
|
||||||
DualToxocyst: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/DualToxocystIncarnonUnlocker",
|
|
||||||
DualIchor: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/DualIchorIncarnonUnlocker",
|
|
||||||
Miter: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/MiterIncarnonUnlocker",
|
|
||||||
Atomos: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/AtomosIncarnonUnlocker",
|
|
||||||
AckAndBrunt: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/AckAndBruntIncarnonUnlocker",
|
|
||||||
Soma: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/SomaIncarnonUnlocker",
|
|
||||||
Vasto: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/VastoIncarnonUnlocker",
|
|
||||||
NamiSolo: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/NamiSoloIncarnonUnlocker",
|
|
||||||
Burston: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BurstonIncarnonUnlocker",
|
|
||||||
Zylok: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/ZylokIncarnonUnlocker",
|
|
||||||
Sibear: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/SibearIncarnonUnlocker",
|
|
||||||
Dread: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/DreadIncarnonUnlocker",
|
|
||||||
Despair: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/DespairIncarnonUnlocker",
|
|
||||||
Hate: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/HateIncarnonUnlocker",
|
|
||||||
Dera: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/DeraIncarnonUnlocker",
|
|
||||||
Cestra: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/CestraIncarnonUnlocker",
|
|
||||||
Okina: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/OkinaIncarnonUnlocker",
|
|
||||||
Sybaris: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/SybarisIncarnonUnlocker",
|
|
||||||
Sicarus: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/SicarusIncarnonUnlocker",
|
|
||||||
RivenPrimary: "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawRifleRandomMod",
|
|
||||||
RivenSecondary: "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawPistolRandomMod",
|
|
||||||
RivenMelee: "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawMeleeRandomMod",
|
|
||||||
Kuva: "/Lotus/Types/Game/DuviriEndless/CircuitSteelPathBIGKuvaReward"
|
|
||||||
};
|
|
||||||
|
|
||||||
const generateHardModeRewards = (choices: string[]): IEndlessXpReward[] => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 285,
|
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSilverRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 600,
|
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathArcaneRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 945,
|
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSilverRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 1335,
|
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSilverRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 1785,
|
|
||||||
Rewards: [
|
|
||||||
{
|
|
||||||
StoreItem: hardModeChosenRewards[choices[0]],
|
|
||||||
ItemCount: 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 2310,
|
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathGoldRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 2925,
|
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathGoldRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 3645,
|
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathArcaneRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 4485,
|
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSteelEssenceRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 5460,
|
|
||||||
Rewards: [
|
|
||||||
{
|
|
||||||
StoreItem: hardModeChosenRewards[choices[1]],
|
|
||||||
ItemCount: 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
@ -1,71 +0,0 @@
|
|||||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
|
|
||||||
export const entratiLabConquestModeController: RequestHandler = async (req, res) => {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const inventory = await getInventory(
|
|
||||||
accountId,
|
|
||||||
"EntratiVaultCountResetDate EntratiVaultCountLastPeriod EntratiLabConquestUnlocked EchoesHexConquestUnlocked EchoesHexConquestActiveFrameVariants EchoesHexConquestActiveStickers EntratiLabConquestActiveFrameVariants EntratiLabConquestCacheScoreMission EchoesHexConquestCacheScoreMission"
|
|
||||||
);
|
|
||||||
const body = getJSONfromString<IEntratiLabConquestModeRequest>(String(req.body));
|
|
||||||
if (!inventory.EntratiVaultCountResetDate || Date.now() >= inventory.EntratiVaultCountResetDate.getTime()) {
|
|
||||||
const EPOCH = 1734307200 * 1000; // Mondays, amirite?
|
|
||||||
const day = Math.trunc((Date.now() - EPOCH) / 86400000);
|
|
||||||
const week = Math.trunc(day / 7);
|
|
||||||
const weekStart = EPOCH + week * 604800000;
|
|
||||||
const weekEnd = weekStart + 604800000;
|
|
||||||
inventory.EntratiVaultCountLastPeriod = 0;
|
|
||||||
inventory.EntratiVaultCountResetDate = new Date(weekEnd);
|
|
||||||
if (inventory.EntratiLabConquestUnlocked) {
|
|
||||||
inventory.EntratiLabConquestUnlocked = 0;
|
|
||||||
inventory.EntratiLabConquestCacheScoreMission = 0;
|
|
||||||
inventory.EntratiLabConquestActiveFrameVariants = [];
|
|
||||||
}
|
|
||||||
if (inventory.EchoesHexConquestUnlocked) {
|
|
||||||
inventory.EchoesHexConquestUnlocked = 0;
|
|
||||||
inventory.EchoesHexConquestCacheScoreMission = 0;
|
|
||||||
inventory.EchoesHexConquestActiveFrameVariants = [];
|
|
||||||
inventory.EchoesHexConquestActiveStickers = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (body.BuyMode) {
|
|
||||||
inventory.EntratiVaultCountLastPeriod! += 2;
|
|
||||||
if (body.IsEchoesDeepArchemedea) {
|
|
||||||
inventory.EchoesHexConquestUnlocked = 1;
|
|
||||||
} else {
|
|
||||||
inventory.EntratiLabConquestUnlocked = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (body.IsEchoesDeepArchemedea) {
|
|
||||||
if (inventory.EchoesHexConquestUnlocked) {
|
|
||||||
inventory.EchoesHexConquestActiveFrameVariants = body.EchoesHexConquestActiveFrameVariants!;
|
|
||||||
inventory.EchoesHexConquestActiveStickers = body.EchoesHexConquestActiveStickers!;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (inventory.EntratiLabConquestUnlocked) {
|
|
||||||
inventory.EntratiLabConquestActiveFrameVariants = body.EntratiLabConquestActiveFrameVariants!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await inventory.save();
|
|
||||||
res.json({
|
|
||||||
EntratiVaultCountResetDate: toMongoDate(inventory.EntratiVaultCountResetDate),
|
|
||||||
EntratiVaultCountLastPeriod: inventory.EntratiVaultCountLastPeriod,
|
|
||||||
EntratiLabConquestUnlocked: inventory.EntratiLabConquestUnlocked,
|
|
||||||
EntratiLabConquestCacheScoreMission: inventory.EntratiLabConquestCacheScoreMission,
|
|
||||||
EchoesHexConquestUnlocked: inventory.EchoesHexConquestUnlocked,
|
|
||||||
EchoesHexConquestCacheScoreMission: inventory.EchoesHexConquestCacheScoreMission
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IEntratiLabConquestModeRequest {
|
|
||||||
BuyMode?: number;
|
|
||||||
IsEchoesDeepArchemedea?: number;
|
|
||||||
EntratiLabConquestUnlocked?: number;
|
|
||||||
EntratiLabConquestActiveFrameVariants?: string[];
|
|
||||||
EchoesHexConquestUnlocked?: number;
|
|
||||||
EchoesHexConquestActiveFrameVariants?: string[];
|
|
||||||
EchoesHexConquestActiveStickers?: string[];
|
|
||||||
}
|
|
@ -17,7 +17,7 @@ export const evolveWeaponController: RequestHandler = async (req, res) => {
|
|||||||
recipe.ingredients.map(x => ({ ItemType: x.ItemType, ItemCount: x.ItemCount * -1 }))
|
recipe.ingredients.map(x => ({ ItemType: x.ItemType, ItemCount: x.ItemCount * -1 }))
|
||||||
);
|
);
|
||||||
|
|
||||||
const item = inventory[payload.Category].id(req.query.ItemId as string)!;
|
const item = inventory[payload.Category].find(item => item._id.toString() == (req.query.ItemId as string))!;
|
||||||
item.Features ??= 0;
|
item.Features ??= 0;
|
||||||
item.Features |= EquipmentFeatures.INCARNON_GENESIS;
|
item.Features |= EquipmentFeatures.INCARNON_GENESIS;
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ export const evolveWeaponController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const item = inventory[payload.Category].id(req.query.ItemId as string)!;
|
const item = inventory[payload.Category].find(item => item._id.toString() == (req.query.ItemId as string))!;
|
||||||
item.Features! &= ~EquipmentFeatures.INCARNON_GENESIS;
|
item.Features! &= ~EquipmentFeatures.INCARNON_GENESIS;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`unexpected evolve weapon action: ${payload.Action}`);
|
throw new Error(`unexpected evolve weapon action: ${payload.Action}`);
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { addMiscItems, addStanding, getInventory } from "@/src/services/inventoryService";
|
import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper";
|
||||||
|
import { addMiscItems, getInventory, getStandingLimit, updateStandingLimit } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { ExportResources } from "warframe-public-export-plus";
|
import { ExportResources, ExportSyndicates } from "warframe-public-export-plus";
|
||||||
|
|
||||||
export const fishmongerController: RequestHandler = async (req, res) => {
|
export const fishmongerController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
@ -30,7 +31,25 @@ export const fishmongerController: RequestHandler = async (req, res) => {
|
|||||||
miscItemChanges.push({ ItemType: fish.ItemType, ItemCount: fish.ItemCount * -1 });
|
miscItemChanges.push({ ItemType: fish.ItemType, ItemCount: fish.ItemCount * -1 });
|
||||||
}
|
}
|
||||||
addMiscItems(inventory, miscItemChanges);
|
addMiscItems(inventory, miscItemChanges);
|
||||||
if (gainedStanding && syndicateTag) addStanding(inventory, syndicateTag, gainedStanding);
|
if (gainedStanding && syndicateTag) {
|
||||||
|
let syndicate = inventory.Affiliations.find(x => x.Tag == syndicateTag);
|
||||||
|
if (!syndicate) {
|
||||||
|
syndicate = inventory.Affiliations[inventory.Affiliations.push({ Tag: syndicateTag, Standing: 0 }) - 1];
|
||||||
|
}
|
||||||
|
const syndicateMeta = ExportSyndicates[syndicateTag];
|
||||||
|
|
||||||
|
const max = getMaxStanding(syndicateMeta, syndicate.Title ?? 0);
|
||||||
|
if (syndicate.Standing + gainedStanding > max) {
|
||||||
|
gainedStanding = max - syndicate.Standing;
|
||||||
|
}
|
||||||
|
if (gainedStanding > getStandingLimit(inventory, syndicateMeta.dailyLimitBin)) {
|
||||||
|
gainedStanding = getStandingLimit(inventory, syndicateMeta.dailyLimitBin);
|
||||||
|
}
|
||||||
|
|
||||||
|
syndicate.Standing += gainedStanding;
|
||||||
|
|
||||||
|
updateStandingLimit(inventory, syndicateMeta.dailyLimitBin, gainedStanding);
|
||||||
|
}
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({
|
res.json({
|
||||||
InventoryChanges: {
|
InventoryChanges: {
|
||||||
|
@ -18,15 +18,17 @@ export const focusController: RequestHandler = async (req, res) => {
|
|||||||
case FocusOperation.InstallLens: {
|
case FocusOperation.InstallLens: {
|
||||||
const request = JSON.parse(String(req.body)) as ILensInstallRequest;
|
const request = JSON.parse(String(req.body)) as ILensInstallRequest;
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
const item = inventory[request.Category].id(request.WeaponId);
|
for (const item of inventory[request.Category]) {
|
||||||
if (item) {
|
if (item._id.toString() == request.WeaponId) {
|
||||||
item.FocusLens = request.LensType;
|
item.FocusLens = request.LensType;
|
||||||
addMiscItems(inventory, [
|
addMiscItems(inventory, [
|
||||||
{
|
{
|
||||||
ItemType: request.LensType,
|
ItemType: request.LensType,
|
||||||
ItemCount: -1
|
ItemCount: -1
|
||||||
} satisfies IMiscItem
|
} satisfies IMiscItem
|
||||||
]);
|
]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({
|
res.json({
|
||||||
@ -43,7 +45,7 @@ export const focusController: RequestHandler = async (req, res) => {
|
|||||||
inventory.FocusAbility ??= focusType;
|
inventory.FocusAbility ??= focusType;
|
||||||
inventory.FocusUpgrades.push({ ItemType: focusType });
|
inventory.FocusUpgrades.push({ ItemType: focusType });
|
||||||
if (inventory.FocusXP) {
|
if (inventory.FocusXP) {
|
||||||
inventory.FocusXP[focusPolarity]! -= cost;
|
inventory.FocusXP[focusPolarity] -= cost;
|
||||||
}
|
}
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({
|
res.json({
|
||||||
@ -64,9 +66,7 @@ export const focusController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
res.json({
|
res.end();
|
||||||
FocusUpgrade: { ItemType: focusType }
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case FocusOperation.UnlockUpgrade: {
|
case FocusOperation.UnlockUpgrade: {
|
||||||
@ -78,7 +78,7 @@ export const focusController: RequestHandler = async (req, res) => {
|
|||||||
cost += ExportFocusUpgrades[focusType].baseFocusPointCost;
|
cost += ExportFocusUpgrades[focusType].baseFocusPointCost;
|
||||||
inventory.FocusUpgrades.push({ ItemType: focusType, Level: 0 });
|
inventory.FocusUpgrades.push({ ItemType: focusType, Level: 0 });
|
||||||
}
|
}
|
||||||
inventory.FocusXP![focusPolarity]! -= cost;
|
inventory.FocusXP![focusPolarity] -= cost;
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({
|
res.json({
|
||||||
FocusTypes: request.FocusTypes,
|
FocusTypes: request.FocusTypes,
|
||||||
@ -96,7 +96,7 @@ export const focusController: RequestHandler = async (req, res) => {
|
|||||||
const focusUpgradeDb = inventory.FocusUpgrades.find(entry => entry.ItemType == focusUpgrade.ItemType)!;
|
const focusUpgradeDb = inventory.FocusUpgrades.find(entry => entry.ItemType == focusUpgrade.ItemType)!;
|
||||||
focusUpgradeDb.Level = focusUpgrade.Level;
|
focusUpgradeDb.Level = focusUpgrade.Level;
|
||||||
}
|
}
|
||||||
inventory.FocusXP![focusPolarity]! -= cost;
|
inventory.FocusXP![focusPolarity] -= cost;
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({
|
res.json({
|
||||||
FocusInfos: request.FocusInfos,
|
FocusInfos: request.FocusInfos,
|
||||||
@ -106,14 +106,13 @@ export const focusController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
case FocusOperation.SentTrainingAmplifier: {
|
case FocusOperation.SentTrainingAmplifier: {
|
||||||
const request = JSON.parse(String(req.body)) as ISentTrainingAmplifierRequest;
|
const request = JSON.parse(String(req.body)) as ISentTrainingAmplifierRequest;
|
||||||
|
const parts: string[] = [
|
||||||
|
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingGrip",
|
||||||
|
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingChassis",
|
||||||
|
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingBarrel"
|
||||||
|
];
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, {
|
const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, parts);
|
||||||
ModularParts: [
|
|
||||||
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingGrip",
|
|
||||||
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingChassis",
|
|
||||||
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingBarrel"
|
|
||||||
]
|
|
||||||
});
|
|
||||||
occupySlot(inventory, InventorySlot.AMPS, false);
|
occupySlot(inventory, InventorySlot.AMPS, false);
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json((inventoryChanges.OperatorAmps as IEquipmentClient[])[0]);
|
res.json((inventoryChanges.OperatorAmps as IEquipmentClient[])[0]);
|
||||||
@ -123,7 +122,7 @@ export const focusController: RequestHandler = async (req, res) => {
|
|||||||
const request = JSON.parse(String(req.body)) as IUnbindUpgradeRequest;
|
const request = JSON.parse(String(req.body)) as IUnbindUpgradeRequest;
|
||||||
const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]);
|
const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]);
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
inventory.FocusXP![focusPolarity]! -= 750_000 * request.FocusTypes.length;
|
inventory.FocusXP![focusPolarity] -= 750_000 * request.FocusTypes.length;
|
||||||
addMiscItems(inventory, [
|
addMiscItems(inventory, [
|
||||||
{
|
{
|
||||||
ItemType: "/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantItem",
|
ItemType: "/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantItem",
|
||||||
@ -168,10 +167,8 @@ export const focusController: RequestHandler = async (req, res) => {
|
|||||||
shard.ItemCount *= -1;
|
shard.ItemCount *= -1;
|
||||||
}
|
}
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
const polarity = request.Polarity;
|
inventory.FocusXP ??= { AP_POWER: 0, AP_TACTIC: 0, AP_DEFENSE: 0, AP_ATTACK: 0, AP_WARD: 0 };
|
||||||
inventory.FocusXP ??= {};
|
inventory.FocusXP[request.Polarity] += xp;
|
||||||
inventory.FocusXP[polarity] ??= 0;
|
|
||||||
inventory.FocusXP[polarity] += xp;
|
|
||||||
addMiscItems(inventory, request.Shards);
|
addMiscItems(inventory, request.Shards);
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
break;
|
break;
|
||||||
|
@ -1,84 +0,0 @@
|
|||||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|
||||||
import { addMiscItem, getInventory } from "@/src/services/inventoryService";
|
|
||||||
import { toStoreItem } from "@/src/services/itemDataService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
|
|
||||||
import { IMongoDate } from "@/src/types/commonTypes";
|
|
||||||
import { IMissionReward } from "@/src/types/missionTypes";
|
|
||||||
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
|
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
|
||||||
import { IGardeningClient } from "@/src/types/shipTypes";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
import { dict_en, ExportResources } from "warframe-public-export-plus";
|
|
||||||
|
|
||||||
export const gardeningController: RequestHandler = async (req, res) => {
|
|
||||||
const data = getJSONfromString<IGardeningRequest>(String(req.body));
|
|
||||||
if (data.Mode != "HarvestAll") {
|
|
||||||
throw new Error(`unexpected gardening mode: ${data.Mode}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const [inventory, personalRooms] = await Promise.all([
|
|
||||||
getInventory(accountId, "MiscItems"),
|
|
||||||
getPersonalRooms(accountId, "Apartment")
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Harvest plants
|
|
||||||
const inventoryChanges: IInventoryChanges = {};
|
|
||||||
const rewards: Record<string, IMissionReward[][]> = {};
|
|
||||||
for (const planter of personalRooms.Apartment.Gardening.Planters) {
|
|
||||||
rewards[planter.Name] = [];
|
|
||||||
for (const plant of planter.Plants) {
|
|
||||||
const itemType =
|
|
||||||
"/Lotus/Types/Gameplay/Duviri/Resource/DuviriPlantItem" +
|
|
||||||
plant.PlantType.substring(plant.PlantType.length - 1);
|
|
||||||
const itemCount = Math.random() < 0.775 ? 2 : 4;
|
|
||||||
|
|
||||||
addMiscItem(inventory, itemType, itemCount, inventoryChanges);
|
|
||||||
|
|
||||||
rewards[planter.Name].push([
|
|
||||||
{
|
|
||||||
StoreItem: toStoreItem(itemType),
|
|
||||||
TypeName: itemType,
|
|
||||||
ItemCount: itemCount,
|
|
||||||
DailyCooldown: false,
|
|
||||||
Rarity: itemCount == 2 ? 0.7743589743589744 : 0.22564102564102564,
|
|
||||||
TweetText: `${itemCount}x ${dict_en[ExportResources[itemType].name]} (Resource)`,
|
|
||||||
ProductCategory: "MiscItems"
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh garden
|
|
||||||
personalRooms.Apartment.Gardening = createGarden();
|
|
||||||
|
|
||||||
await Promise.all([inventory.save(), personalRooms.save()]);
|
|
||||||
|
|
||||||
const planter = personalRooms.Apartment.Gardening.Planters[personalRooms.Apartment.Gardening.Planters.length - 1];
|
|
||||||
const plant = planter.Plants[planter.Plants.length - 1];
|
|
||||||
res.json({
|
|
||||||
GardenTagName: planter.Name,
|
|
||||||
PlantType: plant.PlantType,
|
|
||||||
PlotIndex: plant.PlotIndex,
|
|
||||||
EndTime: toMongoDate(plant.EndTime),
|
|
||||||
InventoryChanges: inventoryChanges,
|
|
||||||
Gardening: personalRooms.toJSON<IPersonalRoomsClient>().Apartment.Gardening,
|
|
||||||
Rewards: rewards
|
|
||||||
} satisfies IGardeningResponse);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IGardeningRequest {
|
|
||||||
Mode: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IGardeningResponse {
|
|
||||||
GardenTagName: string;
|
|
||||||
PlantType: string;
|
|
||||||
PlotIndex: number;
|
|
||||||
EndTime: IMongoDate;
|
|
||||||
InventoryChanges: IInventoryChanges;
|
|
||||||
Gardening: IGardeningClient;
|
|
||||||
Rewards: Record<string, IMissionReward[][]>;
|
|
||||||
}
|
|
@ -10,7 +10,8 @@ import { IGenericUpdate } from "@/src/types/genericUpdate";
|
|||||||
const genericUpdateController: RequestHandler = async (request, response) => {
|
const genericUpdateController: RequestHandler = async (request, response) => {
|
||||||
const accountId = await getAccountIdForRequest(request);
|
const accountId = await getAccountIdForRequest(request);
|
||||||
const update = getJSONfromString<IGenericUpdate>(String(request.body));
|
const update = getJSONfromString<IGenericUpdate>(String(request.body));
|
||||||
response.json(await updateGeneric(update, accountId));
|
await updateGeneric(update, accountId);
|
||||||
|
response.json(update);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { genericUpdateController };
|
export { genericUpdateController };
|
||||||
|
@ -1,26 +1,7 @@
|
|||||||
import { Alliance, Guild } from "@/src/models/guildModel";
|
|
||||||
import { getAllianceClient } from "@/src/services/guildService";
|
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
export const getAllianceController: RequestHandler = async (req, res) => {
|
const getAllianceController: RequestHandler = (_req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
res.sendStatus(200);
|
||||||
const inventory = await getInventory(accountId, "GuildId");
|
|
||||||
if (inventory.GuildId) {
|
|
||||||
const guild = (await Guild.findById(inventory.GuildId, "Name Tier AllianceId"))!;
|
|
||||||
if (guild.AllianceId) {
|
|
||||||
const alliance = (await Alliance.findById(guild.AllianceId))!;
|
|
||||||
res.json(await getAllianceClient(alliance, guild));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res.end();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// POST request since U27
|
export { getAllianceController };
|
||||||
/*interface IGetAllianceRequest {
|
|
||||||
memberCount: number;
|
|
||||||
clanLeaderName: string;
|
|
||||||
clanLeaderId: string;
|
|
||||||
}*/
|
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import { DailyDeal } from "@/src/models/worldStateModel";
|
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
export const getDailyDealStockLevelsController: RequestHandler = async (req, res) => {
|
export const getDailyDealStockLevelsController: RequestHandler = (req, res) => {
|
||||||
const dailyDeal = (await DailyDeal.findOne({ StoreItem: req.query.productName }, "AmountSold"))!;
|
|
||||||
res.json({
|
res.json({
|
||||||
StoreItem: req.query.productName,
|
StoreItem: req.query.productName,
|
||||||
AmountSold: dailyDeal.AmountSold
|
AmountSold: 0
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,54 +1,14 @@
|
|||||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
import { Request, Response } from "express";
|
||||||
import { Friendship } from "@/src/models/friendModel";
|
|
||||||
import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "@/src/services/friendService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { IFriendInfo } from "@/src/types/friendTypes";
|
|
||||||
import { Request, RequestHandler, Response } from "express";
|
|
||||||
|
|
||||||
// POST with {} instead of GET as of 38.5.0
|
const getFriendsController = (_request: Request, response: Response): void => {
|
||||||
export const getFriendsController: RequestHandler = async (req: Request, res: Response) => {
|
response.writeHead(200, {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
//Connection: "keep-alive",
|
||||||
const response: IGetFriendsResponse = {
|
//"Content-Encoding": "gzip",
|
||||||
Current: [],
|
"Content-Type": "text/html",
|
||||||
IncomingFriendRequests: [],
|
// charset: "UTF - 8",
|
||||||
OutgoingFriendRequests: []
|
"Content-Length": "3"
|
||||||
};
|
});
|
||||||
const [internalFriendships, externalFriendships] = await Promise.all([
|
response.end(Buffer.from([0x7b, 0x7d, 0x0a]));
|
||||||
Friendship.find({ owner: accountId }),
|
|
||||||
Friendship.find({ friend: accountId }, "owner Note")
|
|
||||||
]);
|
|
||||||
for (const externalFriendship of externalFriendships) {
|
|
||||||
if (!internalFriendships.find(x => x.friend.equals(externalFriendship.owner))) {
|
|
||||||
response.IncomingFriendRequests.push({
|
|
||||||
_id: toOid(externalFriendship.owner),
|
|
||||||
Note: externalFriendship.Note
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const internalFriendship of internalFriendships) {
|
|
||||||
const friendInfo: IFriendInfo = {
|
|
||||||
_id: toOid(internalFriendship.friend)
|
|
||||||
};
|
|
||||||
if (externalFriendships.find(x => x.owner.equals(internalFriendship.friend))) {
|
|
||||||
response.Current.push(friendInfo);
|
|
||||||
} else {
|
|
||||||
response.OutgoingFriendRequests.push(friendInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const promises: Promise<void>[] = [];
|
|
||||||
for (const arr of Object.values(response)) {
|
|
||||||
for (const friendInfo of arr) {
|
|
||||||
promises.push(addAccountDataToFriendInfo(friendInfo));
|
|
||||||
promises.push(addInventoryDataToFriendInfo(friendInfo));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await Promise.all(promises);
|
|
||||||
res.json(response);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// interface IGetFriendsResponse {
|
export { getFriendsController };
|
||||||
// Current: IFriendInfo[];
|
|
||||||
// IncomingFriendRequests: IFriendInfo[];
|
|
||||||
// OutgoingFriendRequests: IFriendInfo[];
|
|
||||||
// }
|
|
||||||
type IGetFriendsResponse = Record<"Current" | "IncomingFriendRequests" | "OutgoingFriendRequests", IFriendInfo[]>;
|
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
import { GuildMember } from "@/src/models/guildModel";
|
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { IGuildMemberClient } from "@/src/types/guildTypes";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
|
|
||||||
export const getGuildContributionsController: RequestHandler = async (req, res) => {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const guildId = (await getInventory(accountId, "GuildId")).GuildId;
|
|
||||||
const guildMember = (await GuildMember.findOne({ guildId, accountId: req.query.buddyId }))!;
|
|
||||||
res.json({
|
|
||||||
_id: { $oid: req.query.buddyId as string },
|
|
||||||
RegularCreditsContributed: guildMember.RegularCreditsContributed,
|
|
||||||
PremiumCreditsContributed: guildMember.PremiumCreditsContributed,
|
|
||||||
MiscItemsContributed: guildMember.MiscItemsContributed,
|
|
||||||
ConsumablesContributed: [], // ???
|
|
||||||
ShipDecorationsContributed: guildMember.ShipDecorationsContributed
|
|
||||||
} satisfies Partial<IGuildMemberClient>);
|
|
||||||
};
|
|
@ -1,15 +1,15 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { Guild } from "@/src/models/guildModel";
|
import { Guild } from "@/src/models/guildModel";
|
||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { createUniqueClanName, getGuildClient } from "@/src/services/guildService";
|
import { createUniqueClanName, getGuildClient } from "@/src/services/guildService";
|
||||||
|
|
||||||
export const getGuildController: RequestHandler = async (req, res) => {
|
const getGuildController: RequestHandler = async (req, res) => {
|
||||||
const account = await getAccountForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await getInventory(account._id.toString(), "GuildId");
|
const inventory = await getInventory(accountId, "GuildId");
|
||||||
if (inventory.GuildId) {
|
if (inventory.GuildId) {
|
||||||
const guild = await Guild.findById(inventory.GuildId);
|
const guild = await Guild.findOne({ _id: inventory.GuildId });
|
||||||
if (guild) {
|
if (guild) {
|
||||||
// Handle guilds created before we added discriminators
|
// Handle guilds created before we added discriminators
|
||||||
if (guild.Name.indexOf("#") == -1) {
|
if (guild.Name.indexOf("#") == -1) {
|
||||||
@ -24,9 +24,11 @@ export const getGuildController: RequestHandler = async (req, res) => {
|
|||||||
guild.CeremonyResetDate = undefined;
|
guild.CeremonyResetDate = undefined;
|
||||||
await guild.save();
|
await guild.save();
|
||||||
}
|
}
|
||||||
res.json(await getGuildClient(guild, account));
|
res.json(await getGuildClient(guild, accountId));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res.end();
|
res.sendStatus(200);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export { getGuildController };
|
||||||
|
@ -2,12 +2,11 @@ import { RequestHandler } from "express";
|
|||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { Guild } from "@/src/models/guildModel";
|
import { Guild } from "@/src/models/guildModel";
|
||||||
import { getDojoClient } from "@/src/services/guildService";
|
import { getDojoClient } from "@/src/services/guildService";
|
||||||
import { Account } from "@/src/models/loginModel";
|
|
||||||
|
|
||||||
export const getGuildDojoController: RequestHandler = async (req, res) => {
|
export const getGuildDojoController: RequestHandler = async (req, res) => {
|
||||||
const guildId = req.query.guildId as string;
|
const guildId = req.query.guildId as string;
|
||||||
|
|
||||||
const guild = await Guild.findById(guildId);
|
const guild = await Guild.findOne({ _id: guildId });
|
||||||
if (!guild) {
|
if (!guild) {
|
||||||
res.status(404).end();
|
res.status(404).end();
|
||||||
return;
|
return;
|
||||||
@ -26,8 +25,7 @@ export const getGuildDojoController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const payload: IGetGuildDojoRequest = req.body ? (JSON.parse(String(req.body)) as IGetGuildDojoRequest) : {};
|
const payload: IGetGuildDojoRequest = req.body ? (JSON.parse(String(req.body)) as IGetGuildDojoRequest) : {};
|
||||||
const account = await Account.findById(req.query.accountId as string);
|
res.json(await getDojoClient(guild, 0, payload.ComponentId));
|
||||||
res.json(await getDojoClient(guild, 0, payload.ComponentId, account?.BuildLabel));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IGetGuildDojoRequest {
|
interface IGetGuildDojoRequest {
|
||||||
|
@ -9,7 +9,7 @@ export const getGuildLogController: RequestHandler = async (req, res) => {
|
|||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await getInventory(accountId, "GuildId");
|
const inventory = await getInventory(accountId, "GuildId");
|
||||||
if (inventory.GuildId) {
|
if (inventory.GuildId) {
|
||||||
const guild = await Guild.findById(inventory.GuildId);
|
const guild = await Guild.findOne({ _id: inventory.GuildId });
|
||||||
if (guild) {
|
if (guild) {
|
||||||
const log: Record<string, IGuildLogEntryClient[]> = {
|
const log: Record<string, IGuildLogEntryClient[]> = {
|
||||||
RoomChanges: [],
|
RoomChanges: [],
|
||||||
|
@ -1,20 +1,16 @@
|
|||||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
|
||||||
import { Account, Ignore } from "@/src/models/loginModel";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { IFriendInfo } from "@/src/types/friendTypes";
|
|
||||||
import { parallelForeach } from "@/src/utils/async-utils";
|
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
export const getIgnoredUsersController: RequestHandler = async (req, res) => {
|
const getIgnoredUsersController: RequestHandler = (_req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
res.writeHead(200, {
|
||||||
const ignores = await Ignore.find({ ignorer: accountId });
|
"Content-Type": "text/html",
|
||||||
const ignoredUsers: IFriendInfo[] = [];
|
"Content-Length": "3"
|
||||||
await parallelForeach(ignores, async ignore => {
|
|
||||||
const ignoreeAccount = (await Account.findById(ignore.ignoree, "DisplayName"))!;
|
|
||||||
ignoredUsers.push({
|
|
||||||
_id: toOid(ignore.ignoree),
|
|
||||||
DisplayName: ignoreeAccount.DisplayName + ""
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
res.json({ IgnoredUsers: ignoredUsers });
|
res.end(
|
||||||
|
Buffer.from([
|
||||||
|
0x7b, 0x22, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x22, 0x3a, 0x38, 0x33, 0x30, 0x34, 0x30, 0x37, 0x37, 0x32, 0x32,
|
||||||
|
0x34, 0x30, 0x32, 0x32, 0x32, 0x36, 0x31, 0x35, 0x30, 0x31, 0x7d
|
||||||
|
])
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export { getIgnoredUsersController };
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
import { generateRewardSeed } from "@/src/services/inventoryService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { logger } from "@/src/utils/logger";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
export const getNewRewardSeedController: RequestHandler = async (req, res) => {
|
export const getNewRewardSeedController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
|
||||||
const rewardSeed = generateRewardSeed();
|
const rewardSeed = generateRewardSeed();
|
||||||
|
logger.debug(`generated new reward seed: ${rewardSeed}`);
|
||||||
await Inventory.updateOne(
|
await Inventory.updateOne(
|
||||||
{
|
{
|
||||||
accountOwnerId: accountId
|
accountOwnerId: accountId
|
||||||
@ -17,3 +18,9 @@ export const getNewRewardSeedController: RequestHandler = async (req, res) => {
|
|||||||
);
|
);
|
||||||
res.json({ rewardSeed: rewardSeed });
|
res.json({ rewardSeed: rewardSeed });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function generateRewardSeed(): number {
|
||||||
|
const min = -Number.MAX_SAFE_INTEGER;
|
||||||
|
const max = Number.MAX_SAFE_INTEGER;
|
||||||
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
}
|
||||||
|
@ -2,24 +2,19 @@ import { RequestHandler } from "express";
|
|||||||
import { config } from "@/src/services/configService";
|
import { config } from "@/src/services/configService";
|
||||||
import allShipFeatures from "@/static/fixed_responses/allShipFeatures.json";
|
import allShipFeatures from "@/static/fixed_responses/allShipFeatures.json";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
|
import { getPersonalRooms } from "@/src/services/personalRoomsService";
|
||||||
|
import { getShip } from "@/src/services/shipService";
|
||||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
import { IGetShipResponse } from "@/src/types/shipTypes";
|
import { IGetShipResponse } from "@/src/types/shipTypes";
|
||||||
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
|
import { IPersonalRooms } from "@/src/types/personalRoomsTypes";
|
||||||
import { getLoadout } from "@/src/services/loadoutService";
|
import { getLoadout } from "@/src/services/loadoutService";
|
||||||
|
|
||||||
export const getShipController: RequestHandler = async (req, res) => {
|
export const getShipController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const personalRoomsDb = await getPersonalRooms(accountId);
|
const personalRoomsDb = await getPersonalRooms(accountId);
|
||||||
|
const personalRooms = personalRoomsDb.toJSON<IPersonalRooms>();
|
||||||
// Setup gardening if it's missing. Maybe should be done as part of some quest completion in the future.
|
|
||||||
if (personalRoomsDb.Apartment.Gardening.Planters.length == 0) {
|
|
||||||
personalRoomsDb.Apartment.Gardening = createGarden();
|
|
||||||
await personalRoomsDb.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
const personalRooms = personalRoomsDb.toJSON<IPersonalRoomsClient>();
|
|
||||||
const loadout = await getLoadout(accountId);
|
const loadout = await getLoadout(accountId);
|
||||||
|
const ship = await getShip(personalRoomsDb.activeShipId, "ShipAttachments SkinFlavourItem");
|
||||||
|
|
||||||
const getShipResponse: IGetShipResponse = {
|
const getShipResponse: IGetShipResponse = {
|
||||||
ShipOwnerId: accountId,
|
ShipOwnerId: accountId,
|
||||||
@ -29,12 +24,9 @@ export const getShipController: RequestHandler = async (req, res) => {
|
|||||||
ShipId: toOid(personalRoomsDb.activeShipId),
|
ShipId: toOid(personalRoomsDb.activeShipId),
|
||||||
ShipInterior: {
|
ShipInterior: {
|
||||||
Colors: personalRooms.ShipInteriorColors,
|
Colors: personalRooms.ShipInteriorColors,
|
||||||
ShipAttachments: { HOOD_ORNAMENT: "" },
|
ShipAttachments: ship.ShipAttachments,
|
||||||
SkinFlavourItem: ""
|
SkinFlavourItem: ship.SkinFlavourItem
|
||||||
},
|
}
|
||||||
FavouriteLoadoutId: personalRooms.Ship.FavouriteLoadoutId
|
|
||||||
? toOid(personalRooms.Ship.FavouriteLoadoutId)
|
|
||||||
: undefined
|
|
||||||
},
|
},
|
||||||
Apartment: personalRooms.Apartment,
|
Apartment: personalRooms.Apartment,
|
||||||
TailorShop: personalRooms.TailorShop
|
TailorShop: personalRooms.TailorShop
|
||||||
|
@ -1,29 +1,14 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { applyStandingToVendorManifest, getVendorManifestByTypeName } from "@/src/services/serversideVendorsService";
|
import { 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) => {
|
export const getVendorInfoController: RequestHandler = (req, res) => {
|
||||||
let manifest = getVendorManifestByTypeName(req.query.vendor as string);
|
if (typeof req.query.vendor == "string") {
|
||||||
if (!manifest) {
|
const manifest = getVendorManifestByTypeName(req.query.vendor);
|
||||||
throw new Error(`Unknown vendor: ${req.query.vendor as string}`);
|
if (!manifest) {
|
||||||
}
|
throw new Error(`Unknown vendor: ${req.query.vendor}`);
|
||||||
|
|
||||||
// For testing purposes, authenticating with this endpoint is optional here, but would be required on live.
|
|
||||||
if (req.query.accountId) {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const inventory = await getInventory(accountId);
|
|
||||||
manifest = applyStandingToVendorManifest(inventory, manifest);
|
|
||||||
if (config.dev?.keepVendorsExpired) {
|
|
||||||
manifest = {
|
|
||||||
VendorInfo: {
|
|
||||||
...manifest.VendorInfo,
|
|
||||||
Expiry: { $date: { $numberLong: "0" } }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
res.json(manifest);
|
||||||
|
} else {
|
||||||
|
res.status(400).end();
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json(manifest);
|
|
||||||
};
|
};
|
||||||
|
@ -1,125 +0,0 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|
||||||
import { Account } from "@/src/models/loginModel";
|
|
||||||
import { areFriends } from "@/src/services/friendService";
|
|
||||||
import { createMessage } from "@/src/services/inboxService";
|
|
||||||
import {
|
|
||||||
combineInventoryChanges,
|
|
||||||
getEffectiveAvatarImageType,
|
|
||||||
getInventory,
|
|
||||||
updateCurrency
|
|
||||||
} from "@/src/services/inventoryService";
|
|
||||||
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
|
||||||
import { handleDailyDealPurchase, handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
|
||||||
import { IOid } from "@/src/types/commonTypes";
|
|
||||||
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 (!checkPurchaseParams(data.PurchaseParams)) {
|
|
||||||
throw new Error(`unexpected purchase params in gifting request: ${String(req.body)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const account = await Account.findOne(
|
|
||||||
data.RecipientId ? { _id: data.RecipientId.$oid } : { DisplayName: data.Recipient }
|
|
||||||
);
|
|
||||||
if (!account) {
|
|
||||||
res.status(400).send("9").end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const inventory = await getInventory(account._id.toString(), "Suits Settings");
|
|
||||||
|
|
||||||
// Cannot gift items to players that have not completed the tutorial.
|
|
||||||
if (inventory.Suits.length == 0) {
|
|
||||||
res.status(400).send("14").end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cannot gift to players who have gifting disabled.
|
|
||||||
const senderAccount = await getAccountForRequest(req);
|
|
||||||
if (
|
|
||||||
inventory.Settings?.GiftMode == "GIFT_MODE_NONE" ||
|
|
||||||
(inventory.Settings?.GiftMode == "GIFT_MODE_FRIENDS" && !(await areFriends(account._id, senderAccount._id)))
|
|
||||||
) {
|
|
||||||
res.status(400).send("17").end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Cannot gift items with mastery requirement to players who are too low level. (Code 2)
|
|
||||||
// TODO: Cannot gift archwing items to players that have not completed the archwing quest. (Code 7)
|
|
||||||
// TODO: Cannot gift necramechs to players that have not completed heart of deimos. (Code 20)
|
|
||||||
|
|
||||||
const senderInventory = await getInventory(senderAccount._id.toString());
|
|
||||||
|
|
||||||
if (senderInventory.GiftsRemaining == 0) {
|
|
||||||
res.status(400).send("10").end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
senderInventory.GiftsRemaining -= 1;
|
|
||||||
|
|
||||||
const response: IPurchaseResponse = {
|
|
||||||
InventoryChanges: {}
|
|
||||||
};
|
|
||||||
if (data.PurchaseParams.Source == PurchaseSource.DailyDeal) {
|
|
||||||
await handleDailyDealPurchase(senderInventory, data.PurchaseParams, response);
|
|
||||||
} else {
|
|
||||||
updateCurrency(senderInventory, data.PurchaseParams.ExpectedPrice, true, response.InventoryChanges);
|
|
||||||
}
|
|
||||||
if (data.PurchaseParams.StoreItem in ExportBundles) {
|
|
||||||
const bundle = ExportBundles[data.PurchaseParams.StoreItem];
|
|
||||||
if (bundle.giftingBonus) {
|
|
||||||
combineInventoryChanges(
|
|
||||||
response.InventoryChanges,
|
|
||||||
(await handleStoreItemAcquisition(bundle.giftingBonus, senderInventory)).InventoryChanges
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await senderInventory.save();
|
|
||||||
|
|
||||||
const senderName = getSuffixedName(senderAccount);
|
|
||||||
await createMessage(account._id, [
|
|
||||||
{
|
|
||||||
sndr: senderName,
|
|
||||||
msg: data.Message || "/Lotus/Language/Menu/GiftReceivedBody_NoCustomMessage",
|
|
||||||
arg: [
|
|
||||||
{
|
|
||||||
Key: "GIFTER_NAME",
|
|
||||||
Tag: senderName
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "GIFT_QUANTITY",
|
|
||||||
Tag: data.PurchaseParams.Quantity
|
|
||||||
}
|
|
||||||
],
|
|
||||||
sub: "/Lotus/Language/Menu/GiftReceivedSubject",
|
|
||||||
icon: ExportFlavour[getEffectiveAvatarImageType(senderInventory)].icon,
|
|
||||||
gifts: [
|
|
||||||
{
|
|
||||||
GiftType: data.PurchaseParams.StoreItem
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
res.json(response);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IGiftingRequest {
|
|
||||||
PurchaseParams: IPurchaseParams;
|
|
||||||
Message?: string;
|
|
||||||
Recipient?: string;
|
|
||||||
RecipientId?: IOid;
|
|
||||||
buildLabel: string;
|
|
||||||
}
|
|
@ -2,25 +2,36 @@ import { RequestHandler } from "express";
|
|||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
||||||
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { WeaponTypeInternal } from "@/src/services/itemDataService";
|
||||||
import { ArtifactPolarity, EquipmentFeatures, IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { ArtifactPolarity, EquipmentFeatures, IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
import { ExportRecipes } from "warframe-public-export-plus";
|
import { ExportRecipes } from "warframe-public-export-plus";
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
|
||||||
|
const modularWeaponCategory: (WeaponTypeInternal | "Hoverboards")[] = [
|
||||||
|
"LongGuns",
|
||||||
|
"Pistols",
|
||||||
|
"Melee",
|
||||||
|
"OperatorAmps",
|
||||||
|
"Hoverboards"
|
||||||
|
];
|
||||||
|
|
||||||
interface IGildWeaponRequest {
|
interface IGildWeaponRequest {
|
||||||
ItemName: string;
|
ItemName: string;
|
||||||
Recipe: string; // e.g. /Lotus/Weapons/SolarisUnited/LotusGildKitgunBlueprint
|
Recipe: string; // e.g. /Lotus/Weapons/SolarisUnited/LotusGildKitgunBlueprint
|
||||||
PolarizeSlot?: number;
|
PolarizeSlot?: number;
|
||||||
PolarizeValue?: ArtifactPolarity;
|
PolarizeValue?: ArtifactPolarity;
|
||||||
ItemId: string;
|
ItemId: string;
|
||||||
Category: TEquipmentKey;
|
Category: WeaponTypeInternal | "Hoverboards";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const gildWeaponController: RequestHandler = async (req, res) => {
|
export const gildWeaponController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const data = getJSONfromString<IGildWeaponRequest>(String(req.body));
|
const data = getJSONfromString<IGildWeaponRequest>(String(req.body));
|
||||||
data.ItemId = String(req.query.ItemId);
|
data.ItemId = String(req.query.ItemId);
|
||||||
data.Category = req.query.Category as TEquipmentKey;
|
if (!modularWeaponCategory.includes(req.query.Category as WeaponTypeInternal | "Hoverboards")) {
|
||||||
|
throw new Error(`Unknown modular weapon Category: ${String(req.query.Category)}`);
|
||||||
|
}
|
||||||
|
data.Category = req.query.Category as WeaponTypeInternal | "Hoverboards";
|
||||||
|
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
const weaponIndex = inventory[data.Category].findIndex(x => String(x._id) === data.ItemId);
|
const weaponIndex = inventory[data.Category].findIndex(x => String(x._id) === data.ItemId);
|
||||||
@ -31,10 +42,8 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
|
|||||||
const weapon = inventory[data.Category][weaponIndex];
|
const weapon = inventory[data.Category][weaponIndex];
|
||||||
weapon.Features ??= 0;
|
weapon.Features ??= 0;
|
||||||
weapon.Features |= EquipmentFeatures.GILDED;
|
weapon.Features |= EquipmentFeatures.GILDED;
|
||||||
if (data.Recipe != "webui") {
|
weapon.ItemName = data.ItemName;
|
||||||
weapon.ItemName = data.ItemName;
|
weapon.XP = 0;
|
||||||
weapon.XP = 0;
|
|
||||||
}
|
|
||||||
if (data.Category != "OperatorAmps" && data.PolarizeSlot && data.PolarizeValue) {
|
if (data.Category != "OperatorAmps" && data.PolarizeSlot && data.PolarizeValue) {
|
||||||
weapon.Polarity = [
|
weapon.Polarity = [
|
||||||
{
|
{
|
||||||
@ -47,24 +56,21 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
|
|||||||
const inventoryChanges: IInventoryChanges = {};
|
const inventoryChanges: IInventoryChanges = {};
|
||||||
inventoryChanges[data.Category] = [weapon.toJSON<IEquipmentClient>()];
|
inventoryChanges[data.Category] = [weapon.toJSON<IEquipmentClient>()];
|
||||||
|
|
||||||
|
const recipe = ExportRecipes[data.Recipe];
|
||||||
|
inventoryChanges.MiscItems = recipe.secretIngredients!.map(ingredient => ({
|
||||||
|
ItemType: ingredient.ItemType,
|
||||||
|
ItemCount: ingredient.ItemCount * -1
|
||||||
|
}));
|
||||||
|
addMiscItems(inventory, inventoryChanges.MiscItems);
|
||||||
|
|
||||||
const affiliationMods = [];
|
const affiliationMods = [];
|
||||||
|
if (recipe.syndicateStandingChange) {
|
||||||
if (data.Recipe != "webui") {
|
const affiliation = inventory.Affiliations.find(x => x.Tag == recipe.syndicateStandingChange!.tag)!;
|
||||||
const recipe = ExportRecipes[data.Recipe];
|
affiliation.Standing += recipe.syndicateStandingChange.value;
|
||||||
inventoryChanges.MiscItems = recipe.secretIngredients!.map(ingredient => ({
|
affiliationMods.push({
|
||||||
ItemType: ingredient.ItemType,
|
Tag: recipe.syndicateStandingChange.tag,
|
||||||
ItemCount: ingredient.ItemCount * -1
|
Standing: recipe.syndicateStandingChange.value
|
||||||
}));
|
});
|
||||||
addMiscItems(inventory, inventoryChanges.MiscItems);
|
|
||||||
|
|
||||||
if (recipe.syndicateStandingChange) {
|
|
||||||
const affiliation = inventory.Affiliations.find(x => x.Tag == recipe.syndicateStandingChange!.tag)!;
|
|
||||||
affiliation.Standing += recipe.syndicateStandingChange.value;
|
|
||||||
affiliationMods.push({
|
|
||||||
Tag: recipe.syndicateStandingChange.tag,
|
|
||||||
Standing: recipe.syndicateStandingChange.value
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
|
@ -2,8 +2,8 @@ import { RequestHandler } from "express";
|
|||||||
import { parseString } from "@/src/helpers/general";
|
import { parseString } from "@/src/helpers/general";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { IGroup } from "@/src/types/loginTypes";
|
||||||
import { giveKeyChainItem } from "@/src/services/questService";
|
import { giveKeyChainItem } from "@/src/services/questService";
|
||||||
import { IKeyChainRequest } from "@/src/types/requestTypes";
|
|
||||||
|
|
||||||
export const giveKeyChainTriggeredItemsController: RequestHandler = async (req, res) => {
|
export const giveKeyChainTriggeredItemsController: RequestHandler = async (req, res) => {
|
||||||
const accountId = parseString(req.query.accountId);
|
const accountId = parseString(req.query.accountId);
|
||||||
@ -15,3 +15,9 @@ export const giveKeyChainTriggeredItemsController: RequestHandler = async (req,
|
|||||||
|
|
||||||
res.send(inventoryChanges);
|
res.send(inventoryChanges);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface IKeyChainRequest {
|
||||||
|
KeyChain: string;
|
||||||
|
ChainStage: number;
|
||||||
|
Groups?: IGroup[];
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
import { IKeyChainRequest } from "@/src/controllers/api/giveKeyChainTriggeredItemsController";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { giveKeyChainMessage } from "@/src/services/questService";
|
import { giveKeyChainMessage } from "@/src/services/questService";
|
||||||
import { IKeyChainRequest } from "@/src/types/requestTypes";
|
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
export const giveKeyChainTriggeredMessageController: RequestHandler = async (req, res) => {
|
export const giveKeyChainTriggeredMessageController: RequestHandler = async (req, res) => {
|
||||||
|
@ -16,15 +16,15 @@ export const giveQuestKeyRewardController: RequestHandler = async (req, res) =>
|
|||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
const inventoryChanges = await addItem(inventory, reward.ItemType, reward.Amount);
|
const inventoryChanges = await addItem(inventory, reward.ItemType, reward.Amount);
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json(inventoryChanges);
|
res.json(inventoryChanges.InventoryChanges);
|
||||||
//TODO: consider whishlist changes
|
//TODO: consider whishlist changes
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IQuestKeyRewardRequest {
|
export interface IQuestKeyRewardRequest {
|
||||||
reward: IQuestKeyReward;
|
reward: IQuestKeyReward;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IQuestKeyReward {
|
export interface IQuestKeyReward {
|
||||||
RewardType: string;
|
RewardType: string;
|
||||||
CouponType: string;
|
CouponType: string;
|
||||||
Icon: string;
|
Icon: string;
|
@ -1,20 +0,0 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|
||||||
import { addLoreFragmentScans, addShipDecorations, getInventory } from "@/src/services/inventoryService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { ILoreFragmentScan, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
|
|
||||||
export const giveShipDecoAndLoreFragmentController: RequestHandler = async (req, res) => {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const inventory = await getInventory(accountId, "LoreFragmentScans ShipDecorations");
|
|
||||||
const data = getJSONfromString<IGiveShipDecoAndLoreFragmentRequest>(String(req.body));
|
|
||||||
addLoreFragmentScans(inventory, data.LoreFragmentScans);
|
|
||||||
addShipDecorations(inventory, data.ShipDecorations);
|
|
||||||
await inventory.save();
|
|
||||||
res.end();
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IGiveShipDecoAndLoreFragmentRequest {
|
|
||||||
LoreFragmentScans: ILoreFragmentScan[];
|
|
||||||
ShipDecorations: ITypeCount[];
|
|
||||||
}
|
|
@ -1,8 +1,20 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { addStartingGear, getInventory } from "@/src/services/inventoryService";
|
import { InventoryDocumentProps } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
|
import {
|
||||||
|
addEquipment,
|
||||||
|
addItem,
|
||||||
|
addPowerSuit,
|
||||||
|
combineInventoryChanges,
|
||||||
|
getInventory,
|
||||||
|
updateSlots
|
||||||
|
} from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { TPartialStartingGear } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IInventoryClient, IInventoryDatabase, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
import { HydratedDocument } from "mongoose";
|
||||||
|
|
||||||
|
type TPartialStartingGear = Pick<IInventoryClient, "LongGuns" | "Suits" | "Pistols" | "Melee">;
|
||||||
|
|
||||||
export const giveStartingGearController: RequestHandler = async (req, res) => {
|
export const giveStartingGearController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
@ -14,3 +26,72 @@ export const giveStartingGearController: RequestHandler = async (req, res) => {
|
|||||||
|
|
||||||
res.send(inventoryChanges);
|
res.send(inventoryChanges);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//TODO: RawUpgrades might need to return a LastAdded
|
||||||
|
const awakeningRewards = [
|
||||||
|
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem1",
|
||||||
|
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem2",
|
||||||
|
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem3",
|
||||||
|
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem4",
|
||||||
|
"/Lotus/Types/Restoratives/LisetAutoHack",
|
||||||
|
"/Lotus/Upgrades/Mods/Warframe/AvatarShieldMaxMod"
|
||||||
|
];
|
||||||
|
|
||||||
|
export const addStartingGear = async (
|
||||||
|
inventory: HydratedDocument<IInventoryDatabase, InventoryDocumentProps>,
|
||||||
|
startingGear: TPartialStartingGear | undefined = undefined
|
||||||
|
): Promise<IInventoryChanges> => {
|
||||||
|
const { LongGuns, Pistols, Suits, Melee } = startingGear || {
|
||||||
|
LongGuns: [{ ItemType: "/Lotus/Weapons/Tenno/Rifle/Rifle" }],
|
||||||
|
Pistols: [{ ItemType: "/Lotus/Weapons/Tenno/Pistol/Pistol" }],
|
||||||
|
Suits: [{ ItemType: "/Lotus/Powersuits/Excalibur/Excalibur" }],
|
||||||
|
Melee: [{ ItemType: "/Lotus/Weapons/Tenno/Melee/LongSword/LongSword" }]
|
||||||
|
};
|
||||||
|
|
||||||
|
//TODO: properly merge weapon bin changes it is currently static here
|
||||||
|
const inventoryChanges: IInventoryChanges = {};
|
||||||
|
addEquipment(inventory, "LongGuns", LongGuns[0].ItemType, undefined, inventoryChanges);
|
||||||
|
addEquipment(inventory, "Pistols", Pistols[0].ItemType, undefined, inventoryChanges);
|
||||||
|
addEquipment(inventory, "Melee", Melee[0].ItemType, undefined, inventoryChanges);
|
||||||
|
addPowerSuit(inventory, Suits[0].ItemType, inventoryChanges);
|
||||||
|
addEquipment(
|
||||||
|
inventory,
|
||||||
|
"DataKnives",
|
||||||
|
"/Lotus/Weapons/Tenno/HackingDevices/TnHackingDevice/TnHackingDeviceWeapon",
|
||||||
|
undefined,
|
||||||
|
inventoryChanges,
|
||||||
|
{ XP: 450_000 }
|
||||||
|
);
|
||||||
|
addEquipment(
|
||||||
|
inventory,
|
||||||
|
"Scoops",
|
||||||
|
"/Lotus/Weapons/Tenno/Speedball/SpeedballWeaponTest",
|
||||||
|
undefined,
|
||||||
|
inventoryChanges
|
||||||
|
);
|
||||||
|
|
||||||
|
updateSlots(inventory, InventorySlot.SUITS, 0, 1);
|
||||||
|
updateSlots(inventory, InventorySlot.WEAPONS, 0, 3);
|
||||||
|
inventoryChanges.SuitBin = { count: 1, platinum: 0, Slots: -1 };
|
||||||
|
inventoryChanges.WeaponBin = { count: 3, platinum: 0, Slots: -3 };
|
||||||
|
|
||||||
|
await addItem(inventory, "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain");
|
||||||
|
inventory.ActiveQuest = "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain";
|
||||||
|
|
||||||
|
inventory.PremiumCredits = 50;
|
||||||
|
inventory.PremiumCreditsFree = 50;
|
||||||
|
inventoryChanges.PremiumCredits = 50;
|
||||||
|
inventoryChanges.PremiumCreditsFree = 50;
|
||||||
|
inventory.RegularCredits = 3000;
|
||||||
|
inventoryChanges.RegularCredits = 3000;
|
||||||
|
|
||||||
|
for (const item of awakeningRewards) {
|
||||||
|
const inventoryDelta = await addItem(inventory, item);
|
||||||
|
combineInventoryChanges(inventoryChanges, inventoryDelta.InventoryChanges);
|
||||||
|
}
|
||||||
|
|
||||||
|
inventory.PlayedParkourTutorial = true;
|
||||||
|
inventory.ReceivedStartingGear = true;
|
||||||
|
|
||||||
|
return inventoryChanges;
|
||||||
|
};
|
||||||
|
@ -1,47 +1,37 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import {
|
import {
|
||||||
addGuildMemberMiscItemContribution,
|
|
||||||
getGuildForRequestEx,
|
getGuildForRequestEx,
|
||||||
getGuildVault,
|
getGuildVault,
|
||||||
hasAccessToDojo,
|
hasAccessToDojo,
|
||||||
hasGuildPermission,
|
hasGuildPermission,
|
||||||
processCompletedGuildTechProject,
|
|
||||||
processFundedGuildTechProject,
|
|
||||||
processGuildTechProjectContributionsUpdate,
|
|
||||||
removePigmentsFromGuildMembers,
|
removePigmentsFromGuildMembers,
|
||||||
scaleRequiredCount,
|
scaleRequiredCount
|
||||||
setGuildTechLogState
|
|
||||||
} from "@/src/services/guildService";
|
} from "@/src/services/guildService";
|
||||||
import { ExportDojoRecipes, ExportRailjackWeapons } from "warframe-public-export-plus";
|
import { ExportDojoRecipes, IDojoResearch } from "warframe-public-export-plus";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import {
|
import {
|
||||||
addCrewShipWeaponSkin,
|
|
||||||
addEquipment,
|
|
||||||
addItem,
|
addItem,
|
||||||
addMiscItems,
|
addMiscItems,
|
||||||
addRecipes,
|
addRecipes,
|
||||||
combineInventoryChanges,
|
combineInventoryChanges,
|
||||||
getInventory,
|
getInventory,
|
||||||
occupySlot,
|
|
||||||
updateCurrency
|
updateCurrency
|
||||||
} from "@/src/services/inventoryService";
|
} from "@/src/services/inventoryService";
|
||||||
import { IMiscItem, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { config } from "@/src/services/configService";
|
import { config } from "@/src/services/configService";
|
||||||
import { GuildPermission, ITechProjectClient } from "@/src/types/guildTypes";
|
import { GuildPermission, ITechProjectClient, ITechProjectDatabase } from "@/src/types/guildTypes";
|
||||||
import { GuildMember } from "@/src/models/guildModel";
|
import { TGuildDatabaseDocument } from "@/src/models/guildModel";
|
||||||
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
|
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||||
import { logger } from "@/src/utils/logger";
|
|
||||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
|
||||||
|
|
||||||
export const guildTechController: RequestHandler = async (req, res) => {
|
export const guildTechController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
const data = JSON.parse(String(req.body)) as TGuildTechRequest;
|
const data = JSON.parse(String(req.body)) as TGuildTechRequest;
|
||||||
if (data.Action == "Sync") {
|
if (data.Action == "Sync") {
|
||||||
let needSave = false;
|
let needSave = false;
|
||||||
const techProjects: ITechProjectClient[] = [];
|
const techProjects: ITechProjectClient[] = [];
|
||||||
const guild = await getGuildForRequestEx(req, inventory);
|
|
||||||
if (guild.TechProjects) {
|
if (guild.TechProjects) {
|
||||||
for (const project of guild.TechProjects) {
|
for (const project of guild.TechProjects) {
|
||||||
const techProject: ITechProjectClient = {
|
const techProject: ITechProjectClient = {
|
||||||
@ -52,12 +42,8 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
|||||||
};
|
};
|
||||||
if (project.CompletionDate) {
|
if (project.CompletionDate) {
|
||||||
techProject.CompletionDate = toMongoDate(project.CompletionDate);
|
techProject.CompletionDate = toMongoDate(project.CompletionDate);
|
||||||
if (
|
if (Date.now() >= project.CompletionDate.getTime()) {
|
||||||
Date.now() >= project.CompletionDate.getTime() &&
|
needSave ||= setTechLogState(guild, project.ItemType, 4, project.CompletionDate);
|
||||||
setGuildTechLogState(guild, project.ItemType, 4, project.CompletionDate)
|
|
||||||
) {
|
|
||||||
processCompletedGuildTechProject(guild, project.ItemType);
|
|
||||||
needSave = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
techProjects.push(techProject);
|
techProjects.push(techProject);
|
||||||
@ -68,224 +54,134 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
res.json({ TechProjects: techProjects });
|
res.json({ TechProjects: techProjects });
|
||||||
} else if (data.Action == "Start") {
|
} else if (data.Action == "Start") {
|
||||||
if (data.Mode == "Guild") {
|
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
|
||||||
const guild = await getGuildForRequestEx(req, inventory);
|
res.status(400).send("-1").end();
|
||||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
|
return;
|
||||||
res.status(400).send("-1").end();
|
}
|
||||||
return;
|
const recipe = ExportDojoRecipes.research[data.RecipeType];
|
||||||
}
|
guild.TechProjects ??= [];
|
||||||
const recipe = ExportDojoRecipes.research[data.RecipeType];
|
if (!guild.TechProjects.find(x => x.ItemType == data.RecipeType)) {
|
||||||
guild.TechProjects ??= [];
|
|
||||||
if (!guild.TechProjects.find(x => x.ItemType == data.RecipeType)) {
|
|
||||||
const techProject =
|
|
||||||
guild.TechProjects[
|
|
||||||
guild.TechProjects.push({
|
|
||||||
ItemType: data.RecipeType,
|
|
||||||
ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, recipe.price),
|
|
||||||
ReqItems: recipe.ingredients.map(x => ({
|
|
||||||
ItemType: x.ItemType,
|
|
||||||
ItemCount: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, x.ItemCount)
|
|
||||||
})),
|
|
||||||
State: 0
|
|
||||||
}) - 1
|
|
||||||
];
|
|
||||||
setGuildTechLogState(guild, techProject.ItemType, 5);
|
|
||||||
if (config.noDojoResearchCosts) {
|
|
||||||
processFundedGuildTechProject(guild, techProject, recipe);
|
|
||||||
} else {
|
|
||||||
if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
|
|
||||||
guild.ActiveDojoColorResearch = data.RecipeType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await guild.save();
|
|
||||||
res.end();
|
|
||||||
} else {
|
|
||||||
const recipe = ExportDojoRecipes.research[data.RecipeType];
|
|
||||||
if (data.TechProductCategory) {
|
|
||||||
if (
|
|
||||||
data.TechProductCategory != "CrewShipWeapons" &&
|
|
||||||
data.TechProductCategory != "CrewShipWeaponSkins"
|
|
||||||
) {
|
|
||||||
throw new Error(`unexpected TechProductCategory: ${data.TechProductCategory}`);
|
|
||||||
}
|
|
||||||
if (!inventory[getSalvageCategory(data.TechProductCategory)].id(data.CategoryItemId!)) {
|
|
||||||
throw new Error(
|
|
||||||
`no item with id ${data.CategoryItemId} in ${getSalvageCategory(data.TechProductCategory)} array`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const techProject =
|
const techProject =
|
||||||
inventory.PersonalTechProjects[
|
guild.TechProjects[
|
||||||
inventory.PersonalTechProjects.push({
|
guild.TechProjects.push({
|
||||||
State: 0,
|
|
||||||
ReqCredits: recipe.price,
|
|
||||||
ItemType: data.RecipeType,
|
ItemType: data.RecipeType,
|
||||||
ProductCategory: data.TechProductCategory,
|
ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(recipe.price),
|
||||||
CategoryItemId: data.CategoryItemId,
|
ReqItems: recipe.ingredients.map(x => ({
|
||||||
ReqItems: recipe.ingredients
|
ItemType: x.ItemType,
|
||||||
|
ItemCount: config.noDojoResearchCosts ? 0 : scaleRequiredCount(x.ItemCount)
|
||||||
|
})),
|
||||||
|
State: 0
|
||||||
}) - 1
|
}) - 1
|
||||||
];
|
];
|
||||||
await inventory.save();
|
setTechLogState(guild, techProject.ItemType, 5);
|
||||||
res.json({
|
if (config.noDojoResearchCosts) {
|
||||||
isPersonal: true,
|
processFundedProject(guild, techProject, recipe);
|
||||||
action: "Start",
|
} else {
|
||||||
personalTech: techProject.toJSON()
|
if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
|
||||||
});
|
guild.ActiveDojoColorResearch = data.RecipeType;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
await guild.save();
|
||||||
|
res.end();
|
||||||
} else if (data.Action == "Contribute") {
|
} else if (data.Action == "Contribute") {
|
||||||
if ((req.query.guildId as string) == "000000000000000000000000") {
|
if (!hasAccessToDojo(inventory)) {
|
||||||
const techProject = inventory.PersonalTechProjects.id(data.ResearchId)!;
|
res.status(400).send("-1").end();
|
||||||
|
return;
|
||||||
techProject.ReqCredits -= data.RegularCredits;
|
|
||||||
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, data.RegularCredits, false);
|
|
||||||
|
|
||||||
const miscItemChanges = [];
|
|
||||||
for (const miscItem of data.MiscItems) {
|
|
||||||
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
|
|
||||||
if (reqItem) {
|
|
||||||
if (miscItem.ItemCount > reqItem.ItemCount) {
|
|
||||||
miscItem.ItemCount = reqItem.ItemCount;
|
|
||||||
}
|
|
||||||
reqItem.ItemCount -= miscItem.ItemCount;
|
|
||||||
miscItemChanges.push({
|
|
||||||
ItemType: miscItem.ItemType,
|
|
||||||
ItemCount: miscItem.ItemCount * -1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addMiscItems(inventory, miscItemChanges);
|
|
||||||
inventoryChanges.MiscItems = miscItemChanges;
|
|
||||||
|
|
||||||
techProject.HasContributions = true;
|
|
||||||
|
|
||||||
if (techProject.ReqCredits == 0 && !techProject.ReqItems.find(x => x.ItemCount > 0)) {
|
|
||||||
techProject.State = 1;
|
|
||||||
const recipe = ExportDojoRecipes.research[techProject.ItemType];
|
|
||||||
techProject.CompletionDate = new Date(Date.now() + recipe.time * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
await inventory.save();
|
|
||||||
res.json({
|
|
||||||
InventoryChanges: inventoryChanges,
|
|
||||||
PersonalResearch: { $oid: data.ResearchId },
|
|
||||||
PersonalResearchDate: techProject.CompletionDate ? toMongoDate(techProject.CompletionDate) : undefined
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (!hasAccessToDojo(inventory)) {
|
|
||||||
res.status(400).send("-1").end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const guild = await getGuildForRequestEx(req, inventory);
|
|
||||||
const guildMember = (await GuildMember.findOne(
|
|
||||||
{ accountId, guildId: guild._id },
|
|
||||||
"RegularCreditsContributed MiscItemsContributed"
|
|
||||||
))!;
|
|
||||||
|
|
||||||
const techProject = guild.TechProjects!.find(x => x.ItemType == data.RecipeType)!;
|
|
||||||
|
|
||||||
if (data.VaultCredits) {
|
|
||||||
if (data.VaultCredits > techProject.ReqCredits) {
|
|
||||||
data.VaultCredits = techProject.ReqCredits;
|
|
||||||
}
|
|
||||||
techProject.ReqCredits -= data.VaultCredits;
|
|
||||||
guild.VaultRegularCredits! -= data.VaultCredits;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.RegularCredits > techProject.ReqCredits) {
|
|
||||||
data.RegularCredits = techProject.ReqCredits;
|
|
||||||
}
|
|
||||||
techProject.ReqCredits -= data.RegularCredits;
|
|
||||||
|
|
||||||
guildMember.RegularCreditsContributed ??= 0;
|
|
||||||
guildMember.RegularCreditsContributed += data.RegularCredits;
|
|
||||||
|
|
||||||
if (data.VaultMiscItems.length) {
|
|
||||||
for (const miscItem of data.VaultMiscItems) {
|
|
||||||
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
|
|
||||||
if (reqItem) {
|
|
||||||
if (miscItem.ItemCount > reqItem.ItemCount) {
|
|
||||||
miscItem.ItemCount = reqItem.ItemCount;
|
|
||||||
}
|
|
||||||
reqItem.ItemCount -= miscItem.ItemCount;
|
|
||||||
|
|
||||||
const vaultMiscItem = guild.VaultMiscItems!.find(x => x.ItemType == miscItem.ItemType)!;
|
|
||||||
vaultMiscItem.ItemCount -= miscItem.ItemCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const miscItemChanges = [];
|
|
||||||
for (const miscItem of data.MiscItems) {
|
|
||||||
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
|
|
||||||
if (reqItem) {
|
|
||||||
if (miscItem.ItemCount > reqItem.ItemCount) {
|
|
||||||
miscItem.ItemCount = reqItem.ItemCount;
|
|
||||||
}
|
|
||||||
reqItem.ItemCount -= miscItem.ItemCount;
|
|
||||||
miscItemChanges.push({
|
|
||||||
ItemType: miscItem.ItemType,
|
|
||||||
ItemCount: miscItem.ItemCount * -1
|
|
||||||
});
|
|
||||||
|
|
||||||
addGuildMemberMiscItemContribution(guildMember, miscItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addMiscItems(inventory, miscItemChanges);
|
|
||||||
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, data.RegularCredits, false);
|
|
||||||
inventoryChanges.MiscItems = miscItemChanges;
|
|
||||||
|
|
||||||
// Check if research is fully funded now.
|
|
||||||
await processGuildTechProjectContributionsUpdate(guild, techProject);
|
|
||||||
|
|
||||||
await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
|
|
||||||
res.json({
|
|
||||||
InventoryChanges: inventoryChanges,
|
|
||||||
Vault: getGuildVault(guild)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
const contributions = data;
|
||||||
|
const techProject = guild.TechProjects!.find(x => x.ItemType == contributions.RecipeType)!;
|
||||||
|
|
||||||
|
if (contributions.VaultCredits) {
|
||||||
|
if (contributions.VaultCredits > techProject.ReqCredits) {
|
||||||
|
contributions.VaultCredits = techProject.ReqCredits;
|
||||||
|
}
|
||||||
|
techProject.ReqCredits -= contributions.VaultCredits;
|
||||||
|
guild.VaultRegularCredits! -= contributions.VaultCredits;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contributions.RegularCredits > techProject.ReqCredits) {
|
||||||
|
contributions.RegularCredits = techProject.ReqCredits;
|
||||||
|
}
|
||||||
|
techProject.ReqCredits -= contributions.RegularCredits;
|
||||||
|
|
||||||
|
if (contributions.VaultMiscItems.length) {
|
||||||
|
for (const miscItem of contributions.VaultMiscItems) {
|
||||||
|
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
|
||||||
|
if (reqItem) {
|
||||||
|
if (miscItem.ItemCount > reqItem.ItemCount) {
|
||||||
|
miscItem.ItemCount = reqItem.ItemCount;
|
||||||
|
}
|
||||||
|
reqItem.ItemCount -= miscItem.ItemCount;
|
||||||
|
|
||||||
|
const vaultMiscItem = guild.VaultMiscItems!.find(x => x.ItemType == miscItem.ItemType)!;
|
||||||
|
vaultMiscItem.ItemCount -= miscItem.ItemCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const miscItemChanges = [];
|
||||||
|
for (const miscItem of contributions.MiscItems) {
|
||||||
|
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
|
||||||
|
if (reqItem) {
|
||||||
|
if (miscItem.ItemCount > reqItem.ItemCount) {
|
||||||
|
miscItem.ItemCount = reqItem.ItemCount;
|
||||||
|
}
|
||||||
|
reqItem.ItemCount -= miscItem.ItemCount;
|
||||||
|
miscItemChanges.push({
|
||||||
|
ItemType: miscItem.ItemType,
|
||||||
|
ItemCount: miscItem.ItemCount * -1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addMiscItems(inventory, miscItemChanges);
|
||||||
|
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, contributions.RegularCredits, false);
|
||||||
|
inventoryChanges.MiscItems = miscItemChanges;
|
||||||
|
|
||||||
|
if (techProject.ReqCredits == 0 && !techProject.ReqItems.find(x => x.ItemCount > 0)) {
|
||||||
|
// This research is now fully funded.
|
||||||
|
const recipe = ExportDojoRecipes.research[data.RecipeType];
|
||||||
|
processFundedProject(guild, techProject, recipe);
|
||||||
|
if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
|
||||||
|
guild.ActiveDojoColorResearch = "";
|
||||||
|
await removePigmentsFromGuildMembers(guild._id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await guild.save();
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
InventoryChanges: inventoryChanges,
|
||||||
|
Vault: getGuildVault(guild)
|
||||||
|
});
|
||||||
} else if (data.Action.split(",")[0] == "Buy") {
|
} else if (data.Action.split(",")[0] == "Buy") {
|
||||||
const purchase = data as IGuildTechBuyRequest;
|
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
|
||||||
if (purchase.Mode == "Guild") {
|
res.status(400).send("-1").end();
|
||||||
const guild = await getGuildForRequestEx(req, inventory);
|
return;
|
||||||
if (
|
|
||||||
!hasAccessToDojo(inventory) ||
|
|
||||||
!(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))
|
|
||||||
) {
|
|
||||||
res.status(400).send("-1").end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const quantity = parseInt(data.Action.split(",")[1]);
|
|
||||||
const recipeChanges = [
|
|
||||||
{
|
|
||||||
ItemType: purchase.RecipeType,
|
|
||||||
ItemCount: quantity
|
|
||||||
}
|
|
||||||
];
|
|
||||||
addRecipes(inventory, recipeChanges);
|
|
||||||
const currencyChanges = updateCurrency(
|
|
||||||
inventory,
|
|
||||||
ExportDojoRecipes.research[purchase.RecipeType].replicatePrice,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
await inventory.save();
|
|
||||||
// Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`.
|
|
||||||
res.json({
|
|
||||||
inventoryChanges: {
|
|
||||||
...currencyChanges,
|
|
||||||
Recipes: recipeChanges
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const inventoryChanges = claimSalvagedComponent(inventory, purchase.CategoryItemId!);
|
|
||||||
await inventory.save();
|
|
||||||
res.json({
|
|
||||||
inventoryChanges: inventoryChanges
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
const purchase = data as IGuildTechBuyRequest;
|
||||||
|
const quantity = parseInt(data.Action.split(",")[1]);
|
||||||
|
const recipeChanges = [
|
||||||
|
{
|
||||||
|
ItemType: purchase.RecipeType,
|
||||||
|
ItemCount: quantity
|
||||||
|
}
|
||||||
|
];
|
||||||
|
addRecipes(inventory, recipeChanges);
|
||||||
|
const currencyChanges = updateCurrency(
|
||||||
|
inventory,
|
||||||
|
ExportDojoRecipes.research[purchase.RecipeType].replicatePrice,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
await inventory.save();
|
||||||
|
// Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`.
|
||||||
|
res.json({
|
||||||
|
inventoryChanges: {
|
||||||
|
...currencyChanges,
|
||||||
|
Recipes: recipeChanges
|
||||||
|
}
|
||||||
|
});
|
||||||
} else if (data.Action == "Fabricate") {
|
} else if (data.Action == "Fabricate") {
|
||||||
const guild = await getGuildForRequestEx(req, inventory);
|
|
||||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
|
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
|
||||||
res.status(400).send("-1").end();
|
res.status(400).send("-1").end();
|
||||||
return;
|
return;
|
||||||
@ -297,12 +193,11 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
|||||||
ItemCount: x.ItemCount * -1
|
ItemCount: x.ItemCount * -1
|
||||||
}));
|
}));
|
||||||
addMiscItems(inventory, inventoryChanges.MiscItems);
|
addMiscItems(inventory, inventoryChanges.MiscItems);
|
||||||
combineInventoryChanges(inventoryChanges, await addItem(inventory, recipe.resultType));
|
combineInventoryChanges(inventoryChanges, (await addItem(inventory, recipe.resultType)).InventoryChanges);
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
// Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`.
|
// Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`.
|
||||||
res.json({ inventoryChanges: inventoryChanges });
|
res.json({ inventoryChanges: inventoryChanges });
|
||||||
} else if (data.Action == "Pause") {
|
} else if (data.Action == "Pause") {
|
||||||
const guild = await getGuildForRequestEx(req, inventory);
|
|
||||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
|
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
|
||||||
res.status(400).send("-1").end();
|
res.status(400).send("-1").end();
|
||||||
return;
|
return;
|
||||||
@ -314,7 +209,6 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
|||||||
await removePigmentsFromGuildMembers(guild._id);
|
await removePigmentsFromGuildMembers(guild._id);
|
||||||
res.end();
|
res.end();
|
||||||
} else if (data.Action == "Unpause") {
|
} else if (data.Action == "Unpause") {
|
||||||
const guild = await getGuildForRequestEx(req, inventory);
|
|
||||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
|
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
|
||||||
res.status(400).send("-1").end();
|
res.status(400).send("-1").end();
|
||||||
return;
|
return;
|
||||||
@ -324,140 +218,71 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
|||||||
guild.ActiveDojoColorResearch = data.RecipeType;
|
guild.ActiveDojoColorResearch = data.RecipeType;
|
||||||
await guild.save();
|
await guild.save();
|
||||||
res.end();
|
res.end();
|
||||||
} else if (data.Action == "Cancel" && data.CategoryItemId) {
|
|
||||||
const personalTechProjectIndex = inventory.PersonalTechProjects.findIndex(x =>
|
|
||||||
x.CategoryItemId?.equals(data.CategoryItemId)
|
|
||||||
);
|
|
||||||
const personalTechProject = inventory.PersonalTechProjects[personalTechProjectIndex];
|
|
||||||
inventory.PersonalTechProjects.splice(personalTechProjectIndex, 1);
|
|
||||||
|
|
||||||
const meta = ExportDojoRecipes.research[personalTechProject.ItemType];
|
|
||||||
const contributedCredits = meta.price - personalTechProject.ReqCredits;
|
|
||||||
const inventoryChanges = updateCurrency(inventory, contributedCredits * -1, false);
|
|
||||||
inventoryChanges.MiscItems = [];
|
|
||||||
for (const ingredient of meta.ingredients) {
|
|
||||||
const reqItem = personalTechProject.ReqItems.find(x => x.ItemType == ingredient.ItemType);
|
|
||||||
if (reqItem) {
|
|
||||||
const contributedItems = ingredient.ItemCount - reqItem.ItemCount;
|
|
||||||
inventoryChanges.MiscItems.push({
|
|
||||||
ItemType: ingredient.ItemType,
|
|
||||||
ItemCount: contributedItems
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addMiscItems(inventory, inventoryChanges.MiscItems);
|
|
||||||
|
|
||||||
await inventory.save();
|
|
||||||
res.json({
|
|
||||||
action: "Cancel",
|
|
||||||
isPersonal: true,
|
|
||||||
inventoryChanges: inventoryChanges,
|
|
||||||
personalTech: {
|
|
||||||
ItemId: toOid(personalTechProject._id)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (data.Action == "Rush" && data.CategoryItemId) {
|
|
||||||
const inventoryChanges: IInventoryChanges = {
|
|
||||||
...updateCurrency(inventory, 20, true),
|
|
||||||
...claimSalvagedComponent(inventory, data.CategoryItemId)
|
|
||||||
};
|
|
||||||
await inventory.save();
|
|
||||||
res.json({
|
|
||||||
inventoryChanges: inventoryChanges
|
|
||||||
});
|
|
||||||
} else if (data.Action == "InstantFinish") {
|
|
||||||
if (data.TechProductCategory != "CrewShipWeapons" && data.TechProductCategory != "CrewShipWeaponSkins") {
|
|
||||||
throw new Error(`unexpected TechProductCategory: ${data.TechProductCategory}`);
|
|
||||||
}
|
|
||||||
const inventoryChanges = finishComponentRepair(inventory, data.TechProductCategory, data.CategoryItemId!);
|
|
||||||
inventoryChanges.MiscItems = [
|
|
||||||
{
|
|
||||||
ItemType: "/Lotus/Types/Items/MiscItems/InstantSalvageRepairItem",
|
|
||||||
ItemCount: -1
|
|
||||||
}
|
|
||||||
];
|
|
||||||
addMiscItems(inventory, inventoryChanges.MiscItems);
|
|
||||||
await inventory.save();
|
|
||||||
res.json({
|
|
||||||
inventoryChanges: inventoryChanges
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
throw new Error(`unknown guildTech action: ${data.Action}`);
|
||||||
throw new Error(`unhandled guildTech request`);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const processFundedProject = (
|
||||||
|
guild: TGuildDatabaseDocument,
|
||||||
|
techProject: ITechProjectDatabase,
|
||||||
|
recipe: IDojoResearch
|
||||||
|
): void => {
|
||||||
|
techProject.State = 1;
|
||||||
|
techProject.CompletionDate = new Date(Date.now() + (config.noDojoResearchTime ? 0 : recipe.time) * 1000);
|
||||||
|
if (recipe.guildXpValue) {
|
||||||
|
guild.XP += recipe.guildXpValue;
|
||||||
|
}
|
||||||
|
setTechLogState(guild, techProject.ItemType, config.noDojoResearchTime ? 4 : 3, techProject.CompletionDate);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setTechLogState = (
|
||||||
|
guild: TGuildDatabaseDocument,
|
||||||
|
type: string,
|
||||||
|
state: number,
|
||||||
|
dateTime: Date | undefined = undefined
|
||||||
|
): boolean => {
|
||||||
|
guild.TechChanges ??= [];
|
||||||
|
const entry = guild.TechChanges.find(x => x.details == type);
|
||||||
|
if (entry) {
|
||||||
|
if (entry.entryType == state) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
entry.dateTime = dateTime;
|
||||||
|
entry.entryType = state;
|
||||||
|
} else {
|
||||||
|
guild.TechChanges.push({
|
||||||
|
dateTime: dateTime,
|
||||||
|
entryType: state,
|
||||||
|
details: type
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
type TGuildTechRequest =
|
type TGuildTechRequest =
|
||||||
| { Action: "Sync" | "SomethingElseThatWeMightNotKnowAbout" }
|
| { Action: "Sync" | "SomethingElseThatWeMightNotKnowAbout" }
|
||||||
| IGuildTechBasicRequest
|
| IGuildTechBasicRequest
|
||||||
| IGuildTechContributeRequest;
|
| IGuildTechContributeRequest;
|
||||||
|
|
||||||
interface IGuildTechBasicRequest {
|
interface IGuildTechBasicRequest {
|
||||||
Action: "Start" | "Fabricate" | "Pause" | "Unpause" | "Cancel" | "Rush" | "InstantFinish";
|
Action: "Start" | "Fabricate" | "Pause" | "Unpause";
|
||||||
Mode: "Guild" | "Personal";
|
Mode: "Guild";
|
||||||
RecipeType: string;
|
RecipeType: string;
|
||||||
TechProductCategory?: string;
|
|
||||||
CategoryItemId?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IGuildTechBuyRequest extends Omit<IGuildTechBasicRequest, "Action"> {
|
interface IGuildTechBuyRequest {
|
||||||
Action: string;
|
Action: string;
|
||||||
|
Mode: "Guild";
|
||||||
|
RecipeType: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IGuildTechContributeRequest {
|
interface IGuildTechContributeRequest {
|
||||||
Action: "Contribute";
|
Action: "Contribute";
|
||||||
ResearchId: string;
|
ResearchId: "";
|
||||||
RecipeType: string;
|
RecipeType: string;
|
||||||
RegularCredits: number;
|
RegularCredits: number;
|
||||||
MiscItems: IMiscItem[];
|
MiscItems: IMiscItem[];
|
||||||
VaultCredits: number;
|
VaultCredits: number;
|
||||||
VaultMiscItems: IMiscItem[];
|
VaultMiscItems: IMiscItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSalvageCategory = (
|
|
||||||
category: "CrewShipWeapons" | "CrewShipWeaponSkins"
|
|
||||||
): "CrewShipSalvagedWeapons" | "CrewShipSalvagedWeaponSkins" => {
|
|
||||||
return category == "CrewShipWeapons" ? "CrewShipSalvagedWeapons" : "CrewShipSalvagedWeaponSkins";
|
|
||||||
};
|
|
||||||
|
|
||||||
const claimSalvagedComponent = (inventory: TInventoryDatabaseDocument, itemId: string): IInventoryChanges => {
|
|
||||||
// delete personal tech project
|
|
||||||
const personalTechProjectIndex = inventory.PersonalTechProjects.findIndex(x => x.CategoryItemId?.equals(itemId));
|
|
||||||
const personalTechProject = inventory.PersonalTechProjects[personalTechProjectIndex];
|
|
||||||
inventory.PersonalTechProjects.splice(personalTechProjectIndex, 1);
|
|
||||||
|
|
||||||
const category = personalTechProject.ProductCategory! as "CrewShipWeapons" | "CrewShipWeaponSkins";
|
|
||||||
return finishComponentRepair(inventory, category, itemId);
|
|
||||||
};
|
|
||||||
|
|
||||||
const finishComponentRepair = (
|
|
||||||
inventory: TInventoryDatabaseDocument,
|
|
||||||
category: "CrewShipWeapons" | "CrewShipWeaponSkins",
|
|
||||||
itemId: string
|
|
||||||
): IInventoryChanges => {
|
|
||||||
const salvageCategory = getSalvageCategory(category);
|
|
||||||
|
|
||||||
// find salved part & delete it
|
|
||||||
const salvageIndex = inventory[salvageCategory].findIndex(x => x._id.equals(itemId));
|
|
||||||
const salvageItem = inventory[salvageCategory][salvageIndex];
|
|
||||||
inventory[salvageCategory].splice(salvageIndex, 1);
|
|
||||||
|
|
||||||
// add final item
|
|
||||||
const inventoryChanges = {
|
|
||||||
...(category == "CrewShipWeaponSkins"
|
|
||||||
? addCrewShipWeaponSkin(inventory, salvageItem.ItemType, salvageItem.UpgradeFingerprint)
|
|
||||||
: addEquipment(inventory, category, salvageItem.ItemType, {
|
|
||||||
UpgradeType: ExportRailjackWeapons[salvageItem.ItemType].defaultUpgrades?.[0].ItemType,
|
|
||||||
UpgradeFingerprint: salvageItem.UpgradeFingerprint
|
|
||||||
})),
|
|
||||||
...occupySlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS, false)
|
|
||||||
};
|
|
||||||
|
|
||||||
inventoryChanges.RemovedIdItems = [
|
|
||||||
{
|
|
||||||
ItemId: { $oid: itemId }
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
return inventoryChanges;
|
|
||||||
};
|
|
||||||
|
@ -1,24 +1,17 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { createNewSession } from "@/src/managers/sessionManager";
|
import { createNewSession } from "@/src/managers/sessionManager";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { ISession } from "@/src/types/session";
|
import { ISession } from "@/src/types/session";
|
||||||
import { JSONParse } from "json-with-bigint";
|
|
||||||
import { toOid2, version_compare } from "@/src/helpers/inventoryHelpers";
|
|
||||||
|
|
||||||
const hostSessionController: RequestHandler = async (req, res) => {
|
const hostSessionController: RequestHandler = async (req, res) => {
|
||||||
const account = await getAccountForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const hostSessionRequest = JSONParse(String(req.body)) as ISession;
|
const hostSessionRequest = JSON.parse(req.body as string) as ISession;
|
||||||
logger.debug("HostSession Request", { hostSessionRequest });
|
logger.debug("HostSession Request", { hostSessionRequest });
|
||||||
const session = createNewSession(hostSessionRequest, account._id);
|
const session = createNewSession(hostSessionRequest, accountId);
|
||||||
logger.debug(`New Session Created`, { session });
|
logger.debug(`New Session Created`, { session });
|
||||||
|
|
||||||
if (account.BuildLabel && version_compare(account.BuildLabel, "2015.03.21.08.17") < 0) {
|
res.json({ sessionId: { $oid: session.sessionId }, rewardSeed: 99999999 });
|
||||||
// U15 or below
|
|
||||||
res.send(session.sessionId.toString());
|
|
||||||
} else {
|
|
||||||
res.json({ sessionId: toOid2(session.sessionId, account.BuildLabel), rewardSeed: 99999999 });
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export { hostSessionController };
|
export { hostSessionController };
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|
||||||
import { addBooster, getInventory } from "@/src/services/inventoryService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { getRandomInt } from "@/src/services/rngService";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
import { ExportBoosters } from "warframe-public-export-plus";
|
|
||||||
|
|
||||||
export const hubBlessingController: RequestHandler = async (req, res) => {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const data = getJSONfromString<IHubBlessingRequest>(String(req.body));
|
|
||||||
const boosterType = ExportBoosters[data.booster].typeName;
|
|
||||||
if (req.query.mode == "send") {
|
|
||||||
const inventory = await getInventory(accountId, "BlessingCooldown Boosters");
|
|
||||||
inventory.BlessingCooldown = new Date(Date.now() + 86400000);
|
|
||||||
addBooster(boosterType, 3 * 3600, inventory);
|
|
||||||
await inventory.save();
|
|
||||||
|
|
||||||
let token = "";
|
|
||||||
for (let i = 0; i != 32; ++i) {
|
|
||||||
token += getRandomInt(0, 15).toString(16);
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
BlessingCooldown: inventory.BlessingCooldown,
|
|
||||||
SendTime: Math.trunc(Date.now() / 1000).toString(),
|
|
||||||
Token: token
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const inventory = await getInventory(accountId, "Boosters");
|
|
||||||
addBooster(boosterType, 3 * 3600, inventory);
|
|
||||||
await inventory.save();
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
BoosterType: data.booster,
|
|
||||||
Sender: data.senderId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IHubBlessingRequest {
|
|
||||||
booster: string;
|
|
||||||
senderId?: string; // mode=request
|
|
||||||
sendTime?: string; // mode=request
|
|
||||||
token?: string; // mode=request
|
|
||||||
}
|
|
@ -1,31 +1,21 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { Inbox } from "@/src/models/inboxModel";
|
import { Inbox } from "@/src/models/inboxModel";
|
||||||
import {
|
import {
|
||||||
createMessage,
|
|
||||||
createNewEventMessages,
|
createNewEventMessages,
|
||||||
deleteAllMessagesRead,
|
deleteAllMessagesRead,
|
||||||
deleteMessageRead,
|
deleteMessageRead,
|
||||||
getAllMessagesSorted,
|
getAllMessagesSorted,
|
||||||
getMessage
|
getMessage
|
||||||
} from "@/src/services/inboxService";
|
} from "@/src/services/inboxService";
|
||||||
import { getAccountForRequest, getAccountFromSuffixedName, getSuffixedName } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import {
|
import { addItems, getInventory } from "@/src/services/inventoryService";
|
||||||
addItems,
|
|
||||||
combineInventoryChanges,
|
|
||||||
getEffectiveAvatarImageType,
|
|
||||||
getInventory
|
|
||||||
} from "@/src/services/inventoryService";
|
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { ExportFlavour } from "warframe-public-export-plus";
|
import { ExportGear } from "warframe-public-export-plus";
|
||||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
|
||||||
import { fromStoreItem, isStoreItem } from "@/src/services/itemDataService";
|
|
||||||
import { IOid } from "@/src/types/commonTypes";
|
|
||||||
|
|
||||||
export const inboxController: RequestHandler = async (req, res) => {
|
export const inboxController: RequestHandler = async (req, res) => {
|
||||||
const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query;
|
const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query;
|
||||||
|
|
||||||
const account = await getAccountForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const accountId = account._id.toString();
|
|
||||||
|
|
||||||
if (deleteId) {
|
if (deleteId) {
|
||||||
if (deleteId === "DeleteAllRead") {
|
if (deleteId === "DeleteAllRead") {
|
||||||
@ -34,17 +24,17 @@ export const inboxController: RequestHandler = async (req, res) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await deleteMessageRead(parseOid(deleteId as string));
|
await deleteMessageRead(deleteId as string);
|
||||||
res.status(200).end();
|
res.status(200).end();
|
||||||
} else if (messageId) {
|
} else if (messageId) {
|
||||||
const message = await getMessage(parseOid(messageId as string));
|
const message = await getMessage(messageId as string);
|
||||||
message.r = true;
|
message.r = true;
|
||||||
await message.save();
|
const attachmentItems = message.att;
|
||||||
|
const attachmentCountedItems = message.countedAtt;
|
||||||
|
|
||||||
const attachmentItems = message.attVisualOnly ? undefined : message.att;
|
if (!attachmentItems && !attachmentCountedItems) {
|
||||||
const attachmentCountedItems = message.attVisualOnly ? undefined : message.countedAtt;
|
await message.save();
|
||||||
|
|
||||||
if (!attachmentItems && !attachmentCountedItems && !message.gifts) {
|
|
||||||
res.status(200).end();
|
res.status(200).end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -55,8 +45,8 @@ export const inboxController: RequestHandler = async (req, res) => {
|
|||||||
await addItems(
|
await addItems(
|
||||||
inventory,
|
inventory,
|
||||||
attachmentItems.map(attItem => ({
|
attachmentItems.map(attItem => ({
|
||||||
ItemType: isStoreItem(attItem) ? fromStoreItem(attItem) : attItem,
|
ItemType: attItem,
|
||||||
ItemCount: 1
|
ItemCount: attItem in ExportGear ? (ExportGear[attItem].purchaseQuantity ?? 1) : 1
|
||||||
})),
|
})),
|
||||||
inventoryChanges
|
inventoryChanges
|
||||||
);
|
);
|
||||||
@ -64,49 +54,15 @@ export const inboxController: RequestHandler = async (req, res) => {
|
|||||||
if (attachmentCountedItems) {
|
if (attachmentCountedItems) {
|
||||||
await addItems(inventory, attachmentCountedItems, inventoryChanges);
|
await addItems(inventory, attachmentCountedItems, inventoryChanges);
|
||||||
}
|
}
|
||||||
if (message.gifts) {
|
|
||||||
const sender = await getAccountFromSuffixedName(message.sndr);
|
|
||||||
const recipientName = getSuffixedName(account);
|
|
||||||
const giftQuantity = message.arg!.find(x => x.Key == "GIFT_QUANTITY")!.Tag as number;
|
|
||||||
for (const gift of message.gifts) {
|
|
||||||
combineInventoryChanges(
|
|
||||||
inventoryChanges,
|
|
||||||
(await handleStoreItemAcquisition(gift.GiftType, inventory, giftQuantity)).InventoryChanges
|
|
||||||
);
|
|
||||||
if (sender) {
|
|
||||||
await createMessage(sender._id, [
|
|
||||||
{
|
|
||||||
sndr: recipientName,
|
|
||||||
msg: "/Lotus/Language/Menu/GiftReceivedConfirmationBody",
|
|
||||||
arg: [
|
|
||||||
{
|
|
||||||
Key: "RECIPIENT_NAME",
|
|
||||||
Tag: recipientName
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "GIFT_TYPE",
|
|
||||||
Tag: gift.GiftType
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "GIFT_QUANTITY",
|
|
||||||
Tag: giftQuantity
|
|
||||||
}
|
|
||||||
],
|
|
||||||
sub: "/Lotus/Language/Menu/GiftReceivedConfirmationSubject",
|
|
||||||
icon: ExportFlavour[getEffectiveAvatarImageType(inventory)].icon,
|
|
||||||
highPriority: true
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
|
await message.save();
|
||||||
|
|
||||||
res.json({ InventoryChanges: inventoryChanges });
|
res.json({ InventoryChanges: inventoryChanges });
|
||||||
} else if (latestClientMessageId) {
|
} else if (latestClientMessageId) {
|
||||||
await createNewEventMessages(req);
|
await createNewEventMessages(req);
|
||||||
const messages = await Inbox.find({ ownerId: accountId }).sort({ date: 1 });
|
const messages = await Inbox.find({ ownerId: accountId }).sort({ date: 1 });
|
||||||
|
|
||||||
const latestClientMessage = messages.find(m => m._id.toString() === parseOid(latestClientMessageId as string));
|
const latestClientMessage = messages.find(m => m._id.toString() === latestClientMessageId);
|
||||||
|
|
||||||
if (!latestClientMessage) {
|
if (!latestClientMessage) {
|
||||||
logger.debug(`this should only happen after DeleteAllRead `);
|
logger.debug(`this should only happen after DeleteAllRead `);
|
||||||
@ -129,11 +85,3 @@ export const inboxController: RequestHandler = async (req, res) => {
|
|||||||
res.json({ Inbox: inbox });
|
res.json({ Inbox: inbox });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 33.6.0 has query arguments like lastMessage={"$oid":"68112baebf192e786d1502bb"} instead of lastMessage=68112baebf192e786d1502bb
|
|
||||||
const parseOid = (oid: string): string => {
|
|
||||||
if (oid[0] == "{") {
|
|
||||||
return (JSON.parse(oid) as IOid).$oid;
|
|
||||||
}
|
|
||||||
return oid;
|
|
||||||
};
|
|
||||||
|
@ -1,38 +1,36 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { getInventory, addMiscItems, updateCurrency, addRecipes, freeUpSlot } from "@/src/services/inventoryService";
|
import { getInventory, addMiscItems, updateCurrency, addRecipes, freeUpSlot } from "@/src/services/inventoryService";
|
||||||
import { IOid } from "@/src/types/commonTypes";
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
import {
|
import {
|
||||||
IConsumedSuit,
|
IConsumedSuit,
|
||||||
IHelminthFoodRecord,
|
IHelminthFoodRecord,
|
||||||
|
IInfestedFoundryClient,
|
||||||
|
IInfestedFoundryDatabase,
|
||||||
IInventoryClient,
|
IInventoryClient,
|
||||||
IMiscItem,
|
IMiscItem,
|
||||||
InventorySlot
|
InventorySlot,
|
||||||
|
ITypeCount
|
||||||
} from "@/src/types/inventoryTypes/inventoryTypes";
|
} from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { ExportMisc } from "warframe-public-export-plus";
|
import { ExportMisc, ExportRecipes } from "warframe-public-export-plus";
|
||||||
import { getRecipe } from "@/src/services/itemDataService";
|
import { getRecipe } from "@/src/services/itemDataService";
|
||||||
import { toMongoDate, version_compare } from "@/src/helpers/inventoryHelpers";
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
|
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { colorToShard } from "@/src/helpers/shardHelper";
|
import { colorToShard } from "@/src/helpers/shardHelper";
|
||||||
import { config } from "@/src/services/configService";
|
import { config } from "@/src/services/configService";
|
||||||
import {
|
|
||||||
addInfestedFoundryXP,
|
|
||||||
applyCheatsToInfestedFoundry,
|
|
||||||
handleSubsumeCompletion
|
|
||||||
} from "@/src/services/infestedFoundryService";
|
|
||||||
|
|
||||||
export const infestedFoundryController: RequestHandler = async (req, res) => {
|
export const infestedFoundryController: RequestHandler = async (req, res) => {
|
||||||
const account = await getAccountForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
switch (req.query.mode) {
|
switch (req.query.mode) {
|
||||||
case "s": {
|
case "s": {
|
||||||
// shard installation
|
// shard installation
|
||||||
const request = getJSONfromString<IShardInstallRequest>(String(req.body));
|
const request = getJSONfromString<IShardInstallRequest>(String(req.body));
|
||||||
const inventory = await getInventory(account._id.toString());
|
const inventory = await getInventory(accountId);
|
||||||
const suit = inventory.Suits.id(request.SuitId.$oid)!;
|
const suit = inventory.Suits.find(suit => suit._id.toString() == request.SuitId.$oid)!;
|
||||||
suit.ArchonCrystalUpgrades ??= [];
|
if (!suit.ArchonCrystalUpgrades || suit.ArchonCrystalUpgrades.length != 5) {
|
||||||
while (suit.ArchonCrystalUpgrades.length < request.Slot) {
|
suit.ArchonCrystalUpgrades = [{}, {}, {}, {}, {}];
|
||||||
suit.ArchonCrystalUpgrades.push({});
|
|
||||||
}
|
}
|
||||||
suit.ArchonCrystalUpgrades[request.Slot] = {
|
suit.ArchonCrystalUpgrades[request.Slot] = {
|
||||||
UpgradeType: request.UpgradeType,
|
UpgradeType: request.UpgradeType,
|
||||||
@ -57,8 +55,8 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
case "x": {
|
case "x": {
|
||||||
// shard removal
|
// shard removal
|
||||||
const request = getJSONfromString<IShardUninstallRequest>(String(req.body));
|
const request = getJSONfromString<IShardUninstallRequest>(String(req.body));
|
||||||
const inventory = await getInventory(account._id.toString());
|
const inventory = await getInventory(accountId);
|
||||||
const suit = inventory.Suits.id(request.SuitId.$oid)!;
|
const suit = inventory.Suits.find(suit => suit._id.toString() == request.SuitId.$oid)!;
|
||||||
|
|
||||||
const miscItemChanges: IMiscItem[] = [];
|
const miscItemChanges: IMiscItem[] = [];
|
||||||
if (suit.ArchonCrystalUpgrades![request.Slot].Color) {
|
if (suit.ArchonCrystalUpgrades![request.Slot].Color) {
|
||||||
@ -71,30 +69,18 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
ItemCount: 1
|
ItemCount: 1
|
||||||
});
|
});
|
||||||
addMiscItems(inventory, miscItemChanges);
|
addMiscItems(inventory, miscItemChanges);
|
||||||
|
|
||||||
// consume resources
|
|
||||||
if (!config.infiniteHelminthMaterials) {
|
|
||||||
let type: string;
|
|
||||||
let count: number;
|
|
||||||
if (account.BuildLabel && version_compare(account.BuildLabel, "2025.05.20.10.18") < 0) {
|
|
||||||
// < 38.6.0
|
|
||||||
type = "/Lotus/Types/Items/InfestedFoundry/HelminthBile";
|
|
||||||
count = 300;
|
|
||||||
} else {
|
|
||||||
// >= 38.6.0
|
|
||||||
type =
|
|
||||||
archonCrystalRemovalResource[
|
|
||||||
suit.ArchonCrystalUpgrades![request.Slot].Color!.replace("_MYTHIC", "")
|
|
||||||
];
|
|
||||||
count = suit.ArchonCrystalUpgrades![request.Slot].Color!.indexOf("_MYTHIC") != -1 ? 300 : 150;
|
|
||||||
}
|
|
||||||
inventory.InfestedFoundry!.Resources!.find(x => x.ItemType == type)!.Count -= count;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove from suit
|
// remove from suit
|
||||||
suit.ArchonCrystalUpgrades![request.Slot].UpgradeType = undefined;
|
suit.ArchonCrystalUpgrades![request.Slot] = {};
|
||||||
suit.ArchonCrystalUpgrades![request.Slot].Color = undefined;
|
|
||||||
|
if (!config.infiniteHelminthMaterials) {
|
||||||
|
// remove bile
|
||||||
|
const bile = inventory.InfestedFoundry!.Resources!.find(
|
||||||
|
x => x.ItemType == "/Lotus/Types/Items/InfestedFoundry/HelminthBile"
|
||||||
|
)!;
|
||||||
|
bile.Count -= 300;
|
||||||
|
}
|
||||||
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
|
|
||||||
@ -112,7 +98,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
case "n": {
|
case "n": {
|
||||||
// name the beast
|
// name the beast
|
||||||
const request = getJSONfromString<IHelminthNameRequest>(String(req.body));
|
const request = getJSONfromString<IHelminthNameRequest>(String(req.body));
|
||||||
const inventory = await getInventory(account._id.toString());
|
const inventory = await getInventory(accountId);
|
||||||
inventory.InfestedFoundry ??= {};
|
inventory.InfestedFoundry ??= {};
|
||||||
inventory.InfestedFoundry.Name = request.newName;
|
inventory.InfestedFoundry.Name = request.newName;
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
@ -135,7 +121,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const request = getJSONfromString<IHelminthFeedRequest>(String(req.body));
|
const request = getJSONfromString<IHelminthFeedRequest>(String(req.body));
|
||||||
const inventory = await getInventory(account._id.toString());
|
const inventory = await getInventory(accountId);
|
||||||
inventory.InfestedFoundry ??= {};
|
inventory.InfestedFoundry ??= {};
|
||||||
inventory.InfestedFoundry.Resources ??= [];
|
inventory.InfestedFoundry.Resources ??= [];
|
||||||
|
|
||||||
@ -231,7 +217,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
case "o": {
|
case "o": {
|
||||||
// offerings update
|
// offerings update
|
||||||
const request = getJSONfromString<IHelminthOfferingsUpdate>(String(req.body));
|
const request = getJSONfromString<IHelminthOfferingsUpdate>(String(req.body));
|
||||||
const inventory = await getInventory(account._id.toString());
|
const inventory = await getInventory(accountId);
|
||||||
inventory.InfestedFoundry ??= {};
|
inventory.InfestedFoundry ??= {};
|
||||||
inventory.InfestedFoundry.InvigorationIndex = request.OfferingsIndex;
|
inventory.InfestedFoundry.InvigorationIndex = request.OfferingsIndex;
|
||||||
inventory.InfestedFoundry.InvigorationSuitOfferings = request.SuitTypes;
|
inventory.InfestedFoundry.InvigorationSuitOfferings = request.SuitTypes;
|
||||||
@ -252,7 +238,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
case "a": {
|
case "a": {
|
||||||
// subsume warframe
|
// subsume warframe
|
||||||
const request = getJSONfromString<IHelminthSubsumeRequest>(String(req.body));
|
const request = getJSONfromString<IHelminthSubsumeRequest>(String(req.body));
|
||||||
const inventory = await getInventory(account._id.toString());
|
const inventory = await getInventory(accountId);
|
||||||
const recipe = getRecipe(request.Recipe)!;
|
const recipe = getRecipe(request.Recipe)!;
|
||||||
if (!config.infiniteHelminthMaterials) {
|
if (!config.infiniteHelminthMaterials) {
|
||||||
for (const ingredient of recipe.secretIngredients!) {
|
for (const ingredient of recipe.secretIngredients!) {
|
||||||
@ -302,7 +288,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
|
|
||||||
case "r": {
|
case "r": {
|
||||||
// rush subsume
|
// rush subsume
|
||||||
const inventory = await getInventory(account._id.toString());
|
const inventory = await getInventory(accountId);
|
||||||
const currencyChanges = updateCurrency(inventory, 50, true);
|
const currencyChanges = updateCurrency(inventory, 50, true);
|
||||||
const recipeChanges = handleSubsumeCompletion(inventory);
|
const recipeChanges = handleSubsumeCompletion(inventory);
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
@ -320,7 +306,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
|
|
||||||
case "u": {
|
case "u": {
|
||||||
const request = getJSONfromString<IHelminthInvigorationRequest>(String(req.body));
|
const request = getJSONfromString<IHelminthInvigorationRequest>(String(req.body));
|
||||||
const inventory = await getInventory(account._id.toString());
|
const inventory = await getInventory(accountId);
|
||||||
const suit = inventory.Suits.id(request.SuitId.$oid)!;
|
const suit = inventory.Suits.id(request.SuitId.$oid)!;
|
||||||
const upgradesExpiry = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
const upgradesExpiry = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
||||||
suit.OffensiveUpgrade = request.OffensiveUpgradeType;
|
suit.OffensiveUpgrade = request.OffensiveUpgradeType;
|
||||||
@ -353,7 +339,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "custom_unlockall": {
|
case "custom_unlockall": {
|
||||||
const inventory = await getInventory(account._id.toString());
|
const inventory = await getInventory(accountId);
|
||||||
inventory.InfestedFoundry ??= {};
|
inventory.InfestedFoundry ??= {};
|
||||||
inventory.InfestedFoundry.XP ??= 0;
|
inventory.InfestedFoundry.XP ??= 0;
|
||||||
if (151875_00 > inventory.InfestedFoundry.XP) {
|
if (151875_00 > inventory.InfestedFoundry.XP) {
|
||||||
@ -369,7 +355,6 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
|
||||||
throw new Error(`unhandled infestedFoundry mode: ${String(req.query.mode)}`);
|
throw new Error(`unhandled infestedFoundry mode: ${String(req.query.mode)}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -397,11 +382,116 @@ interface IHelminthFeedRequest {
|
|||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const addInfestedFoundryXP = (infestedFoundry: IInfestedFoundryDatabase, delta: number): ITypeCount[] => {
|
||||||
|
const recipeChanges: ITypeCount[] = [];
|
||||||
|
infestedFoundry.XP ??= 0;
|
||||||
|
const prevXP = infestedFoundry.XP;
|
||||||
|
infestedFoundry.XP += delta;
|
||||||
|
if (prevXP < 2250_00 && infestedFoundry.XP >= 2250_00) {
|
||||||
|
infestedFoundry.Slots ??= 0;
|
||||||
|
infestedFoundry.Slots += 3;
|
||||||
|
}
|
||||||
|
if (prevXP < 5625_00 && infestedFoundry.XP >= 5625_00) {
|
||||||
|
recipeChanges.push({
|
||||||
|
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthShieldsBlueprint",
|
||||||
|
ItemCount: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (prevXP < 10125_00 && infestedFoundry.XP >= 10125_00) {
|
||||||
|
recipeChanges.push({ ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthHackBlueprint", ItemCount: 1 });
|
||||||
|
}
|
||||||
|
if (prevXP < 15750_00 && infestedFoundry.XP >= 15750_00) {
|
||||||
|
infestedFoundry.Slots ??= 0;
|
||||||
|
infestedFoundry.Slots += 10;
|
||||||
|
}
|
||||||
|
if (prevXP < 22500_00 && infestedFoundry.XP >= 22500_00) {
|
||||||
|
recipeChanges.push({
|
||||||
|
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthAmmoEfficiencyBlueprint",
|
||||||
|
ItemCount: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (prevXP < 30375_00 && infestedFoundry.XP >= 30375_00) {
|
||||||
|
recipeChanges.push({ ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthStunBlueprint", ItemCount: 1 });
|
||||||
|
}
|
||||||
|
if (prevXP < 39375_00 && infestedFoundry.XP >= 39375_00) {
|
||||||
|
infestedFoundry.Slots ??= 0;
|
||||||
|
infestedFoundry.Slots += 20;
|
||||||
|
}
|
||||||
|
if (prevXP < 60750_00 && infestedFoundry.XP >= 60750_00) {
|
||||||
|
recipeChanges.push({ ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthStatusBlueprint", ItemCount: 1 });
|
||||||
|
}
|
||||||
|
if (prevXP < 73125_00 && infestedFoundry.XP >= 73125_00) {
|
||||||
|
infestedFoundry.Slots = 1;
|
||||||
|
}
|
||||||
|
if (prevXP < 86625_00 && infestedFoundry.XP >= 86625_00) {
|
||||||
|
recipeChanges.push({
|
||||||
|
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthShieldArmorBlueprint",
|
||||||
|
ItemCount: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (prevXP < 101250_00 && infestedFoundry.XP >= 101250_00) {
|
||||||
|
recipeChanges.push({
|
||||||
|
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthProcBlockBlueprint",
|
||||||
|
ItemCount: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (prevXP < 117000_00 && infestedFoundry.XP >= 117000_00) {
|
||||||
|
recipeChanges.push({
|
||||||
|
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthEnergyShareBlueprint",
|
||||||
|
ItemCount: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (prevXP < 133875_00 && infestedFoundry.XP >= 133875_00) {
|
||||||
|
recipeChanges.push({
|
||||||
|
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthMaxStatusBlueprint",
|
||||||
|
ItemCount: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (prevXP < 151875_00 && infestedFoundry.XP >= 151875_00) {
|
||||||
|
recipeChanges.push({
|
||||||
|
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthTreasureBlueprint",
|
||||||
|
ItemCount: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return recipeChanges;
|
||||||
|
};
|
||||||
|
|
||||||
interface IHelminthSubsumeRequest {
|
interface IHelminthSubsumeRequest {
|
||||||
SuitId: IOid;
|
SuitId: IOid;
|
||||||
Recipe: string;
|
Recipe: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const handleSubsumeCompletion = (inventory: TInventoryDatabaseDocument): ITypeCount[] => {
|
||||||
|
const [recipeType] = Object.entries(ExportRecipes).find(
|
||||||
|
([_recipeType, recipe]) =>
|
||||||
|
recipe.secretIngredientAction == "SIA_WARFRAME_ABILITY" &&
|
||||||
|
recipe.secretIngredients![0].ItemType == inventory.InfestedFoundry!.LastConsumedSuit!.ItemType
|
||||||
|
)!;
|
||||||
|
inventory.InfestedFoundry!.LastConsumedSuit = undefined;
|
||||||
|
inventory.InfestedFoundry!.AbilityOverrideUnlockCooldown = undefined;
|
||||||
|
const recipeChanges: ITypeCount[] = [
|
||||||
|
{
|
||||||
|
ItemType: recipeType,
|
||||||
|
ItemCount: 1
|
||||||
|
}
|
||||||
|
];
|
||||||
|
addRecipes(inventory, recipeChanges);
|
||||||
|
return recipeChanges;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const applyCheatsToInfestedFoundry = (infestedFoundry: IInfestedFoundryClient): void => {
|
||||||
|
if (config.infiniteHelminthMaterials) {
|
||||||
|
infestedFoundry.Resources = [
|
||||||
|
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthCalx", Count: 1000 },
|
||||||
|
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthBiotics", Count: 1000 },
|
||||||
|
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthSynthetics", Count: 1000 },
|
||||||
|
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthPheromones", Count: 1000 },
|
||||||
|
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthBile", Count: 1000 },
|
||||||
|
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthOxides", Count: 1000 }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
interface IHelminthOfferingsUpdate {
|
interface IHelminthOfferingsUpdate {
|
||||||
OfferingsIndex: number;
|
OfferingsIndex: number;
|
||||||
SuitTypes: string[];
|
SuitTypes: string[];
|
||||||
@ -452,12 +542,3 @@ const apetiteModel = (x: number): number => {
|
|||||||
}
|
}
|
||||||
return 3;
|
return 3;
|
||||||
};
|
};
|
||||||
|
|
||||||
const archonCrystalRemovalResource: Record<string, string> = {
|
|
||||||
ACC_RED: "/Lotus/Types/Items/InfestedFoundry/HelminthOxides",
|
|
||||||
ACC_YELLOW: "/Lotus/Types/Items/InfestedFoundry/HelminthBile",
|
|
||||||
ACC_BLUE: "/Lotus/Types/Items/InfestedFoundry/HelminthSynthetics",
|
|
||||||
ACC_GREEN: "/Lotus/Types/Items/InfestedFoundry/HelminthBiotics",
|
|
||||||
ACC_ORANGE: "/Lotus/Types/Items/InfestedFoundry/HelminthPheromones",
|
|
||||||
ACC_PURPLE: "/Lotus/Types/Items/InfestedFoundry/HelminthCalx"
|
|
||||||
};
|
|
||||||
|
@ -1,38 +1,26 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { Inventory, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
import { Inventory, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
import { config } from "@/src/services/configService";
|
import { config } from "@/src/services/configService";
|
||||||
import allDialogue from "@/static/fixed_responses/allDialogue.json";
|
import allDialogue from "@/static/fixed_responses/allDialogue.json";
|
||||||
import { ILoadoutDatabase } from "@/src/types/saveLoadoutTypes";
|
import { ILoadoutDatabase } from "@/src/types/saveLoadoutTypes";
|
||||||
import { IInventoryClient, IShipInventory, equipmentKeys } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IInventoryClient, IShipInventory, equipmentKeys } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { IPolarity, ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { IPolarity, ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
import { ExportCustoms, ExportFlavour, ExportResources, ExportVirtuals } from "warframe-public-export-plus";
|
|
||||||
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "@/src/services/infestedFoundryService";
|
|
||||||
import {
|
import {
|
||||||
addEmailItem,
|
ExportCustoms,
|
||||||
addMiscItems,
|
ExportFlavour,
|
||||||
allDailyAffiliationKeys,
|
ExportRegions,
|
||||||
cleanupInventory,
|
ExportResources,
|
||||||
createLibraryDailyTask,
|
ExportVirtuals
|
||||||
generateRewardSeed,
|
} from "warframe-public-export-plus";
|
||||||
getCalendarProgress
|
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "./infestedFoundryController";
|
||||||
} from "@/src/services/inventoryService";
|
import { addMiscItems, allDailyAffiliationKeys, createLibraryDailyTask } from "@/src/services/inventoryService";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
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, toOid, version_compare } from "@/src/helpers/inventoryHelpers";
|
|
||||||
import { Inbox } from "@/src/models/inboxModel";
|
|
||||||
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
|
||||||
import { DailyDeal } from "@/src/models/worldStateModel";
|
|
||||||
|
|
||||||
export const inventoryController: RequestHandler = async (request, response) => {
|
export const inventoryController: RequestHandler = async (request, response) => {
|
||||||
const account = await getAccountForRequest(request);
|
const accountId = await getAccountIdForRequest(request);
|
||||||
|
|
||||||
const inventory = await Inventory.findOne({ accountOwnerId: account._id });
|
const inventory = await Inventory.findOne({ accountOwnerId: accountId });
|
||||||
|
|
||||||
if (!inventory) {
|
if (!inventory) {
|
||||||
response.status(400).json({ error: "inventory was undefined" });
|
response.status(400).json({ error: "inventory was undefined" });
|
||||||
@ -41,24 +29,20 @@ export const inventoryController: RequestHandler = async (request, response) =>
|
|||||||
|
|
||||||
// Handle daily reset
|
// Handle daily reset
|
||||||
if (!inventory.NextRefill || Date.now() >= inventory.NextRefill.getTime()) {
|
if (!inventory.NextRefill || Date.now() >= inventory.NextRefill.getTime()) {
|
||||||
const today = Math.trunc(Date.now() / 86400000);
|
|
||||||
|
|
||||||
for (const key of allDailyAffiliationKeys) {
|
for (const key of allDailyAffiliationKeys) {
|
||||||
inventory[key] = 16000 + inventory.PlayerLevel * 500;
|
inventory[key] = 16000 + inventory.PlayerLevel * 500;
|
||||||
}
|
}
|
||||||
inventory.DailyFocus = 250000 + inventory.PlayerLevel * 5000;
|
inventory.DailyFocus = 250000 + inventory.PlayerLevel * 5000;
|
||||||
inventory.GiftsRemaining = Math.max(8, inventory.PlayerLevel);
|
|
||||||
inventory.TradesRemaining = inventory.PlayerLevel;
|
|
||||||
|
|
||||||
inventory.LibraryAvailableDailyTaskInfo = createLibraryDailyTask();
|
inventory.LibraryAvailableDailyTaskInfo = createLibraryDailyTask();
|
||||||
|
|
||||||
if (inventory.NextRefill) {
|
if (inventory.NextRefill) {
|
||||||
const lastLoginDay = Math.trunc(inventory.NextRefill.getTime() / 86400000) - 1;
|
|
||||||
const daysPassed = today - lastLoginDay;
|
|
||||||
|
|
||||||
if (config.noArgonCrystalDecay) {
|
if (config.noArgonCrystalDecay) {
|
||||||
inventory.FoundToday = undefined;
|
inventory.FoundToday = undefined;
|
||||||
} else {
|
} else {
|
||||||
|
const lastLoginDay = Math.trunc(inventory.NextRefill.getTime() / 86400000) - 1;
|
||||||
|
const today = Math.trunc(Date.now() / 86400000);
|
||||||
|
const daysPassed = today - lastLoginDay;
|
||||||
for (let i = 0; i != daysPassed; ++i) {
|
for (let i = 0; i != daysPassed; ++i) {
|
||||||
const numArgonCrystals =
|
const numArgonCrystals =
|
||||||
inventory.MiscItems.find(x => x.ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal")
|
inventory.MiscItems.find(x => x.ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal")
|
||||||
@ -66,11 +50,9 @@ export const inventoryController: RequestHandler = async (request, response) =>
|
|||||||
if (numArgonCrystals == 0) {
|
if (numArgonCrystals == 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const numStableArgonCrystals = Math.min(
|
const numStableArgonCrystals =
|
||||||
numArgonCrystals,
|
|
||||||
inventory.FoundToday?.find(x => x.ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal")
|
inventory.FoundToday?.find(x => x.ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal")
|
||||||
?.ItemCount ?? 0
|
?.ItemCount ?? 0;
|
||||||
);
|
|
||||||
const numDecayingArgonCrystals = numArgonCrystals - numStableArgonCrystals;
|
const numDecayingArgonCrystals = numArgonCrystals - numStableArgonCrystals;
|
||||||
const numDecayingArgonCrystalsToRemove = Math.ceil(numDecayingArgonCrystals / 2);
|
const numDecayingArgonCrystalsToRemove = Math.ceil(numDecayingArgonCrystals / 2);
|
||||||
logger.debug(`ticking argon crystals for day ${i + 1} of ${daysPassed}`, {
|
logger.debug(`ticking argon crystals for day ${i + 1} of ${daysPassed}`, {
|
||||||
@ -90,85 +72,10 @@ export const inventoryController: RequestHandler = async (request, response) =>
|
|||||||
inventory.FoundToday = undefined;
|
inventory.FoundToday = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inventory.UsedDailyDeals.length != 0) {
|
|
||||||
if (daysPassed == 1) {
|
|
||||||
const todayAt0Utc = today * 86400000;
|
|
||||||
const darvoIndex = Math.trunc((todayAt0Utc - 25200000) / (26 * unixTimesInMs.hour));
|
|
||||||
const darvoStart = darvoIndex * (26 * unixTimesInMs.hour) + 25200000;
|
|
||||||
const darvoOid =
|
|
||||||
((darvoStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "adc51a72f7324d95";
|
|
||||||
const deal = await DailyDeal.findById(darvoOid);
|
|
||||||
if (deal) {
|
|
||||||
inventory.UsedDailyDeals = inventory.UsedDailyDeals.filter(x => x == deal.StoreItem); // keep only the deal that came into this new day with us
|
|
||||||
} else {
|
|
||||||
inventory.UsedDailyDeals = [];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
inventory.UsedDailyDeals = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inventory.CalendarProgress) {
|
inventory.NextRefill = new Date((Math.trunc(Date.now() / 86400000) + 1) * 86400000);
|
||||||
const previousYearIteration = inventory.CalendarProgress.Iteration;
|
await inventory.save();
|
||||||
getCalendarProgress(inventory); // handle year rollover; the client expects to receive an inventory with an up-to-date CalendarProgress
|
|
||||||
|
|
||||||
// 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((today + 1) * 86400000); // tomorrow at 0 UTC
|
|
||||||
//await inventory.save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -177,51 +84,23 @@ export const inventoryController: RequestHandler = async (request, response) =>
|
|||||||
new Date() >= inventory.InfestedFoundry.AbilityOverrideUnlockCooldown
|
new Date() >= inventory.InfestedFoundry.AbilityOverrideUnlockCooldown
|
||||||
) {
|
) {
|
||||||
handleSubsumeCompletion(inventory);
|
handleSubsumeCompletion(inventory);
|
||||||
//await inventory.save();
|
await inventory.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inventory.LastInventorySync) {
|
response.json(await getInventoryResponse(inventory, "xpBasedLevelCapDisabled" in request.query));
|
||||||
const lastSyncDuviriMood = Math.trunc(inventory.LastInventorySync.getTimestamp().getTime() / 7200000);
|
|
||||||
const currentDuviriMood = Math.trunc(Date.now() / 7200000);
|
|
||||||
if (lastSyncDuviriMood != currentDuviriMood) {
|
|
||||||
logger.debug(`refreshing duviri seed`);
|
|
||||||
if (!inventory.DuviriInfo) {
|
|
||||||
inventory.DuviriInfo = {
|
|
||||||
Seed: generateRewardSeed(),
|
|
||||||
NumCompletions: 0
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
inventory.DuviriInfo.Seed = generateRewardSeed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
inventory.LastInventorySync = new Types.ObjectId();
|
|
||||||
await inventory.save();
|
|
||||||
|
|
||||||
response.json(
|
|
||||||
await getInventoryResponse(inventory, "xpBasedLevelCapDisabled" in request.query, account.BuildLabel)
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getInventoryResponse = async (
|
export const getInventoryResponse = async (
|
||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
xpBasedLevelCapDisabled: boolean,
|
xpBasedLevelCapDisabled: boolean
|
||||||
buildLabel: string | undefined
|
|
||||||
): Promise<IInventoryClient> => {
|
): Promise<IInventoryClient> => {
|
||||||
const [inventoryWithLoadOutPresets, ships, latestMessage] = await Promise.all([
|
const inventoryWithLoadOutPresets = await inventory.populate<{ LoadOutPresets: ILoadoutDatabase }>(
|
||||||
inventory.populate<{ LoadOutPresets: ILoadoutDatabase }>("LoadOutPresets"),
|
"LoadOutPresets"
|
||||||
Ship.find({ ShipOwnerId: inventory.accountOwnerId }),
|
);
|
||||||
Inbox.findOne({ ownerId: inventory.accountOwnerId }, "_id").sort({ date: -1 })
|
const inventoryWithLoadOutPresetsAndShips = await inventoryWithLoadOutPresets.populate<{ Ships: IShipInventory }>(
|
||||||
]);
|
"Ships"
|
||||||
const inventoryResponse = inventoryWithLoadOutPresets.toJSON<IInventoryClient>();
|
);
|
||||||
inventoryResponse.Ships = ships.map(x => x.toJSON<IShipInventory>());
|
const inventoryResponse = inventoryWithLoadOutPresetsAndShips.toJSON<IInventoryClient>();
|
||||||
|
|
||||||
// In case mission inventory update added an inbox message, we need to send the Mailbox part so the client knows to refresh it.
|
|
||||||
if (latestMessage) {
|
|
||||||
inventoryResponse.Mailbox = {
|
|
||||||
LastInboxId: toOid(latestMessage._id)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.infiniteCredits) {
|
if (config.infiniteCredits) {
|
||||||
inventoryResponse.RegularCredits = 999999999;
|
inventoryResponse.RegularCredits = 999999999;
|
||||||
@ -249,11 +128,23 @@ export const getInventoryResponse = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.unlockAllMissions) {
|
||||||
|
inventoryResponse.Missions = [];
|
||||||
|
for (const tag of Object.keys(ExportRegions)) {
|
||||||
|
inventoryResponse.Missions.push({
|
||||||
|
Completes: 1,
|
||||||
|
Tier: 1,
|
||||||
|
Tag: tag
|
||||||
|
});
|
||||||
|
}
|
||||||
|
addString(inventoryResponse.NodeIntrosCompleted, "TeshinHardModeUnlocked");
|
||||||
|
}
|
||||||
|
|
||||||
if (config.unlockAllShipDecorations) {
|
if (config.unlockAllShipDecorations) {
|
||||||
inventoryResponse.ShipDecorations = [];
|
inventoryResponse.ShipDecorations = [];
|
||||||
for (const [uniqueName, item] of Object.entries(ExportResources)) {
|
for (const [uniqueName, item] of Object.entries(ExportResources)) {
|
||||||
if (item.productCategory == "ShipDecorations") {
|
if (item.productCategory == "ShipDecorations") {
|
||||||
inventoryResponse.ShipDecorations.push({ ItemType: uniqueName, ItemCount: 999_999 });
|
inventoryResponse.ShipDecorations.push({ ItemType: uniqueName, ItemCount: 1 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -306,8 +197,7 @@ export const getInventoryResponse = async (
|
|||||||
|
|
||||||
if (config.universalPolarityEverywhere) {
|
if (config.universalPolarityEverywhere) {
|
||||||
const Polarity: IPolarity[] = [];
|
const Polarity: IPolarity[] = [];
|
||||||
// 12 is needed for necramechs. 15 is needed for plexus/crewshipharness.
|
for (let i = 0; i != 12; ++i) {
|
||||||
for (let i = 0; i != 15; ++i) {
|
|
||||||
Polarity.push({
|
Polarity.push({
|
||||||
Slot: i,
|
Slot: i,
|
||||||
Value: ArtifactPolarity.Any
|
Value: ArtifactPolarity.Any
|
||||||
@ -362,85 +252,24 @@ export const getInventoryResponse = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.noDailyFocusLimit) {
|
|
||||||
inventoryResponse.DailyFocus = Math.max(999_999, 250000 + inventoryResponse.PlayerLevel * 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inventoryResponse.InfestedFoundry) {
|
if (inventoryResponse.InfestedFoundry) {
|
||||||
applyCheatsToInfestedFoundry(inventoryResponse.InfestedFoundry);
|
applyCheatsToInfestedFoundry(inventoryResponse.InfestedFoundry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Omitting this field so opening the navigation resyncs the inventory which is more desirable for typical usage.
|
||||||
|
//inventoryResponse.LastInventorySync = toOid(new Types.ObjectId());
|
||||||
|
|
||||||
// Set 2FA enabled so trading post can be used
|
// Set 2FA enabled so trading post can be used
|
||||||
inventoryResponse.HWIDProtectEnabled = true;
|
inventoryResponse.HWIDProtectEnabled = true;
|
||||||
|
|
||||||
if (buildLabel) {
|
|
||||||
// Fix nemesis for older versions
|
|
||||||
if (
|
|
||||||
inventoryResponse.Nemesis &&
|
|
||||||
version_compare(buildLabel, getNemesisManifest(inventoryResponse.Nemesis.manifest).minBuild) < 0
|
|
||||||
) {
|
|
||||||
inventoryResponse.Nemesis = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (version_compare(buildLabel, "2018.02.22.14.34") < 0) {
|
|
||||||
const personalRoomsDb = await getPersonalRooms(inventory.accountOwnerId.toString());
|
|
||||||
const personalRooms = personalRoomsDb.toJSON<IPersonalRoomsClient>();
|
|
||||||
inventoryResponse.Ship = personalRooms.Ship;
|
|
||||||
|
|
||||||
if (version_compare(buildLabel, "2016.12.21.19.13") <= 0) {
|
|
||||||
// U19.5 and below use $id instead of $oid
|
|
||||||
for (const category of equipmentKeys) {
|
|
||||||
for (const item of inventoryResponse[category]) {
|
|
||||||
toLegacyOid(item.ItemId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const upgrade of inventoryResponse.Upgrades) {
|
|
||||||
toLegacyOid(upgrade.ItemId);
|
|
||||||
}
|
|
||||||
if (inventoryResponse.BrandedSuits) {
|
|
||||||
for (const id of inventoryResponse.BrandedSuits) {
|
|
||||||
toLegacyOid(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.unlockAllProfitTakerStages) {
|
|
||||||
inventoryResponse.CompletedJobChains ??= [];
|
|
||||||
const EudicoHeists = inventoryResponse.CompletedJobChains.find(x => x.LocationTag == "EudicoHeists");
|
|
||||||
if (EudicoHeists) {
|
|
||||||
EudicoHeists.Jobs = allEudicoHeistJobs;
|
|
||||||
} else {
|
|
||||||
inventoryResponse.CompletedJobChains.push({
|
|
||||||
LocationTag: "EudicoHeists",
|
|
||||||
Jobs: allEudicoHeistJobs
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.unlockAllSimarisResearchEntries) {
|
|
||||||
inventoryResponse.LibraryPersonalTarget = undefined;
|
|
||||||
inventoryResponse.LibraryPersonalProgress = [
|
|
||||||
"/Lotus/Types/Game/Library/Targets/Research1Target",
|
|
||||||
"/Lotus/Types/Game/Library/Targets/Research2Target",
|
|
||||||
"/Lotus/Types/Game/Library/Targets/Research3Target",
|
|
||||||
"/Lotus/Types/Game/Library/Targets/Research4Target",
|
|
||||||
"/Lotus/Types/Game/Library/Targets/Research5Target",
|
|
||||||
"/Lotus/Types/Game/Library/Targets/Research6Target",
|
|
||||||
"/Lotus/Types/Game/Library/Targets/Research7Target"
|
|
||||||
].map(type => ({ TargetType: type, Scans: 10, Completed: true }));
|
|
||||||
}
|
|
||||||
|
|
||||||
return inventoryResponse;
|
return inventoryResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
const allEudicoHeistJobs = [
|
export const addString = (arr: string[], str: string): void => {
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyOne",
|
if (!arr.find(x => x == str)) {
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyTwo",
|
arr.push(str);
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyThree",
|
}
|
||||||
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyFour"
|
};
|
||||||
];
|
|
||||||
|
|
||||||
const getExpRequiredForMr = (rank: number): number => {
|
const getExpRequiredForMr = (rank: number): number => {
|
||||||
if (rank <= 30) {
|
if (rank <= 30) {
|
||||||
@ -466,3 +295,13 @@ const resourceGetParent = (resourceName: string): string | undefined => {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
return ExportVirtuals[resourceName]?.parentName;
|
return ExportVirtuals[resourceName]?.parentName;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// This is FNV1a-32 except operating under modulus 2^31 because JavaScript is stinky and likes producing negative integers out of nowhere.
|
||||||
|
const catBreadHash = (name: string): number => {
|
||||||
|
let hash = 2166136261;
|
||||||
|
for (let i = 0; i != name.length; ++i) {
|
||||||
|
hash = (hash ^ name.charCodeAt(i)) & 0x7fffffff;
|
||||||
|
hash = (hash * 16777619) & 0x7fffffff;
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
};
|
||||||
|
@ -4,57 +4,49 @@ import { config } from "@/src/services/configService";
|
|||||||
import { buildConfig } from "@/src/services/buildConfigService";
|
import { buildConfig } from "@/src/services/buildConfigService";
|
||||||
|
|
||||||
import { Account } from "@/src/models/loginModel";
|
import { Account } from "@/src/models/loginModel";
|
||||||
import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "@/src/services/loginService";
|
import { createAccount, isCorrectPassword, isNameTaken } from "@/src/services/loginService";
|
||||||
import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes";
|
import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { version_compare } from "@/src/helpers/inventoryHelpers";
|
|
||||||
import { sendWsBroadcastTo } from "@/src/services/webService";
|
|
||||||
|
|
||||||
export const loginController: RequestHandler = async (request, response) => {
|
export const loginController: RequestHandler = async (request, response) => {
|
||||||
const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
|
const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
|
||||||
|
|
||||||
const account = await Account.findOne({ email: loginRequest.email });
|
const account = await Account.findOne({ email: loginRequest.email });
|
||||||
|
const nonce = Math.round(Math.random() * Number.MAX_SAFE_INTEGER);
|
||||||
|
|
||||||
const buildLabel: string =
|
const buildLabel: string =
|
||||||
typeof request.query.buildLabel == "string"
|
typeof request.query.buildLabel == "string"
|
||||||
? request.query.buildLabel.split(" ").join("+")
|
? request.query.buildLabel.split(" ").join("+")
|
||||||
: buildConfig.buildLabel;
|
: buildConfig.buildLabel;
|
||||||
|
|
||||||
let myAddress: string;
|
const myAddress = request.host.indexOf("warframe.com") == -1 ? request.host : config.myAddress;
|
||||||
let myUrlBase: string = request.protocol + "://";
|
|
||||||
if (request.host.indexOf("warframe.com") == -1) {
|
|
||||||
// Client request was redirected cleanly, so we know it can reach us how it's reaching us now.
|
|
||||||
myAddress = request.hostname;
|
|
||||||
myUrlBase += request.host;
|
|
||||||
} else {
|
|
||||||
// Don't know how the client reached us, hoping the config does.
|
|
||||||
myAddress = config.myAddress;
|
|
||||||
myUrlBase += myAddress;
|
|
||||||
const port: number = request.protocol == "http" ? config.httpPort || 80 : config.httpsPort || 443;
|
|
||||||
if (port != (request.protocol == "http" ? 80 : 443)) {
|
|
||||||
myUrlBase += ":" + port;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (!account && config.autoCreateAccount && loginRequest.ClientType != "webui") {
|
||||||
!account &&
|
|
||||||
((config.autoCreateAccount && loginRequest.ClientType != "webui") ||
|
|
||||||
loginRequest.ClientType == "webui-register")
|
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
const name = await getUsernameFromEmail(loginRequest.email);
|
const nameFromEmail = loginRequest.email.substring(0, loginRequest.email.indexOf("@"));
|
||||||
|
let name = nameFromEmail;
|
||||||
|
if (await isNameTaken(name)) {
|
||||||
|
let suffix = 0;
|
||||||
|
do {
|
||||||
|
++suffix;
|
||||||
|
name = nameFromEmail + suffix;
|
||||||
|
} while (await isNameTaken(name));
|
||||||
|
}
|
||||||
const newAccount = await createAccount({
|
const newAccount = await createAccount({
|
||||||
email: loginRequest.email,
|
email: loginRequest.email,
|
||||||
password: loginRequest.password,
|
password: loginRequest.password,
|
||||||
DisplayName: name,
|
DisplayName: name,
|
||||||
CountryCode: loginRequest.lang?.toUpperCase() ?? "EN",
|
CountryCode: loginRequest.lang.toUpperCase(),
|
||||||
ClientType: loginRequest.ClientType,
|
ClientType: loginRequest.ClientType,
|
||||||
Nonce: createNonce(),
|
CrossPlatformAllowed: true,
|
||||||
BuildLabel: buildLabel,
|
ForceLogoutVersion: 0,
|
||||||
LastLogin: new Date()
|
ConsentNeeded: false,
|
||||||
|
TrackedSettings: [],
|
||||||
|
Nonce: nonce,
|
||||||
|
LatestEventMessageDate: new Date(0)
|
||||||
});
|
});
|
||||||
logger.debug("created new account");
|
logger.debug("created new account");
|
||||||
response.json(createLoginResponse(myAddress, myUrlBase, newAccount, buildLabel));
|
response.json(createLoginResponse(myAddress, newAccount, buildLabel));
|
||||||
return;
|
return;
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
@ -63,85 +55,52 @@ export const loginController: RequestHandler = async (request, response) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!account) {
|
//email not found or incorrect password
|
||||||
response.status(400).json({ error: "unknown user" });
|
if (!account || !isCorrectPassword(loginRequest.password, account.password)) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isCorrectPassword(loginRequest.password, account.password)) {
|
|
||||||
response.status(400).json({ error: "incorrect login data" });
|
response.status(400).json({ error: "incorrect login data" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (account.Nonce && account.ClientType != "webui" && !account.Dropped && !loginRequest.kick) {
|
if (loginRequest.ClientType == "webui") {
|
||||||
// U17 seems to handle "nonce still set" like a login failure.
|
if (!account.Nonce) {
|
||||||
if (version_compare(buildLabel, "2015.12.05.18.07") >= 0) {
|
account.ClientType = "webui";
|
||||||
response.status(400).send({ error: "nonce still set" });
|
account.Nonce = nonce;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (account.Nonce && account.ClientType != "webui" && !account.Dropped && !loginRequest.kick) {
|
||||||
|
response.status(400).json({ error: "nonce still set" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
account.ClientType = loginRequest.ClientType;
|
account.ClientType = loginRequest.ClientType;
|
||||||
account.Nonce = createNonce();
|
account.Nonce = nonce;
|
||||||
account.CountryCode = loginRequest.lang?.toUpperCase() ?? "EN";
|
account.CountryCode = loginRequest.lang.toUpperCase();
|
||||||
account.BuildLabel = buildLabel;
|
}
|
||||||
account.LastLogin = new Date();
|
|
||||||
await account.save();
|
await account.save();
|
||||||
|
|
||||||
// Tell WebUI its nonce has been invalidated
|
response.json(createLoginResponse(myAddress, account.toJSON(), buildLabel));
|
||||||
sendWsBroadcastTo(account._id.toString(), { logged_out: true });
|
|
||||||
|
|
||||||
response.json(createLoginResponse(myAddress, myUrlBase, account.toJSON(), buildLabel));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const createLoginResponse = (
|
const createLoginResponse = (myAddress: string, account: IDatabaseAccountJson, buildLabel: string): ILoginResponse => {
|
||||||
myAddress: string,
|
return {
|
||||||
myUrlBase: string,
|
|
||||||
account: IDatabaseAccountJson,
|
|
||||||
buildLabel: string
|
|
||||||
): ILoginResponse => {
|
|
||||||
const resp: ILoginResponse = {
|
|
||||||
id: account.id,
|
id: account.id,
|
||||||
DisplayName: account.DisplayName,
|
DisplayName: account.DisplayName,
|
||||||
CountryCode: account.CountryCode,
|
CountryCode: account.CountryCode,
|
||||||
|
ClientType: account.ClientType,
|
||||||
|
CrossPlatformAllowed: account.CrossPlatformAllowed,
|
||||||
|
ForceLogoutVersion: account.ForceLogoutVersion,
|
||||||
AmazonAuthToken: account.AmazonAuthToken,
|
AmazonAuthToken: account.AmazonAuthToken,
|
||||||
AmazonRefreshToken: account.AmazonRefreshToken,
|
AmazonRefreshToken: account.AmazonRefreshToken,
|
||||||
|
ConsentNeeded: account.ConsentNeeded,
|
||||||
|
TrackedSettings: account.TrackedSettings,
|
||||||
Nonce: account.Nonce,
|
Nonce: account.Nonce,
|
||||||
BuildLabel: buildLabel
|
Groups: [],
|
||||||
|
IRC: config.myIrcAddresses ?? [myAddress],
|
||||||
|
platformCDNs: [`https://${myAddress}/`],
|
||||||
|
HUB: `https://${myAddress}/api/`,
|
||||||
|
NRS: config.NRS,
|
||||||
|
DTLS: 99,
|
||||||
|
BuildLabel: buildLabel,
|
||||||
|
MatchmakingBuildId: buildConfig.matchmakingBuildId
|
||||||
};
|
};
|
||||||
if (version_compare(buildLabel, "2015.02.13.10.41") >= 0) {
|
|
||||||
resp.NRS = config.NRS;
|
|
||||||
}
|
|
||||||
if (version_compare(buildLabel, "2015.05.14.16.29") >= 0) {
|
|
||||||
// U17 and up
|
|
||||||
resp.IRC = config.myIrcAddresses ?? [myAddress];
|
|
||||||
}
|
|
||||||
if (version_compare(buildLabel, "2018.11.08.14.45") >= 0) {
|
|
||||||
// U24 and up
|
|
||||||
resp.ConsentNeeded = account.ConsentNeeded;
|
|
||||||
resp.TrackedSettings = account.TrackedSettings;
|
|
||||||
}
|
|
||||||
if (version_compare(buildLabel, "2019.08.29.20.01") >= 0) {
|
|
||||||
// U25.7 and up
|
|
||||||
resp.ForceLogoutVersion = account.ForceLogoutVersion;
|
|
||||||
}
|
|
||||||
if (version_compare(buildLabel, "2019.10.31.22.42") >= 0) {
|
|
||||||
// U26 and up
|
|
||||||
resp.Groups = [];
|
|
||||||
}
|
|
||||||
if (version_compare(buildLabel, "2021.04.13.19.58") >= 0) {
|
|
||||||
resp.DTLS = 99;
|
|
||||||
}
|
|
||||||
if (version_compare(buildLabel, "2022.04.29.12.53") >= 0) {
|
|
||||||
resp.ClientType = account.ClientType;
|
|
||||||
}
|
|
||||||
if (version_compare(buildLabel, "2022.09.06.19.24") >= 0) {
|
|
||||||
resp.CrossPlatformAllowed = account.CrossPlatformAllowed;
|
|
||||||
resp.HUB = `${myUrlBase}/api/`;
|
|
||||||
resp.MatchmakingBuildId = buildConfig.matchmakingBuildId;
|
|
||||||
}
|
|
||||||
if (version_compare(buildLabel, "2023.04.25.23.40") >= 0) {
|
|
||||||
resp.platformCDNs = [`${myUrlBase}/`];
|
|
||||||
}
|
|
||||||
return resp;
|
|
||||||
};
|
};
|
||||||
|
@ -1,57 +1,8 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
import loginRewards from "@/static/fixed_responses/loginRewards.json";
|
||||||
import {
|
|
||||||
claimLoginReward,
|
|
||||||
getRandomLoginRewards,
|
|
||||||
ILoginRewardsReponse,
|
|
||||||
isLoginRewardAChoice,
|
|
||||||
setAccountGotLoginRewardToday
|
|
||||||
} from "@/src/services/loginRewardService";
|
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
|
||||||
import { config } from "@/src/services/configService";
|
|
||||||
import { sendWsBroadcastTo } from "@/src/services/webService";
|
|
||||||
|
|
||||||
export const loginRewardsController: RequestHandler = async (req, res) => {
|
const loginRewardsController: RequestHandler = (_req, res) => {
|
||||||
const account = await getAccountForRequest(req);
|
res.json(loginRewards);
|
||||||
const today = Math.trunc(Date.now() / 86400000) * 86400;
|
|
||||||
const isMilestoneDay = account.LoginDays == 5 || account.LoginDays % 50 == 0;
|
|
||||||
const nextMilestoneDay = account.LoginDays < 5 ? 5 : (Math.trunc(account.LoginDays / 50) + 1) * 50;
|
|
||||||
|
|
||||||
if (today == account.LastLoginRewardDate || config.disableDailyTribute) {
|
|
||||||
res.json({
|
|
||||||
DailyTributeInfo: {
|
|
||||||
IsMilestoneDay: isMilestoneDay,
|
|
||||||
IsChooseRewardSet: isLoginRewardAChoice(account),
|
|
||||||
LoginDays: account.LoginDays,
|
|
||||||
NextMilestoneReward: "",
|
|
||||||
NextMilestoneDay: nextMilestoneDay
|
|
||||||
}
|
|
||||||
} satisfies ILoginRewardsReponse);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const inventory = await getInventory(account._id.toString());
|
|
||||||
const randomRewards = getRandomLoginRewards(account, inventory);
|
|
||||||
const response: ILoginRewardsReponse = {
|
|
||||||
DailyTributeInfo: {
|
|
||||||
Rewards: randomRewards,
|
|
||||||
IsMilestoneDay: isMilestoneDay,
|
|
||||||
IsChooseRewardSet: randomRewards.length != 1,
|
|
||||||
LoginDays: account.LoginDays,
|
|
||||||
NextMilestoneReward: "",
|
|
||||||
NextMilestoneDay: nextMilestoneDay,
|
|
||||||
HasChosenReward: false
|
|
||||||
},
|
|
||||||
LastLoginRewardDate: today
|
|
||||||
};
|
|
||||||
if (!isMilestoneDay && randomRewards.length == 1) {
|
|
||||||
response.DailyTributeInfo.HasChosenReward = true;
|
|
||||||
response.DailyTributeInfo.ChosenReward = randomRewards[0];
|
|
||||||
response.DailyTributeInfo.NewInventory = await claimLoginReward(inventory, randomRewards[0]);
|
|
||||||
setAccountGotLoginRewardToday(account);
|
|
||||||
await Promise.all([inventory.save(), account.save()]);
|
|
||||||
|
|
||||||
sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
|
|
||||||
}
|
|
||||||
res.json(response);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export { loginRewardsController };
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
import { getInventory } from "@/src/services/inventoryService";
|
|
||||||
import {
|
|
||||||
claimLoginReward,
|
|
||||||
getRandomLoginRewards,
|
|
||||||
setAccountGotLoginRewardToday
|
|
||||||
} from "@/src/services/loginRewardService";
|
|
||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
|
||||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
|
||||||
import { sendWsBroadcastTo } from "@/src/services/webService";
|
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
|
||||||
import { logger } from "@/src/utils/logger";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
|
|
||||||
export const loginRewardsSelectionController: RequestHandler = async (req, res) => {
|
|
||||||
const account = await getAccountForRequest(req);
|
|
||||||
const inventory = await getInventory(account._id.toString());
|
|
||||||
const body = JSON.parse(String(req.body)) as ILoginRewardsSelectionRequest;
|
|
||||||
const isMilestoneDay = account.LoginDays == 5 || account.LoginDays % 50 == 0;
|
|
||||||
if (body.IsMilestoneReward != isMilestoneDay) {
|
|
||||||
logger.warn(`Client disagrees on login milestone (got ${body.IsMilestoneReward}, expected ${isMilestoneDay})`);
|
|
||||||
}
|
|
||||||
let chosenReward;
|
|
||||||
let inventoryChanges: IInventoryChanges;
|
|
||||||
if (body.IsMilestoneReward) {
|
|
||||||
chosenReward = {
|
|
||||||
RewardType: "RT_STORE_ITEM",
|
|
||||||
StoreItemType: body.ChosenReward
|
|
||||||
};
|
|
||||||
inventoryChanges = (await handleStoreItemAcquisition(body.ChosenReward, inventory)).InventoryChanges;
|
|
||||||
if (evergreenRewards.indexOf(body.ChosenReward) == -1) {
|
|
||||||
inventory.LoginMilestoneRewards.push(body.ChosenReward);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const randomRewards = getRandomLoginRewards(account, inventory);
|
|
||||||
chosenReward = randomRewards.find(x => x.StoreItemType == body.ChosenReward)!;
|
|
||||||
inventoryChanges = await claimLoginReward(inventory, chosenReward);
|
|
||||||
}
|
|
||||||
setAccountGotLoginRewardToday(account);
|
|
||||||
await Promise.all([inventory.save(), account.save()]);
|
|
||||||
|
|
||||||
sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
|
|
||||||
res.json({
|
|
||||||
DailyTributeInfo: {
|
|
||||||
NewInventory: inventoryChanges,
|
|
||||||
ChosenReward: chosenReward
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
interface ILoginRewardsSelectionRequest {
|
|
||||||
ChosenReward: string;
|
|
||||||
IsMilestoneReward: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const evergreenRewards = [
|
|
||||||
"/Lotus/Types/StoreItems/Packages/EvergreenTripleForma",
|
|
||||||
"/Lotus/Types/StoreItems/Packages/EvergreenTripleRifleRiven",
|
|
||||||
"/Lotus/Types/StoreItems/Packages/EvergreenTripleMeleeRiven",
|
|
||||||
"/Lotus/Types/StoreItems/Packages/EvergreenTripleSecondaryRiven",
|
|
||||||
"/Lotus/Types/StoreItems/Packages/EvergreenWeaponSlots",
|
|
||||||
"/Lotus/Types/StoreItems/Packages/EvergreenKuva",
|
|
||||||
"/Lotus/Types/StoreItems/Packages/EvergreenBoosters",
|
|
||||||
"/Lotus/Types/StoreItems/Packages/EvergreenEndo",
|
|
||||||
"/Lotus/Types/StoreItems/Packages/EvergreenExilus"
|
|
||||||
];
|
|
@ -1,33 +1,19 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { Account } from "@/src/models/loginModel";
|
import { Account } from "@/src/models/loginModel";
|
||||||
import { sendWsBroadcastTo } from "@/src/services/webService";
|
|
||||||
|
|
||||||
export const logoutController: RequestHandler = async (req, res) => {
|
const logoutController: RequestHandler = async (req, res) => {
|
||||||
if (!req.query.accountId) {
|
const accountId = await getAccountIdForRequest(req);
|
||||||
throw new Error("Request is missing accountId parameter");
|
const account = await Account.findOne({ _id: accountId });
|
||||||
|
if (account) {
|
||||||
|
account.Nonce = 0;
|
||||||
|
await account.save();
|
||||||
}
|
}
|
||||||
const nonce: number = parseInt(req.query.nonce as string);
|
|
||||||
if (!nonce) {
|
|
||||||
throw new Error("Request is missing nonce parameter");
|
|
||||||
}
|
|
||||||
|
|
||||||
const stat = await Account.updateOne(
|
|
||||||
{
|
|
||||||
_id: req.query.accountId,
|
|
||||||
Nonce: nonce
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Nonce: 0
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (stat.modifiedCount) {
|
|
||||||
// Tell WebUI its nonce has been invalidated
|
|
||||||
sendWsBroadcastTo(req.query.accountId as string, { logged_out: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
res.writeHead(200, {
|
res.writeHead(200, {
|
||||||
"Content-Type": "text/html",
|
"Content-Type": "text/html",
|
||||||
"Content-Length": 1
|
"Content-Length": 1
|
||||||
});
|
});
|
||||||
res.end("1");
|
res.end("1");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export { logoutController };
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
|
|
||||||
export const maturePetController: RequestHandler = async (req, res) => {
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
|
||||||
const inventory = await getInventory(accountId, "KubrowPets");
|
|
||||||
const data = getJSONfromString<IMaturePetRequest>(String(req.body));
|
|
||||||
const details = inventory.KubrowPets.id(data.petId)!.Details!;
|
|
||||||
details.IsPuppy = data.revert;
|
|
||||||
await inventory.save();
|
|
||||||
res.json({
|
|
||||||
petId: data.petId,
|
|
||||||
updateCollar: true,
|
|
||||||
armorSkins: ["", "", ""],
|
|
||||||
furPatterns: data.revert
|
|
||||||
? ["", "", ""]
|
|
||||||
: [details.DominantTraits.FurPattern, details.DominantTraits.FurPattern, details.DominantTraits.FurPattern],
|
|
||||||
unmature: data.revert
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IMaturePetRequest {
|
|
||||||
petId: string;
|
|
||||||
revert: boolean;
|
|
||||||
}
|
|
@ -1,13 +1,11 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { IMissionInventoryUpdateRequest } from "@/src/types/requestTypes";
|
import { IMissionInventoryUpdateRequest } from "@/src/types/requestTypes";
|
||||||
import { addMissionInventoryUpdates, addMissionRewards } from "@/src/services/missionInventoryUpdateService";
|
import { addMissionInventoryUpdates, addMissionRewards } from "@/src/services/missionInventoryUpdateService";
|
||||||
import { generateRewardSeed, getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { getInventoryResponse } from "./inventoryController";
|
import { getInventoryResponse } from "./inventoryController";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { IMissionInventoryUpdateResponse } from "@/src/types/missionTypes";
|
|
||||||
import { sendWsBroadcastTo } from "@/src/services/webService";
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
**** INPUT ****
|
**** INPUT ****
|
||||||
@ -49,52 +47,29 @@ import { sendWsBroadcastTo } from "@/src/services/webService";
|
|||||||
- [ ] FpsSamples
|
- [ ] FpsSamples
|
||||||
*/
|
*/
|
||||||
//move credit calc in here, return MissionRewards: [] if no reward info
|
//move credit calc in here, return MissionRewards: [] if no reward info
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
export const missionInventoryUpdateController: RequestHandler = async (req, res): Promise<void> => {
|
export const missionInventoryUpdateController: RequestHandler = async (req, res): Promise<void> => {
|
||||||
const account = await getAccountForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const missionReport = getJSONfromString<IMissionInventoryUpdateRequest>((req.body as string).toString());
|
const missionReport = getJSONfromString<IMissionInventoryUpdateRequest>((req.body as string).toString());
|
||||||
logger.debug("mission report:", missionReport);
|
logger.debug("mission report:", missionReport);
|
||||||
|
|
||||||
const inventory = await getInventory(account._id.toString());
|
const inventory = await getInventory(accountId);
|
||||||
const firstCompletion = missionReport.SortieId
|
const inventoryUpdates = await addMissionInventoryUpdates(inventory, missionReport);
|
||||||
? inventory.CompletedSorties.indexOf(missionReport.SortieId) == -1
|
|
||||||
: false;
|
|
||||||
const inventoryUpdates = await addMissionInventoryUpdates(account, inventory, missionReport);
|
|
||||||
|
|
||||||
if (
|
if (missionReport.MissionStatus !== "GS_SUCCESS") {
|
||||||
missionReport.MissionStatus !== "GS_SUCCESS" &&
|
|
||||||
!(
|
|
||||||
missionReport.RewardInfo?.jobId ||
|
|
||||||
missionReport.RewardInfo?.challengeMissionId ||
|
|
||||||
missionReport.RewardInfo?.T
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
if (missionReport.EndOfMatchUpload) {
|
|
||||||
inventory.RewardSeed = generateRewardSeed();
|
|
||||||
}
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
const inventoryResponse = await getInventoryResponse(inventory, true, account.BuildLabel);
|
const inventoryResponse = await getInventoryResponse(inventory, true);
|
||||||
res.json({
|
res.json({
|
||||||
InventoryJson: JSON.stringify(inventoryResponse),
|
InventoryJson: JSON.stringify(inventoryResponse),
|
||||||
MissionRewards: []
|
MissionRewards: []
|
||||||
});
|
});
|
||||||
sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { MissionRewards, inventoryChanges, credits } = await addMissionRewards(inventory, missionReport);
|
||||||
MissionRewards,
|
|
||||||
inventoryChanges,
|
|
||||||
credits,
|
|
||||||
AffiliationMods,
|
|
||||||
SyndicateXPItemReward,
|
|
||||||
ConquestCompletedMissionsCount
|
|
||||||
} = await addMissionRewards(account, inventory, missionReport, firstCompletion);
|
|
||||||
|
|
||||||
if (missionReport.EndOfMatchUpload) {
|
|
||||||
inventory.RewardSeed = generateRewardSeed();
|
|
||||||
}
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
const inventoryResponse = await getInventoryResponse(inventory, true, account.BuildLabel);
|
const inventoryResponse = await getInventoryResponse(inventory, true);
|
||||||
|
|
||||||
//TODO: figure out when to send inventory. it is needed for many cases.
|
//TODO: figure out when to send inventory. it is needed for many cases.
|
||||||
res.json({
|
res.json({
|
||||||
@ -103,12 +78,8 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
|
|||||||
MissionRewards,
|
MissionRewards,
|
||||||
...credits,
|
...credits,
|
||||||
...inventoryUpdates,
|
...inventoryUpdates,
|
||||||
//FusionPoints: inventoryChanges?.FusionPoints, // This in combination with InventoryJson or InventoryChanges seems to just double the number of endo shown, so unsure when this is needed.
|
FusionPoints: inventoryChanges?.FusionPoints
|
||||||
SyndicateXPItemReward,
|
});
|
||||||
AffiliationMods,
|
|
||||||
ConquestCompletedMissionsCount
|
|
||||||
} satisfies IMissionInventoryUpdateResponse);
|
|
||||||
sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -8,22 +8,15 @@ import {
|
|||||||
addMiscItems,
|
addMiscItems,
|
||||||
applyDefaultUpgrades,
|
applyDefaultUpgrades,
|
||||||
occupySlot,
|
occupySlot,
|
||||||
productCategoryToInventoryBin,
|
productCategoryToInventoryBin
|
||||||
combineInventoryChanges,
|
|
||||||
addSpecialItem
|
|
||||||
} from "@/src/services/inventoryService";
|
} from "@/src/services/inventoryService";
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { getDefaultUpgrades } from "@/src/services/itemDataService";
|
import { getDefaultUpgrades } from "@/src/services/itemDataService";
|
||||||
import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
|
import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
|
||||||
import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
|
||||||
import { getRandomInt } from "@/src/services/rngService";
|
|
||||||
import { ExportSentinels, ExportWeapons, IDefaultUpgrade } from "warframe-public-export-plus";
|
|
||||||
import { Status } from "@/src/types/inventoryTypes/inventoryTypes";
|
|
||||||
|
|
||||||
interface IModularCraftRequest {
|
interface IModularCraftRequest {
|
||||||
WeaponType: string;
|
WeaponType: string;
|
||||||
Parts: string[];
|
Parts: string[];
|
||||||
isWebUi?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const modularWeaponCraftingController: RequestHandler = async (req, res) => {
|
export const modularWeaponCraftingController: RequestHandler = async (req, res) => {
|
||||||
@ -35,158 +28,34 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
|
|||||||
const category = modularWeaponTypes[data.WeaponType];
|
const category = modularWeaponTypes[data.WeaponType];
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
|
|
||||||
let defaultUpgrades: IDefaultUpgrade[] | undefined;
|
const defaultUpgrades = getDefaultUpgrades(data.Parts);
|
||||||
const defaultOverwrites: Partial<IEquipmentDatabase> = {
|
const configs = applyDefaultUpgrades(inventory, defaultUpgrades);
|
||||||
ModularParts: data.Parts
|
const inventoryChanges: IInventoryChanges = {
|
||||||
|
...addEquipment(inventory, category, data.WeaponType, data.Parts, {}, { Configs: configs }),
|
||||||
|
...occupySlot(inventory, productCategoryToInventoryBin(category)!, false)
|
||||||
};
|
};
|
||||||
const inventoryChanges: IInventoryChanges = {};
|
|
||||||
if (category == "KubrowPets") {
|
|
||||||
const traits = {
|
|
||||||
"/Lotus/Types/Friendly/Pets/CreaturePets/ArmoredInfestedCatbrowPetPowerSuit": {
|
|
||||||
BaseColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorRareBase",
|
|
||||||
SecondaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorRareSecondary",
|
|
||||||
TertiaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorRareTertiary",
|
|
||||||
AccentColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorRareAccent",
|
|
||||||
EyeColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorRareEyes",
|
|
||||||
FurPattern: "/Lotus/Types/Game/InfestedKavatPet/Patterns/InfestedCritterPatternDefault",
|
|
||||||
Personality: data.WeaponType,
|
|
||||||
BodyType: "/Lotus/Types/Game/CatbrowPet/BodyTypes/InfestedCatbrowPetRegularBodyType",
|
|
||||||
Head: "/Lotus/Types/Game/InfestedKavatPet/Heads/InfestedCritterHeadC"
|
|
||||||
},
|
|
||||||
"/Lotus/Types/Friendly/Pets/CreaturePets/HornedInfestedCatbrowPetPowerSuit": {
|
|
||||||
BaseColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorUncommonBase",
|
|
||||||
SecondaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorUncommonSecondary",
|
|
||||||
TertiaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorUncommonTertiary",
|
|
||||||
AccentColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorUncommonAccent",
|
|
||||||
EyeColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorUncommonEyes",
|
|
||||||
FurPattern: "/Lotus/Types/Game/InfestedKavatPet/Patterns/InfestedCritterPatternDefault",
|
|
||||||
Personality: data.WeaponType,
|
|
||||||
BodyType: "/Lotus/Types/Game/CatbrowPet/BodyTypes/InfestedCatbrowPetRegularBodyType",
|
|
||||||
Head: "/Lotus/Types/Game/InfestedKavatPet/Heads/InfestedCritterHeadB"
|
|
||||||
},
|
|
||||||
"/Lotus/Types/Friendly/Pets/CreaturePets/MedjayPredatorKubrowPetPowerSuit": {
|
|
||||||
BaseColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorRareBase",
|
|
||||||
SecondaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorRareSecondary",
|
|
||||||
TertiaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorRareTertiary",
|
|
||||||
AccentColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorRareAccent",
|
|
||||||
EyeColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorRareEyes",
|
|
||||||
FurPattern: "/Lotus/Types/Game/InfestedPredatorPet/Patterns/InfestedPredatorPatternDefault",
|
|
||||||
Personality: data.WeaponType,
|
|
||||||
BodyType: "/Lotus/Types/Game/KubrowPet/BodyTypes/InfestedKubrowPetBodyType",
|
|
||||||
Head: "/Lotus/Types/Game/InfestedPredatorPet/Heads/InfestedPredatorHeadA"
|
|
||||||
},
|
|
||||||
"/Lotus/Types/Friendly/Pets/CreaturePets/PharaohPredatorKubrowPetPowerSuit": {
|
|
||||||
BaseColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorUncommonBase",
|
|
||||||
SecondaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorUncommonSecondary",
|
|
||||||
TertiaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorUncommonTertiary",
|
|
||||||
AccentColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorUncommonAccent",
|
|
||||||
EyeColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorUncommonEyes",
|
|
||||||
FurPattern: "/Lotus/Types/Game/InfestedPredatorPet/Patterns/InfestedPredatorPatternDefault",
|
|
||||||
Personality: data.WeaponType,
|
|
||||||
BodyType: "/Lotus/Types/Game/KubrowPet/BodyTypes/InfestedKubrowPetBodyType",
|
|
||||||
Head: "/Lotus/Types/Game/InfestedPredatorPet/Heads/InfestedPredatorHeadB"
|
|
||||||
},
|
|
||||||
"/Lotus/Types/Friendly/Pets/CreaturePets/VizierPredatorKubrowPetPowerSuit": {
|
|
||||||
BaseColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorCommonBase",
|
|
||||||
SecondaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorCommonSecondary",
|
|
||||||
TertiaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorCommonTertiary",
|
|
||||||
AccentColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorCommonAccent",
|
|
||||||
EyeColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorCommonEyes",
|
|
||||||
FurPattern: "/Lotus/Types/Game/InfestedPredatorPet/Patterns/InfestedPredatorPatternDefault",
|
|
||||||
Personality: data.WeaponType,
|
|
||||||
BodyType: "/Lotus/Types/Game/KubrowPet/BodyTypes/InfestedKubrowPetBodyType",
|
|
||||||
Head: "/Lotus/Types/Game/InfestedPredatorPet/Heads/InfestedPredatorHeadC"
|
|
||||||
},
|
|
||||||
"/Lotus/Types/Friendly/Pets/CreaturePets/VulpineInfestedCatbrowPetPowerSuit": {
|
|
||||||
BaseColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorCommonBase",
|
|
||||||
SecondaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorCommonSecondary",
|
|
||||||
TertiaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorCommonTertiary",
|
|
||||||
AccentColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorCommonAccent",
|
|
||||||
EyeColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorCommonEyes",
|
|
||||||
FurPattern: "/Lotus/Types/Game/InfestedKavatPet/Patterns/InfestedCritterPatternDefault",
|
|
||||||
Personality: data.WeaponType,
|
|
||||||
BodyType: "/Lotus/Types/Game/CatbrowPet/BodyTypes/InfestedCatbrowPetRegularBodyType",
|
|
||||||
Head: "/Lotus/Types/Game/InfestedKavatPet/Heads/InfestedCritterHeadA"
|
|
||||||
}
|
|
||||||
}[data.WeaponType];
|
|
||||||
|
|
||||||
if (!traits) {
|
|
||||||
throw new Error(`unknown KubrowPets type: ${data.WeaponType}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultOverwrites.Details = {
|
|
||||||
Name: "",
|
|
||||||
IsPuppy: false,
|
|
||||||
HasCollar: true,
|
|
||||||
PrintsRemaining: 2,
|
|
||||||
Status: Status.StatusStasis,
|
|
||||||
HatchDate: new Date(Math.trunc(Date.now() / 86400000) * 86400000),
|
|
||||||
IsMale: !!getRandomInt(0, 1),
|
|
||||||
Size: getRandomInt(70, 100) / 100,
|
|
||||||
DominantTraits: traits,
|
|
||||||
RecessiveTraits: traits
|
|
||||||
};
|
|
||||||
|
|
||||||
// Only save mutagen & antigen in the ModularParts.
|
|
||||||
defaultOverwrites.ModularParts = [data.Parts[1], data.Parts[2]];
|
|
||||||
|
|
||||||
const meta = ExportSentinels[data.WeaponType];
|
|
||||||
|
|
||||||
for (const specialItem of meta.exalted!) {
|
|
||||||
addSpecialItem(inventory, specialItem, inventoryChanges);
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultUpgrades = meta.defaultUpgrades;
|
|
||||||
} else {
|
|
||||||
defaultUpgrades = getDefaultUpgrades(data.Parts);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (category == "MoaPets") {
|
|
||||||
const weapon = ExportSentinels[data.WeaponType].defaultWeapon;
|
|
||||||
if (weapon) {
|
|
||||||
const category = ExportWeapons[weapon].productCategory;
|
|
||||||
addEquipment(inventory, category, weapon, undefined, inventoryChanges);
|
|
||||||
combineInventoryChanges(
|
|
||||||
inventoryChanges,
|
|
||||||
occupySlot(inventory, productCategoryToInventoryBin(category)!, !!data.isWebUi)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defaultOverwrites.Configs = applyDefaultUpgrades(inventory, defaultUpgrades);
|
|
||||||
addEquipment(inventory, category, data.WeaponType, defaultOverwrites, inventoryChanges);
|
|
||||||
combineInventoryChanges(
|
|
||||||
inventoryChanges,
|
|
||||||
occupySlot(inventory, productCategoryToInventoryBin(category)!, !!data.isWebUi)
|
|
||||||
);
|
|
||||||
if (defaultUpgrades) {
|
if (defaultUpgrades) {
|
||||||
inventoryChanges.RawUpgrades = defaultUpgrades.map(x => ({ ItemType: x.ItemType, ItemCount: 1 }));
|
inventoryChanges.RawUpgrades = defaultUpgrades.map(x => ({ ItemType: x.ItemType, ItemCount: 1 }));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove credits & parts
|
// Remove credits & parts
|
||||||
const miscItemChanges = [];
|
const miscItemChanges = [];
|
||||||
let currencyChanges = {};
|
for (const part of data.Parts) {
|
||||||
if (!data.isWebUi) {
|
miscItemChanges.push({
|
||||||
for (const part of data.Parts) {
|
ItemType: part,
|
||||||
miscItemChanges.push({
|
ItemCount: -1
|
||||||
ItemType: part,
|
});
|
||||||
ItemCount: -1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
currencyChanges = updateCurrency(
|
|
||||||
inventory,
|
|
||||||
category == "Hoverboards" ||
|
|
||||||
category == "MoaPets" ||
|
|
||||||
category == "LongGuns" ||
|
|
||||||
category == "Pistols" ||
|
|
||||||
category == "KubrowPets"
|
|
||||||
? 5000
|
|
||||||
: 4000, // Definitely correct for Melee & OperatorAmps
|
|
||||||
false
|
|
||||||
);
|
|
||||||
addMiscItems(inventory, miscItemChanges);
|
|
||||||
}
|
}
|
||||||
|
const currencyChanges = updateCurrency(
|
||||||
|
inventory,
|
||||||
|
category == "Hoverboards" || category == "MoaPets" || category == "LongGuns" || category == "Pistols"
|
||||||
|
? 5000
|
||||||
|
: 4000, // Definitely correct for Melee & OperatorAmps
|
||||||
|
false
|
||||||
|
);
|
||||||
|
addMiscItems(inventory, miscItemChanges);
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
|
|
||||||
// Tell client what we did
|
// Tell client what we did
|
||||||
res.json({
|
res.json({
|
||||||
InventoryChanges: {
|
InventoryChanges: {
|
||||||
|
@ -2,7 +2,7 @@ import { RequestHandler } from "express";
|
|||||||
import { ExportWeapons } from "warframe-public-export-plus";
|
import { ExportWeapons } from "warframe-public-export-plus";
|
||||||
import { IMongoDate } from "@/src/types/commonTypes";
|
import { IMongoDate } from "@/src/types/commonTypes";
|
||||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||||
import { SRng } from "@/src/services/rngService";
|
import { CRng } from "@/src/services/rngService";
|
||||||
import { ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import {
|
import {
|
||||||
@ -21,11 +21,8 @@ import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
|||||||
export const modularWeaponSaleController: RequestHandler = async (req, res) => {
|
export const modularWeaponSaleController: RequestHandler = async (req, res) => {
|
||||||
const partTypeToParts: Record<string, string[]> = {};
|
const partTypeToParts: Record<string, string[]> = {};
|
||||||
for (const [uniqueName, data] of Object.entries(ExportWeapons)) {
|
for (const [uniqueName, data] of Object.entries(ExportWeapons)) {
|
||||||
if (
|
if (data.partType) {
|
||||||
data.partType &&
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
data.premiumPrice &&
|
|
||||||
!data.excludeFromCodex // exclude pvp variants
|
|
||||||
) {
|
|
||||||
partTypeToParts[data.partType] ??= [];
|
partTypeToParts[data.partType] ??= [];
|
||||||
partTypeToParts[data.partType].push(uniqueName);
|
partTypeToParts[data.partType].push(uniqueName);
|
||||||
}
|
}
|
||||||
@ -45,18 +42,24 @@ export const modularWeaponSaleController: RequestHandler = async (req, res) => {
|
|||||||
const defaultUpgrades = getDefaultUpgrades(weaponInfo.ModularParts);
|
const defaultUpgrades = getDefaultUpgrades(weaponInfo.ModularParts);
|
||||||
const configs = applyDefaultUpgrades(inventory, defaultUpgrades);
|
const configs = applyDefaultUpgrades(inventory, defaultUpgrades);
|
||||||
const inventoryChanges: IInventoryChanges = {
|
const inventoryChanges: IInventoryChanges = {
|
||||||
...addEquipment(inventory, category, weaponInfo.ItemType, {
|
...addEquipment(
|
||||||
Features: EquipmentFeatures.DOUBLE_CAPACITY | EquipmentFeatures.GILDED,
|
inventory,
|
||||||
ItemName: payload.ItemName,
|
category,
|
||||||
Configs: configs,
|
weaponInfo.ItemType,
|
||||||
ModularParts: weaponInfo.ModularParts,
|
weaponInfo.ModularParts,
|
||||||
Polarity: [
|
{},
|
||||||
{
|
{
|
||||||
Slot: payload.PolarizeSlot,
|
Features: EquipmentFeatures.DOUBLE_CAPACITY | EquipmentFeatures.GILDED,
|
||||||
Value: payload.PolarizeValue
|
ItemName: payload.ItemName,
|
||||||
}
|
Configs: configs,
|
||||||
]
|
Polarity: [
|
||||||
}),
|
{
|
||||||
|
Slot: payload.PolarizeSlot,
|
||||||
|
Value: payload.PolarizeValue
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
),
|
||||||
...occupySlot(inventory, productCategoryToInventoryBin(category)!, true),
|
...occupySlot(inventory, productCategoryToInventoryBin(category)!, true),
|
||||||
...updateCurrency(inventory, weaponInfo.PremiumPrice, true)
|
...updateCurrency(inventory, weaponInfo.PremiumPrice, true)
|
||||||
};
|
};
|
||||||
@ -140,11 +143,15 @@ const getModularWeaponSale = (
|
|||||||
partTypes: string[],
|
partTypes: string[],
|
||||||
getItemType: (parts: string[]) => string
|
getItemType: (parts: string[]) => string
|
||||||
): IModularWeaponSaleInfo => {
|
): IModularWeaponSaleInfo => {
|
||||||
const rng = new SRng(day);
|
const rng = new CRng(day);
|
||||||
const parts = partTypes.map(partType => rng.randomElement(partTypeToParts[partType])!);
|
const parts = partTypes.map(partType => rng.randomElement(partTypeToParts[partType]));
|
||||||
let partsCost = 0;
|
let partsCost = 0;
|
||||||
for (const part of parts) {
|
for (const part of parts) {
|
||||||
partsCost += ExportWeapons[part].premiumPrice!;
|
const meta = ExportWeapons[part];
|
||||||
|
if (!meta.premiumPrice) {
|
||||||
|
throw new Error(`no premium price for ${part}`);
|
||||||
|
}
|
||||||
|
partsCost += meta.premiumPrice;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
Name: name,
|
Name: name,
|
||||||
|
@ -3,7 +3,6 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
|
|||||||
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { sendWsBroadcastTo } from "@/src/services/webService";
|
|
||||||
|
|
||||||
interface INameWeaponRequest {
|
interface INameWeaponRequest {
|
||||||
ItemName: string;
|
ItemName: string;
|
||||||
@ -13,20 +12,17 @@ export const nameWeaponController: RequestHandler = async (req, res) => {
|
|||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
const body = getJSONfromString<INameWeaponRequest>(String(req.body));
|
const body = getJSONfromString<INameWeaponRequest>(String(req.body));
|
||||||
const item = inventory[req.query.Category as string as TEquipmentKey].id(req.query.ItemId as string)!;
|
const item = inventory[req.query.Category as string as TEquipmentKey].find(
|
||||||
|
item => item._id.toString() == (req.query.ItemId as string)
|
||||||
|
)!;
|
||||||
if (body.ItemName != "") {
|
if (body.ItemName != "") {
|
||||||
item.ItemName = body.ItemName;
|
item.ItemName = body.ItemName;
|
||||||
} else {
|
} else {
|
||||||
item.ItemName = undefined;
|
item.ItemName = undefined;
|
||||||
}
|
}
|
||||||
const currencyChanges = updateCurrency(
|
const currencyChanges = updateCurrency(inventory, "webui" in req.query ? 0 : 15, true);
|
||||||
inventory,
|
|
||||||
req.query.Category == "Horses" || "webui" in req.query ? 0 : 15,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({
|
res.json({
|
||||||
InventoryChanges: currencyChanges
|
InventoryChanges: currencyChanges
|
||||||
});
|
});
|
||||||
sendWsBroadcastTo(accountId, { update_inventory: true });
|
|
||||||
};
|
};
|
||||||
|
@ -1,393 +0,0 @@
|
|||||||
import { version_compare } from "@/src/helpers/inventoryHelpers";
|
|
||||||
import {
|
|
||||||
antivirusMods,
|
|
||||||
consumeModCharge,
|
|
||||||
decodeNemesisGuess,
|
|
||||||
encodeNemesisGuess,
|
|
||||||
getInfNodes,
|
|
||||||
getKnifeUpgrade,
|
|
||||||
getNemesisManifest,
|
|
||||||
getNemesisPasscode,
|
|
||||||
GUESS_CORRECT,
|
|
||||||
GUESS_INCORRECT,
|
|
||||||
GUESS_NEUTRAL,
|
|
||||||
GUESS_NONE,
|
|
||||||
GUESS_WILDCARD,
|
|
||||||
IKnifeResponse,
|
|
||||||
parseUpgrade
|
|
||||||
} from "@/src/helpers/nemesisHelpers";
|
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|
||||||
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
|
|
||||||
import { freeUpSlot, getInventory } from "@/src/services/inventoryService";
|
|
||||||
import { 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 {
|
|
||||||
IInnateDamageFingerprint,
|
|
||||||
IInventoryClient,
|
|
||||||
INemesisClient,
|
|
||||||
InventorySlot,
|
|
||||||
IUpgradeClient,
|
|
||||||
IWeaponSkinClient,
|
|
||||||
LoadoutIndex,
|
|
||||||
TEquipmentKey,
|
|
||||||
TNemesisFaction
|
|
||||||
} from "@/src/types/inventoryTypes/inventoryTypes";
|
|
||||||
import { logger } from "@/src/utils/logger";
|
|
||||||
import { RequestHandler } from "express";
|
|
||||||
|
|
||||||
export const nemesisController: RequestHandler = async (req, res) => {
|
|
||||||
const account = await getAccountForRequest(req);
|
|
||||||
if ((req.query.mode as string) == "f") {
|
|
||||||
const body = getJSONfromString<IValenceFusionRequest>(String(req.body));
|
|
||||||
const inventory = await getInventory(account._id.toString(), body.Category + " WeaponBin");
|
|
||||||
const destWeapon = inventory[body.Category].id(body.DestWeapon.$oid)!;
|
|
||||||
const sourceWeapon = inventory[body.Category].id(body.SourceWeapon.$oid)!;
|
|
||||||
const destFingerprint = JSON.parse(destWeapon.UpgradeFingerprint!) as IInnateDamageFingerprint;
|
|
||||||
const sourceFingerprint = JSON.parse(sourceWeapon.UpgradeFingerprint!) as IInnateDamageFingerprint;
|
|
||||||
|
|
||||||
// Update destination damage type if desired
|
|
||||||
if (body.UseSourceDmgType) {
|
|
||||||
destFingerprint.buffs[0].Tag = sourceFingerprint.buffs[0].Tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upgrade destination damage value
|
|
||||||
const destDamage = 0.25 + (destFingerprint.buffs[0].Value / 0x3fffffff) * (0.6 - 0.25);
|
|
||||||
const sourceDamage = 0.25 + (sourceFingerprint.buffs[0].Value / 0x3fffffff) * (0.6 - 0.25);
|
|
||||||
let newDamage = Math.max(destDamage, sourceDamage) * 1.1;
|
|
||||||
if (newDamage >= 0.5794998) {
|
|
||||||
newDamage = 0.6;
|
|
||||||
}
|
|
||||||
destFingerprint.buffs[0].Value = Math.trunc(((newDamage - 0.25) / (0.6 - 0.25)) * 0x3fffffff);
|
|
||||||
|
|
||||||
// Commit fingerprint
|
|
||||||
destWeapon.UpgradeFingerprint = JSON.stringify(destFingerprint);
|
|
||||||
|
|
||||||
// Remove source weapon
|
|
||||||
inventory[body.Category].pull({ _id: body.SourceWeapon.$oid });
|
|
||||||
freeUpSlot(inventory, InventorySlot.WEAPONS);
|
|
||||||
|
|
||||||
await inventory.save();
|
|
||||||
res.json({
|
|
||||||
InventoryChanges: {
|
|
||||||
[body.Category]: [destWeapon.toJSON()],
|
|
||||||
RemovedIdItems: [{ ItemId: body.SourceWeapon }]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if ((req.query.mode as string) == "p") {
|
|
||||||
const inventory = await getInventory(account._id.toString(), "Nemesis");
|
|
||||||
const body = getJSONfromString<INemesisPrespawnCheckRequest>(String(req.body));
|
|
||||||
const passcode = getNemesisPasscode(inventory.Nemesis!);
|
|
||||||
let guessResult = 0;
|
|
||||||
if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
|
|
||||||
for (let i = 0; i != 3; ++i) {
|
|
||||||
if (body.guess[i] == passcode[0]) {
|
|
||||||
guessResult = 1 + i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (let i = 0; i != 3; ++i) {
|
|
||||||
if (body.guess[i] == passcode[i] || body.guess[i] == GUESS_WILDCARD) {
|
|
||||||
++guessResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res.json({ GuessResult: guessResult });
|
|
||||||
} else if (req.query.mode == "r") {
|
|
||||||
const inventory = await getInventory(
|
|
||||||
account._id.toString(),
|
|
||||||
"Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades"
|
|
||||||
);
|
|
||||||
const body = getJSONfromString<INemesisRequiemRequest>(String(req.body));
|
|
||||||
if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
|
|
||||||
const guess: number[] = [body.guess & 0xf, (body.guess >> 4) & 0xf, (body.guess >> 8) & 0xf];
|
|
||||||
const passcode = getNemesisPasscode(inventory.Nemesis!)[0];
|
|
||||||
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([
|
|
||||||
{
|
|
||||||
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 == 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);
|
|
||||||
const dataknifeConfigIndex = dataknifeLoadout?.s?.mod ?? 0;
|
|
||||||
const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex].Upgrades!;
|
|
||||||
for (const upgrade of body.knife!.AttachedUpgrades) {
|
|
||||||
switch (upgrade.ItemType) {
|
|
||||||
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndSpeedOnUseMod":
|
|
||||||
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
|
|
||||||
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusOnUseMod": // Immuno Shield
|
|
||||||
antivirusGain += 15;
|
|
||||||
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!.InfNodes = getInfNodes(getNemesisManifest(inventory.Nemesis!.manifest), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
await inventory.save();
|
|
||||||
res.json(response);
|
|
||||||
} else {
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
const inventory = await getInventory(account._id.toString(), "Nemesis");
|
|
||||||
inventory.Nemesis!.LastEnc = inventory.Nemesis!.MissionCount;
|
|
||||||
await inventory.save();
|
|
||||||
res.json({ LastEnc: inventory.Nemesis!.LastEnc });
|
|
||||||
} else if ((req.query.mode as string) == "s") {
|
|
||||||
const inventory = await getInventory(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(account.BuildLabel, manifest.minBuild) < 0) {
|
|
||||||
logger.warn(
|
|
||||||
`client on version ${account.BuildLabel} provided nemesis manifest ${body.target.manifest} which was expected to require ${manifest.minBuild} or above. please file a bug report.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let weaponIdx = -1;
|
|
||||||
if (body.target.Faction != "FC_INFESTATION") {
|
|
||||||
const weapons: readonly string[] = manifest.weapons;
|
|
||||||
const initialWeaponIdx = new SRng(body.target.fp).randomInt(0, weapons.length - 1);
|
|
||||||
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 = {
|
|
||||||
fp: body.target.fp,
|
|
||||||
manifest: body.target.manifest,
|
|
||||||
KillingSuit: body.target.KillingSuit,
|
|
||||||
killingDamageType: body.target.killingDamageType,
|
|
||||||
ShoulderHelmet: body.target.ShoulderHelmet,
|
|
||||||
WeaponIdx: weaponIdx,
|
|
||||||
AgentIdx: body.target.AgentIdx,
|
|
||||||
BirthNode: body.target.BirthNode,
|
|
||||||
Faction: body.target.Faction,
|
|
||||||
Rank: 0,
|
|
||||||
k: false,
|
|
||||||
Traded: false,
|
|
||||||
d: new Date(),
|
|
||||||
InfNodes: getInfNodes(manifest, 0),
|
|
||||||
GuessHistory: [],
|
|
||||||
Hints: [],
|
|
||||||
HintProgress: 0,
|
|
||||||
Weakened: false,
|
|
||||||
PrevOwners: 0,
|
|
||||||
HenchmenKilled: 0,
|
|
||||||
SecondInCommand: false,
|
|
||||||
MissionCount: 0,
|
|
||||||
LastEnc: 0
|
|
||||||
};
|
|
||||||
await inventory.save();
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
target: inventory.toJSON().Nemesis
|
|
||||||
});
|
|
||||||
} else if ((req.query.mode as string) == "w") {
|
|
||||||
const inventory = await getInventory(account._id.toString(), "Nemesis");
|
|
||||||
//const body = getJSONfromString<INemesisWeakenRequest>(String(req.body));
|
|
||||||
|
|
||||||
// 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: INemesisWeakenResponse = {
|
|
||||||
target: inventory.toJSON<IInventoryClient>().Nemesis!
|
|
||||||
};
|
|
||||||
res.json(response);
|
|
||||||
} else {
|
|
||||||
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
|
||||||
throw new Error(`unknown nemesis mode: ${String(req.query.mode)}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IValenceFusionRequest {
|
|
||||||
DestWeapon: IOid;
|
|
||||||
SourceWeapon: IOid;
|
|
||||||
Category: TEquipmentKey;
|
|
||||||
UseSourceDmgType: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface INemesisStartRequest {
|
|
||||||
target: {
|
|
||||||
fp: number | bigint;
|
|
||||||
manifest: string;
|
|
||||||
KillingSuit: string;
|
|
||||||
killingDamageType: number;
|
|
||||||
ShoulderHelmet: string;
|
|
||||||
DisallowedWeapons?: string[];
|
|
||||||
WeaponIdx: number;
|
|
||||||
AgentIdx: number;
|
|
||||||
BirthNode: string;
|
|
||||||
Faction: TNemesisFaction;
|
|
||||||
Rank: number;
|
|
||||||
k: boolean;
|
|
||||||
Traded: boolean;
|
|
||||||
d: IMongoDate;
|
|
||||||
InfNodes: [];
|
|
||||||
GuessHistory: [];
|
|
||||||
Hints: [];
|
|
||||||
HintProgress: number;
|
|
||||||
Weakened: boolean;
|
|
||||||
PrevOwners: number;
|
|
||||||
HenchmenKilled: number;
|
|
||||||
MissionCount?: number; // Added in 38.5.0
|
|
||||||
LastEnc?: number; // Added in 38.5.0
|
|
||||||
SecondInCommand: boolean;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface INemesisPrespawnCheckRequest {
|
|
||||||
guess: number[]; // .length == 3
|
|
||||||
potency?: number[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface INemesisRequiemRequest {
|
|
||||||
guess: number; // grn/crp: 4 bits | coda: 3x 4 bits
|
|
||||||
position: number; // grn/crp: 0-2 | coda: 0
|
|
||||||
// knife field provided for coda only
|
|
||||||
knife?: IKnife;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface INemesisRequiemResponse extends IKnifeResponse {
|
|
||||||
RankIncrease?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// interface INemesisWeakenRequest {
|
|
||||||
// target: INemesisClient;
|
|
||||||
// knife: IKnife;
|
|
||||||
// }
|
|
||||||
|
|
||||||
interface INemesisWeakenResponse extends IKnifeResponse {
|
|
||||||
target: INemesisClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IKnife {
|
|
||||||
Item: IEquipmentClient;
|
|
||||||
Skins: IWeaponSkinClient[];
|
|
||||||
ModSlot: number;
|
|
||||||
CustSlot: number;
|
|
||||||
AttachedUpgrades: IUpgradeClient[];
|
|
||||||
HiddenWhenHolstered: boolean;
|
|
||||||
}
|
|
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