Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| af258d1c7a |
@ -3,6 +3,3 @@
|
||||
Dockerfile*
|
||||
.*
|
||||
docker-data/
|
||||
node_modules/
|
||||
static/data/
|
||||
logs/
|
||||
|
||||
43
.eslintrc
43
.eslintrc
@ -1,48 +1,37 @@
|
||||
{
|
||||
"plugins": ["@typescript-eslint", "prettier", "import"],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
||||
"plugin:import/recommended",
|
||||
"plugin:import/typescript"
|
||||
"plugin:@typescript-eslint/recommended-requiring-type-checking"
|
||||
],
|
||||
"plugins": ["@typescript-eslint", "prettier"],
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"rules": {
|
||||
"@typescript-eslint/consistent-type-imports": "error",
|
||||
"@typescript-eslint/explicit-function-return-type": "error",
|
||||
"@typescript-eslint/restrict-template-expressions": "error",
|
||||
"@typescript-eslint/restrict-plus-operands": "error",
|
||||
"@typescript-eslint/no-unsafe-member-access": "error",
|
||||
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "caughtErrors": "none" }],
|
||||
"@typescript-eslint/explicit-function-return-type": "warn",
|
||||
"@typescript-eslint/restrict-template-expressions": "warn",
|
||||
"@typescript-eslint/restrict-plus-operands": "warn",
|
||||
"@typescript-eslint/no-unsafe-member-access": "warn",
|
||||
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
|
||||
"@typescript-eslint/no-misused-promises": "warn",
|
||||
"@typescript-eslint/no-unsafe-argument": "error",
|
||||
"@typescript-eslint/no-unsafe-call": "error",
|
||||
"@typescript-eslint/no-unsafe-assignment": "error",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"no-loss-of-precision": "error",
|
||||
"@typescript-eslint/no-unnecessary-condition": "error",
|
||||
"@typescript-eslint/no-base-to-string": "off",
|
||||
"@typescript-eslint/no-unsafe-call": "warn",
|
||||
"@typescript-eslint/no-unsafe-assignment": "warn",
|
||||
"@typescript-eslint/no-explicit-any": "warn",
|
||||
"@typescript-eslint/no-loss-of-precision": "warn",
|
||||
"@typescript-eslint/no-unnecessary-condition": "warn",
|
||||
"no-case-declarations": "error",
|
||||
"prettier/prettier": "error",
|
||||
"@typescript-eslint/semi": "error",
|
||||
"no-mixed-spaces-and-tabs": "error",
|
||||
"@typescript-eslint/require-await": "error",
|
||||
"import/no-named-as-default-member": "off",
|
||||
"import/no-cycle": "warn",
|
||||
"@typescript-eslint/no-deprecated": "warn"
|
||||
"require-await": "off",
|
||||
"@typescript-eslint/require-await": "error"
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"settings": {
|
||||
"import/extensions": [ ".ts" ],
|
||||
"import/resolver": {
|
||||
"typescript": true,
|
||||
"node": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
.gitattributes
vendored
2
.gitattributes
vendored
@ -1,4 +1,4 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto eol=lf
|
||||
* text=auto
|
||||
|
||||
static/webui/libs/ linguist-vendored
|
||||
|
||||
24
.github/workflows/build.yml
vendored
24
.github/workflows/build.yml
vendored
@ -1,29 +1,21 @@
|
||||
name: Build
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
push: {}
|
||||
pull_request: {}
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
version: [18, 20, 22]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.1.2
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
node-version: ">=20.18.1"
|
||||
node-version: ${{ matrix.version }}
|
||||
- run: npm ci
|
||||
- run: cp config-vanilla.json config.json
|
||||
- run: npm run verify
|
||||
- run: npm run lint:ci
|
||||
- run: npm run prettier
|
||||
- run: npm run update-translations
|
||||
- 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
|
||||
- run: cp config.json.example config.json
|
||||
- run: npm run build
|
||||
- run: npm run lint
|
||||
|
||||
4
.github/workflows/docker.yml
vendored
4
.github/workflows/docker.yml
vendored
@ -18,10 +18,8 @@ jobs:
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
platforms: linux/arm64,linux/amd64
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
openwf/spaceninjaserver:latest
|
||||
openwf/spaceninjaserver:latest-arm64
|
||||
openwf/spaceninjaserver:${{ github.sha }}
|
||||
openwf/spaceninjaserver:${{ github.sha }}-arm64
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
src/routes/api.ts
|
||||
static/webui/libs/
|
||||
*.html
|
||||
*.md
|
||||
config-vanilla.json
|
||||
|
||||
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",
|
||||
"request": "launch",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -1,3 +0,0 @@
|
||||
{
|
||||
"typescript.preferences.preferTypeOnlyAutoImports": true
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
## In General
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Use `npm i` or `npm ci` to install all dependencies, including dev dependencies.
|
||||
|
||||
## Development Process
|
||||
|
||||
Auto reloading is supported for server and WebUI development. Simply use `npm run dev` or `npm run dev:bun` to start the server and edit away.
|
||||
|
||||
### Testing
|
||||
|
||||
Before submitting a PR:
|
||||
- Use `npm run verify` to verify that the code is type-safe.
|
||||
- Use `npm run fix` to fix formatting issues as well as be informed of any unfixable issues. Avoid introducing new warnings.
|
||||
|
||||
## 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 provide translations generated by AI or other automated tools.
|
||||
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
|
||||
WORKDIR /app
|
||||
|
||||
RUN npm i --omit=dev --omit=optional
|
||||
RUN date '+%d %B %Y' > BUILD_DATE
|
||||
|
||||
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
||||
|
||||
31
README.md
31
README.md
@ -6,36 +6,9 @@ More information for the moment here: [https://discord.gg/PNNZ3asUuY](https://di
|
||||
|
||||
This project is in active development at <https://onlyg.it/OpenWF/SpaceNinjaServer>.
|
||||
|
||||
To get an idea of what functionality you can expect to be missing [have a look through the issues](https://onlyg.it/OpenWF/SpaceNinjaServer/issues). However, many things have been implemented and *should* work as expected. Please open an issue for anything where that's not the case and/or the server is reporting errors.
|
||||
To get an idea of what functionality you can expect to be missing [have a look through the issues](https://onlyg.it/OpenWF/SpaceNinjaServer/issues?q=&type=all&state=open&labels=-4%2C-10&milestone=0&assignee=0&poster=). However, many things have been implemented and *should* work as expected. Please open an issue for anything where that's not the case and/or the server is reporting errors.
|
||||
|
||||
## config.json
|
||||
|
||||
SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [config-vanilla.json](config-vanilla.json), which has most cheats disabled.
|
||||
|
||||
- `skipTutorial` affects only newly created accounts, so you may wish to change it before logging in for the first time.
|
||||
- `logger.level` can be `fatal`, `error`, `warn`, `info`, `http`, `debug`, or `trace`.
|
||||
- `ircExecutable` can be provided with a relative path to an EXE which will be ran as a child process of SpaceNinjaServer.
|
||||
- `ircAddress`, `hubAddress`, and `nrsAddress` can be provided if these secondary servers are on a different machine.
|
||||
- `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:
|
||||
- `RadioLegionIntermission14Syndicate` for Nora's Mix: Dreams of the Dead
|
||||
- `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
|
||||
- `worldState.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`.
|
||||
- `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`.
|
||||
|
||||
@ -1,33 +1,25 @@
|
||||
@echo off
|
||||
|
||||
echo Updating SpaceNinjaServer...
|
||||
git config remote.origin.url https://openwf.io/SpaceNinjaServer.git
|
||||
git fetch --prune
|
||||
if %errorlevel% == 0 (
|
||||
git stash
|
||||
git checkout -f origin/main
|
||||
git reset --hard origin/main
|
||||
|
||||
if exist static\data\0\ (
|
||||
echo Updating stripped assets...
|
||||
cd static\data\0\
|
||||
git pull
|
||||
cd ..\..\..\
|
||||
)
|
||||
|
||||
echo Updating dependencies...
|
||||
node scripts/raw-precheck.js > NUL
|
||||
if %errorlevel% == 0 (
|
||||
call npm i --omit=dev --omit=optional
|
||||
call npm run raw
|
||||
) else (
|
||||
call npm i --omit=dev
|
||||
call npm run build
|
||||
if %errorlevel% == 0 (
|
||||
call npm run start
|
||||
)
|
||||
)
|
||||
echo SpaceNinjaServer seems to have crashed.
|
||||
if exist static\data\0\ (
|
||||
echo Updating stripped assets...
|
||||
cd static\data\0\
|
||||
git pull
|
||||
cd ..\..\..\
|
||||
)
|
||||
|
||||
echo Updating dependencies...
|
||||
call npm i --omit=dev
|
||||
|
||||
call npm run build
|
||||
if %errorlevel% == 0 (
|
||||
call npm run start
|
||||
echo SpaceNinjaServer seems to have crashed.
|
||||
)
|
||||
:a
|
||||
pause > nul
|
||||
goto a
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "Updating SpaceNinjaServer..."
|
||||
git fetch --prune
|
||||
if [ $? -eq 0 ]; then
|
||||
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..."
|
||||
node scripts/raw-precheck.js > /dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
npm i --omit=dev --omit=optional
|
||||
npm run raw
|
||||
else
|
||||
npm i --omit=dev
|
||||
npm run build
|
||||
if [ $? -eq 0 ]; then
|
||||
npm run start
|
||||
fi
|
||||
fi
|
||||
echo "SpaceNinjaServer seems to have crashed."
|
||||
fi
|
||||
@ -1,74 +0,0 @@
|
||||
{
|
||||
"mongodbUrl": "mongodb://127.0.0.1:27017/openWF",
|
||||
"logger": {
|
||||
"files": true,
|
||||
"level": "trace"
|
||||
},
|
||||
"myAddress": "localhost",
|
||||
"bindAddress": "0.0.0.0",
|
||||
"httpPort": 80,
|
||||
"httpsPort": 443,
|
||||
"ircExecutable": null,
|
||||
"ircAddress": null,
|
||||
"hubAddress": null,
|
||||
"nrsAddress": null,
|
||||
"administratorNames": [],
|
||||
"autoCreateAccount": true,
|
||||
"skipTutorial": false,
|
||||
"unlockAllSkins": false,
|
||||
"fullyStockedVendors": false,
|
||||
"skipClanKeyCrafting": false,
|
||||
"unfaithfulBugFixes": {
|
||||
"ignore1999LastRegionPlayed": false,
|
||||
"fixXtraCheeseTimer": false,
|
||||
"useAnniversaryTagForOldGoals": true
|
||||
},
|
||||
"worldState": {
|
||||
"creditBoost": false,
|
||||
"affinityBoost": false,
|
||||
"resourceBoost": false,
|
||||
"tennoLiveRelay": false,
|
||||
"baroTennoConRelay": false,
|
||||
"baroAlwaysAvailable": false,
|
||||
"baroFullyStocked": false,
|
||||
"varziaFullyStocked": false,
|
||||
"wolfHunt": null,
|
||||
"orphixVenom": false,
|
||||
"longShadow": false,
|
||||
"hallowedFlame": false,
|
||||
"anniversary": null,
|
||||
"hallowedNightmares": false,
|
||||
"hallowedNightmaresRewardsOverride": 0,
|
||||
"naberusNightsOverride": null,
|
||||
"proxyRebellion": false,
|
||||
"proxyRebellionRewardsOverride": 0,
|
||||
"voidCorruption2025Week1": false,
|
||||
"voidCorruption2025Week2": false,
|
||||
"voidCorruption2025Week3": false,
|
||||
"voidCorruption2025Week4": false,
|
||||
"qtccAlerts": false,
|
||||
"galleonOfGhouls": 0,
|
||||
"ghoulEmergenceOverride": null,
|
||||
"plagueStarOverride": null,
|
||||
"starDaysOverride": null,
|
||||
"dogDaysOverride": null,
|
||||
"dogDaysRewardsOverride": null,
|
||||
"bellyOfTheBeast": false,
|
||||
"bellyOfTheBeastProgressOverride": 0,
|
||||
"eightClaw": false,
|
||||
"eightClawProgressOverride": 0,
|
||||
"thermiaFracturesOverride": null,
|
||||
"thermiaFracturesProgressOverride": 0,
|
||||
"eidolonOverride": "",
|
||||
"vallisOverride": "",
|
||||
"duviriOverride": "",
|
||||
"nightwaveOverride": "",
|
||||
"allTheFissures": "",
|
||||
"varziaOverride": "",
|
||||
"circuitGameModes": null,
|
||||
"darvoStockMultiplier": 1
|
||||
},
|
||||
"dev": {
|
||||
"keepVendorsExpired": false
|
||||
}
|
||||
}
|
||||
47
config.json.example
Normal file
47
config.json.example
Normal file
@ -0,0 +1,47 @@
|
||||
{
|
||||
"mongodbUrl": "mongodb://127.0.0.1:27017/openWF",
|
||||
"logger": {
|
||||
"files": true,
|
||||
"level": "trace"
|
||||
},
|
||||
"myAddress": "localhost",
|
||||
"httpPort": 80,
|
||||
"httpsPort": 443,
|
||||
"NRS": ["localhost"],
|
||||
"administratorNames": [],
|
||||
"autoCreateAccount": true,
|
||||
"skipTutorial": false,
|
||||
"skipAllDialogue": false,
|
||||
"unlockAllScans": false,
|
||||
"unlockAllMissions": false,
|
||||
"infiniteCredits": false,
|
||||
"infinitePlatinum": false,
|
||||
"infiniteEndo": false,
|
||||
"infiniteRegalAya": false,
|
||||
"infiniteHelminthMaterials": false,
|
||||
"unlockAllShipFeatures": false,
|
||||
"unlockAllShipDecorations": false,
|
||||
"unlockAllFlavourItems": false,
|
||||
"unlockAllSkins": false,
|
||||
"unlockAllCapturaScenes": false,
|
||||
"universalPolarityEverywhere": false,
|
||||
"unlockDoubleCapacityPotatoesEverywhere": false,
|
||||
"unlockExilusEverywhere": false,
|
||||
"unlockArcanesEverywhere": false,
|
||||
"noDailyStandingLimits": false,
|
||||
"noArgonCrystalDecay": false,
|
||||
"noVendorPurchaseLimits": true,
|
||||
"instantResourceExtractorDrones": false,
|
||||
"noDojoRoomBuildStage": false,
|
||||
"fastDojoRoomDestruction": false,
|
||||
"noDojoResearchCosts": false,
|
||||
"noDojoResearchTime": false,
|
||||
"fastClanAscension": false,
|
||||
"spoofMasteryRank": -1,
|
||||
"events": {
|
||||
"creditBoost": false,
|
||||
"affinityBoost": false,
|
||||
"resourceBoost": false,
|
||||
"starDays": true
|
||||
}
|
||||
}
|
||||
@ -1,23 +1,43 @@
|
||||
services:
|
||||
spaceninjaserver:
|
||||
# build: .
|
||||
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:
|
||||
- ./docker-data/conf:/app/conf
|
||||
- ./docker-data/static-data:/app/static/data
|
||||
- ./docker-data/static:/app/static/data
|
||||
- ./docker-data/logs:/app/logs
|
||||
ports:
|
||||
- 80:80
|
||||
- 443:443
|
||||
|
||||
# Normally, the image is fetched from Docker Hub, but you can use the local Dockerfile by removing "image" above and adding this:
|
||||
#build: .
|
||||
# Works best when using `docker-compose up --force-recreate --build`.
|
||||
|
||||
depends_on:
|
||||
- mongodb
|
||||
mongodb:
|
||||
image: docker.io/library/mongo:8.0.0-noble
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: openwfagent
|
||||
MONGO_INITDB_ROOT_PASSWORD: spaceninjaserver
|
||||
volumes:
|
||||
- ./docker-data/database:/data/db
|
||||
command: mongod --quiet --logpath /dev/null
|
||||
|
||||
@ -1,8 +1,23 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [ ! -f conf/config.json ]; then
|
||||
jq --arg value "mongodb://mongodb:27017/openWF" '.mongodbUrl = $value' /app/config-vanilla.json > /app/conf/config.json
|
||||
fi
|
||||
# Set up the configuration file using environment variables.
|
||||
echo '{
|
||||
"logger": {
|
||||
"files": true,
|
||||
"level": "trace",
|
||||
"__valid_levels": "fatal, error, warn, info, http, debug, trace"
|
||||
}
|
||||
}
|
||||
' > config.json
|
||||
|
||||
exec npm run raw -- --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"
|
||||
};
|
||||
3790
package-lock.json
generated
3790
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
69
package.json
69
package.json
@ -4,63 +4,40 @@
|
||||
"description": "WF Emulator",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"start": "node --enable-source-maps build/src/index.js",
|
||||
"build": "tsgo --inlineSourceMap && ncp static/webui build/static/webui",
|
||||
"build:tsc": "tsc --incremental --inlineSourceMap && ncp static/webui build/static/webui",
|
||||
"build:dev": "tsgo --inlineSourceMap",
|
||||
"build:dev:tsc": "tsc --incremental --inlineSourceMap",
|
||||
"build-and-start": "npm run build && npm run start",
|
||||
"dev": "node scripts/dev.cjs",
|
||||
"dev:bun": "bun scripts/dev.cjs",
|
||||
"verify": "tsgo --noEmit",
|
||||
"verify:tsc": "tsc --noEmit",
|
||||
"raw": "node scripts/raw-precheck.js && node --experimental-transform-types src/index.ts",
|
||||
"raw:bun": "bun src/index.ts",
|
||||
"start": "node --import ./build/src/pathman.js build/src/index.js",
|
||||
"dev": "ts-node-dev --openssl-legacy-provider -r tsconfig-paths/register src/index.ts ",
|
||||
"build": "tsc && copyfiles static/webui/** build",
|
||||
"lint": "eslint --ext .ts .",
|
||||
"lint:ci": "eslint --ext .ts --rule \"prettier/prettier: off\" .",
|
||||
"lint:fix": "eslint --fix --ext .ts .",
|
||||
"prettier": "prettier --write .",
|
||||
"update-translations": "cd scripts && node update-translations.cjs",
|
||||
"fix": "npm run update-translations && npm run prettier"
|
||||
"update-translations": "cd scripts && node update-translations.js"
|
||||
},
|
||||
"license": "GNU",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"body-parser": "^2.2.0",
|
||||
"chokidar": "^4.0.3",
|
||||
"crc-32": "^1.2.2",
|
||||
"express": "^5",
|
||||
"json-with-bigint": "^3.4.4",
|
||||
"mongoose": "^8.11.0",
|
||||
"morgan": "^1.10.0",
|
||||
"ncp": "^2.0.0",
|
||||
"undici": "^7.10.0",
|
||||
"warframe-public-export-plus": "^0.5.93",
|
||||
"warframe-riven-info": "^0.1.2",
|
||||
"winston": "^3.17.0",
|
||||
"winston-daily-rotate-file": "^5.0.0",
|
||||
"ws": "^8.18.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@types/body-parser": "^1.19.6",
|
||||
"@types/express": "^5",
|
||||
"@types/morgan": "^1.9.9",
|
||||
"@types/websocket": "^1.0.10",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@typescript/native-preview": "^7.0.0-dev.20250625.1",
|
||||
"typescript": "^5.7"
|
||||
"copyfiles": "^2.4.1",
|
||||
"crc-32": "^1.2.2",
|
||||
"express": "^5",
|
||||
"mongoose": "^8.11.0",
|
||||
"morgan": "^1.10.0",
|
||||
"typescript": ">=5.5 <5.6.0",
|
||||
"warframe-public-export-plus": "^0.5.46",
|
||||
"warframe-riven-info": "^0.1.2",
|
||||
"winston": "^3.17.0",
|
||||
"winston-daily-rotate-file": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^8.28.0",
|
||||
"@typescript-eslint/parser": "^8.28.0",
|
||||
"eslint": "^8",
|
||||
"eslint-import-resolver-typescript": "^4.4.4",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-prettier": "^5.2.5",
|
||||
"prettier": "^3.5.3",
|
||||
"tree-kill": "^1.2.2"
|
||||
"@typescript-eslint/eslint-plugin": "^7.18",
|
||||
"@typescript-eslint/parser": "^7.18",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-prettier": "^5.2.3",
|
||||
"prettier": "^3.4.2",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"tsconfig-paths": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.18.1"
|
||||
"node": ">=18.15.0",
|
||||
"npm": ">=9.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,81 +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);
|
||||
|
||||
const cangoraw = (() => {
|
||||
if (process.versions.bun) {
|
||||
return true;
|
||||
}
|
||||
const [major, minor] = process.versions.node.split(".").map(x => parseInt(x));
|
||||
if (major > 22 || (major == 22 && minor >= 7)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})();
|
||||
|
||||
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(
|
||||
[process.versions.bun ? "bun" : "npm", "run", cangoraw ? "verify" : "build:dev"].join(" "),
|
||||
spawnopts
|
||||
);
|
||||
const thisbuildstart = Date.now();
|
||||
buildproc = thisbuildproc;
|
||||
buildproc.on("exit", code => {
|
||||
if (buildproc !== thisbuildproc) {
|
||||
return;
|
||||
}
|
||||
buildproc = undefined;
|
||||
if (code === 0) {
|
||||
console.log(`${cangoraw ? "Verified" : "Built"} in ${Date.now() - thisbuildstart} ms`);
|
||||
runproc = spawn(
|
||||
[
|
||||
process.versions.bun ? "bun" : "npm",
|
||||
"run",
|
||||
cangoraw ? (process.versions.bun ? "raw:bun" : "raw") : "start",
|
||||
"--",
|
||||
...args
|
||||
].join(" "),
|
||||
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,9 +0,0 @@
|
||||
const [major, minor] = process.versions.node.split(".").map(x => parseInt(x));
|
||||
if (major > 22 || (major == 22 && minor >= 7)) {
|
||||
// ok
|
||||
} else {
|
||||
console.log("Sorry, your Node version is a bit too old for this. You have 2 options:");
|
||||
console.log("- Update Node.js.");
|
||||
console.log("- Use 'npm run build && npm run start'. Optional libraries must be installed for this.");
|
||||
process.exit(1);
|
||||
}
|
||||
@ -1,11 +1,10 @@
|
||||
// Based on https://onlyg.it/OpenWF/Translations/src/branch/main/update.php
|
||||
// Converted via ChatGPT-4o
|
||||
|
||||
/* eslint-disable */
|
||||
const fs = require("fs");
|
||||
|
||||
function extractStrings(content) {
|
||||
const regex = /([a-zA-Z0-9_]+): `([^`]*)`,/g;
|
||||
const regex = /([a-zA-Z_]+): `([^`]*)`,/g;
|
||||
let matches;
|
||||
const strings = {};
|
||||
while ((matches = regex.exec(content)) !== null) {
|
||||
@ -16,7 +15,7 @@ function extractStrings(content) {
|
||||
|
||||
const source = fs.readFileSync("../static/webui/translations/en.js", "utf8");
|
||||
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 => {
|
||||
if (fs.lstatSync(`../static/webui/translations/${file}`).isFile() && file !== "en.js") {
|
||||
@ -31,13 +30,13 @@ fs.readdirSync("../static/webui/translations").forEach(file => {
|
||||
const strings = extractStrings(line);
|
||||
if (Object.keys(strings).length > 0) {
|
||||
Object.entries(strings).forEach(([key, value]) => {
|
||||
if (targetStrings.hasOwnProperty(key) && !targetStrings[key].startsWith("[UNTRANSLATED]")) {
|
||||
if (targetStrings.hasOwnProperty(key)) {
|
||||
fs.writeSync(fileHandle, ` ${key}: \`${targetStrings[key]}\`,\n`);
|
||||
} else {
|
||||
fs.writeSync(fileHandle, ` ${key}: \`[UNTRANSLATED] ${value}\`,\n`);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
} else if (line.length) {
|
||||
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;
|
||||
};
|
||||
39
src/app.ts
39
src/app.ts
@ -1,45 +1,28 @@
|
||||
import express from "express";
|
||||
|
||||
import bodyParser from "body-parser";
|
||||
import { unknownEndpointHandler } from "./middleware/middleware.ts";
|
||||
import { requestLogger } from "./middleware/morgenMiddleware.ts";
|
||||
import { errorHandler } from "./middleware/errorHandler.ts";
|
||||
import { unknownEndpointHandler } from "@/src/middleware/middleware";
|
||||
import { requestLogger } from "@/src/middleware/morgenMiddleware";
|
||||
import { errorHandler } from "@/src/middleware/errorHandler";
|
||||
|
||||
import { apiRouter } from "./routes/api.ts";
|
||||
import { cacheRouter } from "./routes/cache.ts";
|
||||
import { customRouter } from "./routes/custom.ts";
|
||||
import { dynamicController } from "./routes/dynamic.ts";
|
||||
import { payRouter } from "./routes/pay.ts";
|
||||
import { statsRouter } from "./routes/stats.ts";
|
||||
import { webuiRouter } from "./routes/webui.ts";
|
||||
import { apiRouter } from "@/src/routes/api";
|
||||
import { cacheRouter } from "@/src/routes/cache";
|
||||
import { customRouter } from "@/src/routes/custom";
|
||||
import { dynamicController } from "@/src/routes/dynamic";
|
||||
import { payRouter } from "@/src/routes/pay";
|
||||
import { statsRouter } from "@/src/routes/stats";
|
||||
import { webuiRouter } from "@/src/routes/webui";
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use((req, _res, next) => {
|
||||
// 38.5.0 introduced "ezip" for encrypted body blobs and "e" for request verification only (encrypted body blobs with no application data).
|
||||
// The client patch is expected to decrypt 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(express.json({ limit: "4mb" }));
|
||||
app.use(bodyParser.text({ limit: "4mb" }));
|
||||
app.use(bodyParser.text());
|
||||
app.use(requestLogger);
|
||||
|
||||
app.use("/api", apiRouter);
|
||||
app.use("/", cacheRouter);
|
||||
app.use("/custom", customRouter);
|
||||
app.use("/dynamic", dynamicController);
|
||||
app.use("/:id/dynamic", dynamicController);
|
||||
app.use("/pay", payRouter);
|
||||
app.use("/stats", statsRouter);
|
||||
|
||||
@ -1,21 +1,16 @@
|
||||
export const EPOCH = 1734307200_000; // Monday, Dec 16, 2024 @ 00:00 UTC+0; should logically be the start of winter in 1999 iteration 0
|
||||
|
||||
const millisecondsPerSecond = 1000;
|
||||
const secondsPerMinute = 60;
|
||||
const minutesPerHour = 60;
|
||||
const hoursPerDay = 24;
|
||||
const daysPerWeek = 7;
|
||||
|
||||
const unixSecond = millisecondsPerSecond;
|
||||
const unixMinute = secondsPerMinute * millisecondsPerSecond;
|
||||
const unixHour = unixMinute * minutesPerHour;
|
||||
const unixDay = hoursPerDay * unixHour;
|
||||
const unixWeek = daysPerWeek * unixDay;
|
||||
|
||||
export const unixTimesInMs = {
|
||||
second: unixSecond,
|
||||
minute: unixMinute,
|
||||
hour: unixHour,
|
||||
day: unixDay,
|
||||
week: unixWeek
|
||||
day: unixDay
|
||||
};
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { getInventory } from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const abandonLibraryDailyTaskController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
|
||||
@ -5,11 +5,11 @@ import {
|
||||
hasGuildPermission,
|
||||
removeDojoDeco,
|
||||
removeDojoRoom
|
||||
} from "../../services/guildService.ts";
|
||||
import { getInventory } from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { GuildPermission } from "../../types/guildTypes.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
} 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 abortDojoComponentController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
@ -31,13 +31,12 @@ export const abortDojoComponentController: RequestHandler = async (req, res) =>
|
||||
|
||||
if (request.DecoId) {
|
||||
removeDojoDeco(guild, request.ComponentId, request.DecoId);
|
||||
await guild.save();
|
||||
res.json(await getDojoClient(guild, 0, request.ComponentId));
|
||||
} else {
|
||||
await removeDojoRoom(guild, request.ComponentId);
|
||||
await guild.save();
|
||||
res.json(await getDojoClient(guild, 0));
|
||||
removeDojoRoom(guild, request.ComponentId);
|
||||
}
|
||||
|
||||
await guild.save();
|
||||
res.json(await getDojoClient(guild, 0, request.ComponentId));
|
||||
};
|
||||
|
||||
interface IAbortDojoComponentRequest {
|
||||
|
||||
@ -1,13 +1,8 @@
|
||||
import {
|
||||
getDojoClient,
|
||||
getGuildForRequestEx,
|
||||
hasAccessToDojo,
|
||||
hasGuildPermission
|
||||
} from "../../services/guildService.ts";
|
||||
import { getInventory } from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { GuildPermission } from "../../types/guildTypes.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { GuildPermission } from "@/src/types/guildTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const abortDojoComponentDestructionController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
|
||||
@ -1,19 +1,15 @@
|
||||
import { toOid } from "../../helpers/inventoryHelpers.ts";
|
||||
import {
|
||||
createVeiledRivenFingerprint,
|
||||
createUnveiledRivenFingerprint,
|
||||
rivenRawToRealWeighted
|
||||
} from "../../helpers/rivenHelper.ts";
|
||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
|
||||
import { addMods, getInventory } from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { getRandomElement } from "../../services/rngService.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||
import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "@/src/helpers/rivenHelper";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { addMods, getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getRandomElement } from "@/src/services/rngService";
|
||||
import { RequestHandler } from "express";
|
||||
import { ExportUpgrades } from "warframe-public-export-plus";
|
||||
|
||||
export const activateRandomModController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "RawUpgrades Upgrades instantFinishRivenChallenge");
|
||||
const inventory = await getInventory(accountId);
|
||||
const request = getJSONfromString<IActiveRandomModRequest>(String(req.body));
|
||||
addMods(inventory, [
|
||||
{
|
||||
@ -21,10 +17,8 @@ export const activateRandomModController: RequestHandler = async (req, res) => {
|
||||
ItemCount: -1
|
||||
}
|
||||
]);
|
||||
const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType])!;
|
||||
const fingerprint = inventory.instantFinishRivenChallenge
|
||||
? createUnveiledRivenFingerprint(ExportUpgrades[rivenType])
|
||||
: createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
|
||||
const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType]);
|
||||
const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
|
||||
const upgradeIndex =
|
||||
inventory.Upgrades.push({
|
||||
ItemType: rivenType,
|
||||
|
||||
@ -1,60 +0,0 @@
|
||||
import { toOid } from "../../helpers/inventoryHelpers.ts";
|
||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
|
||||
import { Friendship } from "../../models/friendModel.ts";
|
||||
import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "../../services/friendService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import type { IFriendInfo } from "../../types/friendTypes.ts";
|
||||
import type { 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,7 +1,7 @@
|
||||
import type { RequestHandler } from "express";
|
||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { Inventory } from "../../models/inventoryModels/inventoryModel.ts";
|
||||
import { RequestHandler } from "express";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
||||
|
||||
export const addFriendImageController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
import { toOid } from "../../helpers/inventoryHelpers.ts";
|
||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
|
||||
import { Account, Ignore } from "../../models/loginModel.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import type { IFriendInfo } from "../../types/friendTypes.ts";
|
||||
import type { 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 "../../helpers/inventoryHelpers.ts";
|
||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
|
||||
import { Friendship } from "../../models/friendModel.ts";
|
||||
import { Account } from "../../models/loginModel.ts";
|
||||
import { addInventoryDataToFriendInfo, areFriendsOfFriends } from "../../services/friendService.ts";
|
||||
import { getInventory } from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import type { IFriendInfo } from "../../types/friendTypes.ts";
|
||||
import type { 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 "../../helpers/stringHelpers.ts";
|
||||
import { Alliance, AllianceMember, Guild, GuildMember } from "../../models/guildModel.ts";
|
||||
import { createMessage } from "../../services/inboxService.ts";
|
||||
import { getEffectiveAvatarImageType, getInventory } from "../../services/inventoryService.ts";
|
||||
import { getAccountForRequest, getSuffixedName } from "../../services/loginService.ts";
|
||||
import { GuildPermission } from "../../types/guildTypes.ts";
|
||||
import { logger } from "../../utils/logger.ts";
|
||||
import type { 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,112 +1,77 @@
|
||||
import { toMongoDate } from "../../helpers/inventoryHelpers.ts";
|
||||
import { Guild, GuildMember } from "../../models/guildModel.ts";
|
||||
import { Account } from "../../models/loginModel.ts";
|
||||
import { addInventoryDataToFriendInfo, areFriends } from "../../services/friendService.ts";
|
||||
import { hasGuildPermission } from "../../services/guildService.ts";
|
||||
import { createMessage } from "../../services/inboxService.ts";
|
||||
import { getEffectiveAvatarImageType, getInventory } from "../../services/inventoryService.ts";
|
||||
import { getAccountForRequest, getAccountIdForRequest, getSuffixedName } from "../../services/loginService.ts";
|
||||
import type { IOid } from "../../types/commonTypes.ts";
|
||||
import type { IGuildMemberClient } from "../../types/guildTypes.ts";
|
||||
import { GuildPermission } from "../../types/guildTypes.ts";
|
||||
import { logger } from "../../utils/logger.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
import { Guild, GuildMember } from "@/src/models/guildModel";
|
||||
import { Account } from "@/src/models/loginModel";
|
||||
import { fillInInventoryDataForGuildMember, hasGuildPermission } from "@/src/services/guildService";
|
||||
import { createMessage } from "@/src/services/inboxService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||
import { IOid } from "@/src/types/commonTypes";
|
||||
import { GuildPermission, IGuildMemberClient } from "@/src/types/guildTypes";
|
||||
import { RequestHandler } from "express";
|
||||
import { ExportFlavour } from "warframe-public-export-plus";
|
||||
|
||||
export const addToGuildController: RequestHandler = async (req, res) => {
|
||||
const payload = JSON.parse(String(req.body)) as IAddToGuildRequest;
|
||||
|
||||
if ("UserName" in payload) {
|
||||
// Clan recruiter sending an invite
|
||||
|
||||
const account = await Account.findOne({ DisplayName: payload.UserName });
|
||||
if (!account) {
|
||||
res.status(400).json("Username does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
const 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 account = await Account.findOne({ DisplayName: payload.UserName });
|
||||
if (!account) {
|
||||
res.status(400).json("Username does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
const guild = (await Guild.findOne({ _id: payload.GuildId.$oid }, "Name"))!;
|
||||
const 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 {
|
||||
UserName?: string;
|
||||
UserName: string;
|
||||
GuildId: IOid;
|
||||
RequestMsg?: string;
|
||||
}
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
|
||||
import { getInventory } from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import type { 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,22 +0,0 @@
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { getPersonalRooms } from "../../services/personalRoomsService.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
|
||||
export const apartmentController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const personalRooms = await getPersonalRooms(accountId, "Apartment");
|
||||
const response: IApartmentResponse = {};
|
||||
if (req.query.backdrop !== undefined) {
|
||||
response.NewBackdropItem = personalRooms.Apartment.VideoWallBackdrop = req.query.backdrop as string;
|
||||
}
|
||||
if (req.query.soundscape !== undefined) {
|
||||
response.NewSoundscapeItem = personalRooms.Apartment.Soundscape = req.query.soundscape as string;
|
||||
}
|
||||
await personalRooms.save();
|
||||
res.json(response);
|
||||
};
|
||||
|
||||
interface IApartmentResponse {
|
||||
NewBackdropItem?: string;
|
||||
NewSoundscapeItem?: string;
|
||||
}
|
||||
@ -1,8 +1,8 @@
|
||||
import type { RequestHandler } from "express";
|
||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { getInventory, addMods } from "../../services/inventoryService.ts";
|
||||
import type { IOid } from "../../types/commonTypes.ts";
|
||||
import { RequestHandler } from "express";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getInventory, addMods } from "@/src/services/inventoryService";
|
||||
import { IOid } from "@/src/types/commonTypes";
|
||||
|
||||
export const arcaneCommonController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import type { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { addMiscItems, getInventory } from "../../services/inventoryService.ts";
|
||||
import type { IMiscItem } from "../../types/inventoryTypes/inventoryTypes.ts";
|
||||
import { colorToShard, combineColors, shardToColor } from "../../helpers/shardHelper.ts";
|
||||
import { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
||||
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { colorToShard, combineColors, shardToColor } from "@/src/helpers/shardHelper";
|
||||
|
||||
export const archonFusionController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
import { fromOid, toOid } from "../../helpers/inventoryHelpers.ts";
|
||||
import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "../../helpers/rivenHelper.ts";
|
||||
import { addMiscItems, addMods, getInventory } from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { getRandomElement, getRandomWeightedReward, getRandomWeightedRewardUc } from "../../services/rngService.ts";
|
||||
import type { IUpgradeFromClient } from "../../types/inventoryTypes/inventoryTypes.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
import type { TRarity } from "warframe-public-export-plus";
|
||||
import { ExportBoosterPacks, ExportUpgrades } from "warframe-public-export-plus";
|
||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||
import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "@/src/helpers/rivenHelper";
|
||||
import { addMiscItems, addMods, getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getRandomElement, getRandomWeightedReward, getRandomWeightedRewardUc } from "@/src/services/rngService";
|
||||
import { IOid } from "@/src/types/commonTypes";
|
||||
import { RequestHandler } from "express";
|
||||
import { ExportBoosterPacks, ExportUpgrades, TRarity } from "warframe-public-export-plus";
|
||||
|
||||
export const artifactTransmutationController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
@ -25,11 +24,11 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
|
||||
]);
|
||||
|
||||
payload.Consumed.forEach(upgrade => {
|
||||
inventory.Upgrades.pull({ _id: fromOid(upgrade.ItemId) });
|
||||
inventory.Upgrades.pull({ _id: upgrade.ItemId.$oid });
|
||||
});
|
||||
|
||||
const rawRivenType = getRandomRawRivenType();
|
||||
const rivenType = getRandomElement(rivenRawToRealWeighted[rawRivenType])!;
|
||||
const rivenType = getRandomElement(rivenRawToRealWeighted[rawRivenType]);
|
||||
const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
|
||||
|
||||
const upgradeIndex =
|
||||
@ -54,56 +53,33 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
|
||||
RARE: 0,
|
||||
LEGENDARY: 0
|
||||
};
|
||||
let forcedPolarity: string | undefined;
|
||||
payload.Consumed.forEach(upgrade => {
|
||||
const meta = ExportUpgrades[upgrade.ItemType];
|
||||
counts[meta.rarity] += upgrade.ItemCount;
|
||||
if (fromOid(upgrade.ItemId) != "000000000000000000000000") {
|
||||
inventory.Upgrades.pull({ _id: fromOid(upgrade.ItemId) });
|
||||
} else {
|
||||
addMods(inventory, [
|
||||
{
|
||||
ItemType: upgrade.ItemType,
|
||||
ItemCount: upgrade.ItemCount * -1
|
||||
}
|
||||
]);
|
||||
}
|
||||
if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/AttackTransmuteCore") {
|
||||
forcedPolarity = "AP_ATTACK";
|
||||
} else if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/DefenseTransmuteCore") {
|
||||
forcedPolarity = "AP_DEFENSE";
|
||||
} else if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/TacticTransmuteCore") {
|
||||
forcedPolarity = "AP_TACTIC";
|
||||
addMods(inventory, [
|
||||
{
|
||||
ItemType: upgrade.ItemType,
|
||||
ItemCount: upgrade.ItemCount * -1
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
// 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) {
|
||||
options.push({ uniqueName, rarity: upgrade.rarity });
|
||||
}
|
||||
});
|
||||
|
||||
let newModType: string | undefined;
|
||||
for (const specialModSet of specialModSets) {
|
||||
if (specialModSet.indexOf(payload.Consumed[0].ItemType) != -1) {
|
||||
newModType = getRandomElement(specialModSet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!newModType) {
|
||||
// Based on the table on https://wiki.warframe.com/w/Transmutation
|
||||
const weights: Record<TRarity, number> = {
|
||||
COMMON: counts.COMMON * 95 + counts.UNCOMMON * 15 + counts.RARE * 4,
|
||||
UNCOMMON: counts.COMMON * 4 + counts.UNCOMMON * 80 + counts.RARE * 10,
|
||||
RARE: counts.COMMON * 1 + counts.UNCOMMON * 5 + counts.RARE * 50,
|
||||
LEGENDARY: 0
|
||||
};
|
||||
|
||||
const options: { uniqueName: string; rarity: TRarity }[] = [];
|
||||
Object.entries(ExportUpgrades).forEach(([uniqueName, upgrade]) => {
|
||||
if (upgrade.canBeTransmutation && (!forcedPolarity || upgrade.polarity == forcedPolarity)) {
|
||||
options.push({ uniqueName, rarity: upgrade.rarity });
|
||||
}
|
||||
});
|
||||
|
||||
newModType = getRandomWeightedReward(options, weights)!.uniqueName;
|
||||
}
|
||||
|
||||
const newModType = getRandomWeightedReward(options, weights)!.uniqueName;
|
||||
addMods(inventory, [
|
||||
{
|
||||
ItemType: newModType,
|
||||
@ -129,41 +105,20 @@ const getRandomRawRivenType = (): string => {
|
||||
};
|
||||
|
||||
interface IArtifactTransmutationRequest {
|
||||
Upgrade: IUpgradeFromClient;
|
||||
Upgrade: IAgnosticUpgradeClient;
|
||||
LevelDiff: number;
|
||||
Consumed: IUpgradeFromClient[];
|
||||
Consumed: IAgnosticUpgradeClient[];
|
||||
Cost: number;
|
||||
FusionPointCost: number;
|
||||
RivenTransmute?: boolean;
|
||||
}
|
||||
|
||||
const specialModSets: string[][] = [
|
||||
[
|
||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalTwoMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalThreeMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalFourMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalFiveMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalSixMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalSevenMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalEightMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalWildcardMod"
|
||||
],
|
||||
[
|
||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusOneMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusTwoMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusThreeMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusFourMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusFiveMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusSixMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusSevenMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusEightMod"
|
||||
],
|
||||
[
|
||||
"/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndSpeedOnUseMod",
|
||||
"/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndWeaponDamageOnUseMod",
|
||||
"/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusLargeOnSingleUseMod",
|
||||
"/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusOnUseMod",
|
||||
"/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusSmallOnSingleUseMod"
|
||||
]
|
||||
];
|
||||
interface IAgnosticUpgradeClient {
|
||||
ItemType: string;
|
||||
ItemId: IOid;
|
||||
FromSKU: boolean;
|
||||
UpgradeFingerprint: string;
|
||||
PendingRerollFingerprint: string;
|
||||
ItemCount: number;
|
||||
LastAdded: IOid;
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
import type { IInventoryClient, IUpgradeClient } from "../../types/inventoryTypes/inventoryTypes.ts";
|
||||
import { addMods, getInventory } from "../../services/inventoryService.ts";
|
||||
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { RequestHandler } from "express";
|
||||
import { IInventoryClient, IUpgradeClient } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { addMods, getInventory } from "@/src/services/inventoryService";
|
||||
import { config } from "@/src/services/configService";
|
||||
|
||||
export const artifactsController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
@ -24,6 +24,7 @@ export const artifactsController: RequestHandler = async (req, res) => {
|
||||
|
||||
if (itemIndex !== -1) {
|
||||
Upgrades[itemIndex].UpgradeFingerprint = stringifiedUpgradeFingerprint;
|
||||
inventory.markModified(`Upgrades.${itemIndex}.UpgradeFingerprint`);
|
||||
} else {
|
||||
itemIndex =
|
||||
Upgrades.push({
|
||||
@ -34,10 +35,10 @@ export const artifactsController: RequestHandler = async (req, res) => {
|
||||
addMods(inventory, [{ ItemType, ItemCount: -1 }]);
|
||||
}
|
||||
|
||||
if (!inventory.infiniteCredits) {
|
||||
if (!config.infiniteCredits) {
|
||||
inventory.RegularCredits -= Cost;
|
||||
}
|
||||
if (!inventory.infiniteEndo) {
|
||||
if (!config.infiniteEndo) {
|
||||
inventory.FusionPoints -= FusionPointCost;
|
||||
}
|
||||
|
||||
@ -58,7 +59,6 @@ export const artifactsController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
|
||||
res.send(itemId);
|
||||
broadcastInventoryUpdate(req);
|
||||
};
|
||||
|
||||
interface IArtifactsRequest {
|
||||
|
||||
@ -1,20 +0,0 @@
|
||||
import { GuildAd } from "../../models/guildModel.ts";
|
||||
import { getGuildForRequestEx, hasGuildPermission } from "../../services/guildService.ts";
|
||||
import { getInventory } from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { GuildPermission } from "../../types/guildTypes.ts";
|
||||
import type { 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();
|
||||
};
|
||||
@ -1,16 +1,10 @@
|
||||
import type { RequestHandler } from "express";
|
||||
import {
|
||||
getDojoClient,
|
||||
getGuildForRequestEx,
|
||||
hasAccessToDojo,
|
||||
hasGuildPermission
|
||||
} from "../../services/guildService.ts";
|
||||
import { logger } from "../../utils/logger.ts";
|
||||
import type { IDojoComponentDatabase } from "../../types/guildTypes.ts";
|
||||
import { GuildPermission } from "../../types/guildTypes.ts";
|
||||
import { RequestHandler } from "express";
|
||||
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { GuildPermission, IDojoComponentDatabase } from "@/src/types/guildTypes";
|
||||
import { Types } from "mongoose";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { getInventory } from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
|
||||
export const changeDojoRootController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
@ -21,12 +15,6 @@ export const changeDojoRootController: RequestHandler = async (req, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Example POST body: {"pivot":[0, 0, -64],"components":"{\"670429301ca0a63848ccc467\":{\"R\":[0,0,0],\"P\":[0,3,32]},\"6704254a1ca0a63848ccb33c\":{\"R\":[0,0,0],\"P\":[0,9.25,-32]},\"670429461ca0a63848ccc731\":{\"R\":[-90,0,0],\"P\":[-47.999992370605,3,16]}}"}
|
||||
if (req.body) {
|
||||
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
||||
throw new Error("dojo reparent operation should not need deco repositioning"); // because we always provide SortId
|
||||
}
|
||||
|
||||
const idToNode: Record<string, INode> = {};
|
||||
guild.DojoComponents.forEach(x => {
|
||||
idToNode[x._id.toString()] = {
|
||||
@ -55,13 +43,23 @@ export const changeDojoRootController: RequestHandler = async (req, res) => {
|
||||
newRoot.component.pp = 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];
|
||||
let i = 0;
|
||||
const idMap: Record<string, Types.ObjectId> = {};
|
||||
while (stack.length != 0) {
|
||||
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));
|
||||
}
|
||||
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));
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { GuildMember } from "../../models/guildModel.ts";
|
||||
import { getGuildForRequest, hasGuildPermissionEx } from "../../services/guildService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { GuildPermission } from "../../types/guildTypes.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
import { GuildMember } from "@/src/models/guildModel";
|
||||
import { getGuildForRequest, hasGuildPermissionEx } from "@/src/services/guildService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { GuildPermission } from "@/src/types/guildTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const changeGuildRankController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
|
||||
@ -1,12 +1,16 @@
|
||||
import { getAccountForRequest } from "../../services/loginService.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const checkDailyMissionBonusController: RequestHandler = async (req, res) => {
|
||||
const account = await getAccountForRequest(req);
|
||||
const today = Math.trunc(Date.now() / 86400000) * 86400;
|
||||
if (account.DailyFirstWinDate != today) {
|
||||
res.send("DailyMissionBonus:1-DailyPVPWinBonus:1\n");
|
||||
} else {
|
||||
res.send("DailyMissionBonus:0-DailyPVPWinBonus:1\n");
|
||||
}
|
||||
const checkDailyMissionBonusController: RequestHandler = (_req, res) => {
|
||||
const data = Buffer.from([
|
||||
0x44, 0x61, 0x69, 0x6c, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x6f, 0x6e, 0x75, 0x73, 0x3a,
|
||||
0x31, 0x2d, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x50, 0x56, 0x50, 0x57, 0x69, 0x6e, 0x42, 0x6f, 0x6e, 0x75, 0x73,
|
||||
0x3a, 0x31, 0x0a
|
||||
]);
|
||||
res.writeHead(200, {
|
||||
"Content-Type": "text/html",
|
||||
"Content-Length": data.length
|
||||
});
|
||||
res.end(data);
|
||||
};
|
||||
|
||||
export { checkDailyMissionBonusController };
|
||||
|
||||
@ -1,307 +1,126 @@
|
||||
//this is a controller for the claimCompletedRecipe route
|
||||
//it will claim a recipe for the user
|
||||
|
||||
import type { RequestHandler } from "express";
|
||||
import { logger } from "../../utils/logger.ts";
|
||||
import { getRecipe } from "../../services/itemDataService.ts";
|
||||
import type { IOidWithLegacySupport } from "../../types/commonTypes.ts";
|
||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
|
||||
import type { TAccountDocument } from "../../services/loginService.ts";
|
||||
import { getAccountForRequest } from "../../services/loginService.ts";
|
||||
import { RequestHandler } from "express";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { getRecipe } from "@/src/services/itemDataService";
|
||||
import { IOid } from "@/src/types/commonTypes";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import {
|
||||
getInventory,
|
||||
updateCurrency,
|
||||
addItem,
|
||||
addMiscItems,
|
||||
addRecipes,
|
||||
occupySlot,
|
||||
combineInventoryChanges,
|
||||
addKubrowPetPrint,
|
||||
addPowerSuit,
|
||||
addEquipment
|
||||
} from "../../services/inventoryService.ts";
|
||||
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
|
||||
import type { IPendingRecipeDatabase } from "../../types/inventoryTypes/inventoryTypes.ts";
|
||||
import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts";
|
||||
import { fromOid, toOid2 } from "../../helpers/inventoryHelpers.ts";
|
||||
import type { TInventoryDatabaseDocument } from "../../models/inventoryModels/inventoryModel.ts";
|
||||
import type { IRecipe } from "warframe-public-export-plus";
|
||||
import type { IEquipmentClient } from "../../types/equipmentTypes.ts";
|
||||
import { EquipmentFeatures, Status } from "../../types/equipmentTypes.ts";
|
||||
occupySlot
|
||||
} from "@/src/services/inventoryService";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { IMiscItem, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
|
||||
interface IClaimCompletedRecipeRequest {
|
||||
RecipeIds: IOidWithLegacySupport[];
|
||||
}
|
||||
|
||||
interface IClaimCompletedRecipeResponse {
|
||||
InventoryChanges: IInventoryChanges;
|
||||
BrandedSuits?: IOidWithLegacySupport[];
|
||||
export interface IClaimCompletedRecipeRequest {
|
||||
RecipeIds: IOid[];
|
||||
}
|
||||
|
||||
export const claimCompletedRecipeController: RequestHandler = async (req, res) => {
|
||||
const claimCompletedRecipeRequest = getJSONfromString<IClaimCompletedRecipeRequest>(String(req.body));
|
||||
const account = await getAccountForRequest(req);
|
||||
const inventory = await getInventory(account._id.toString());
|
||||
const resp: IClaimCompletedRecipeResponse = {
|
||||
InventoryChanges: {}
|
||||
};
|
||||
for (const recipeId of claimCompletedRecipeRequest.RecipeIds) {
|
||||
const pendingRecipe = inventory.PendingRecipes.id(fromOid(recipeId));
|
||||
if (!pendingRecipe) {
|
||||
throw new Error(`no pending recipe found with id ${fromOid(recipeId)}`);
|
||||
}
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
if (!accountId) throw new Error("no account id");
|
||||
|
||||
//check recipe is indeed ready to be completed
|
||||
// if (pendingRecipe.CompletionDate > new Date()) {
|
||||
// throw new Error(`recipe ${pendingRecipe._id} is not ready to be completed`);
|
||||
// }
|
||||
|
||||
inventory.PendingRecipes.pull(pendingRecipe._id);
|
||||
|
||||
const recipe = getRecipe(pendingRecipe.ItemType);
|
||||
if (!recipe) {
|
||||
throw new Error(`no completed item found for recipe ${pendingRecipe._id.toString()}`);
|
||||
}
|
||||
|
||||
if (req.query.cancel) {
|
||||
const inventoryChanges: IInventoryChanges = {};
|
||||
await refundRecipeIngredients(inventory, inventoryChanges, recipe, pendingRecipe);
|
||||
await inventory.save();
|
||||
res.json(inventoryChanges); // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
|
||||
return;
|
||||
}
|
||||
|
||||
await claimCompletedRecipe(account, inventory, recipe, pendingRecipe, resp, req.query.rush);
|
||||
const inventory = await getInventory(accountId);
|
||||
const pendingRecipe = inventory.PendingRecipes.id(claimCompletedRecipeRequest.RecipeIds[0].$oid);
|
||||
if (!pendingRecipe) {
|
||||
throw new Error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$oid}`);
|
||||
}
|
||||
await inventory.save();
|
||||
res.json(resp);
|
||||
};
|
||||
|
||||
const claimCompletedRecipe = async (
|
||||
account: TAccountDocument,
|
||||
inventory: TInventoryDatabaseDocument,
|
||||
recipe: IRecipe,
|
||||
pendingRecipe: IPendingRecipeDatabase,
|
||||
resp: IClaimCompletedRecipeResponse,
|
||||
rush: any
|
||||
): Promise<void> => {
|
||||
logger.debug("Claiming Recipe", { recipe, pendingRecipe });
|
||||
//check recipe is indeed ready to be completed
|
||||
// if (pendingRecipe.CompletionDate > new Date()) {
|
||||
// throw new Error(`recipe ${pendingRecipe._id} is not ready to be completed`);
|
||||
// }
|
||||
|
||||
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
|
||||
inventory.PendingSpectreLoadouts ??= [];
|
||||
inventory.SpectreLoadouts ??= [];
|
||||
inventory.PendingRecipes.pull(pendingRecipe._id);
|
||||
|
||||
const pendingLoadoutIndex = inventory.PendingSpectreLoadouts.findIndex(x => x.ItemType == recipe.resultType);
|
||||
if (pendingLoadoutIndex != -1) {
|
||||
const loadoutIndex = inventory.SpectreLoadouts.findIndex(x => x.ItemType == recipe.resultType);
|
||||
if (loadoutIndex != -1) {
|
||||
inventory.SpectreLoadouts.splice(loadoutIndex, 1);
|
||||
const recipe = getRecipe(pendingRecipe.ItemType);
|
||||
if (!recipe) {
|
||||
throw new Error(`no completed item found for recipe ${pendingRecipe._id.toString()}`);
|
||||
}
|
||||
|
||||
if (req.query.cancel) {
|
||||
const inventoryChanges: IInventoryChanges = {
|
||||
...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;
|
||||
});
|
||||
}
|
||||
logger.debug(
|
||||
"moving spectre loadout from pending to active",
|
||||
inventory.toJSON().PendingSpectreLoadouts![pendingLoadoutIndex]
|
||||
}
|
||||
const miscItemChanges: IMiscItem[] = [];
|
||||
recipe.ingredients.forEach(ingredient => {
|
||||
if (!nonMiscItemIngredients.has(ingredient.ItemType)) {
|
||||
miscItemChanges.push(ingredient);
|
||||
}
|
||||
});
|
||||
addMiscItems(inventory, miscItemChanges);
|
||||
inventoryChanges.MiscItems = miscItemChanges;
|
||||
|
||||
await inventory.save();
|
||||
res.json(inventoryChanges); // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
|
||||
} else {
|
||||
logger.debug("Claiming Recipe", { recipe, pendingRecipe });
|
||||
|
||||
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
|
||||
inventory.PendingSpectreLoadouts ??= [];
|
||||
inventory.SpectreLoadouts ??= [];
|
||||
|
||||
const pendingLoadoutIndex = inventory.PendingSpectreLoadouts.findIndex(
|
||||
x => x.ItemType == recipe.resultType
|
||||
);
|
||||
inventory.SpectreLoadouts.push(inventory.PendingSpectreLoadouts[pendingLoadoutIndex]);
|
||||
inventory.PendingSpectreLoadouts.splice(pendingLoadoutIndex, 1);
|
||||
}
|
||||
} else if (recipe.secretIngredientAction == "SIA_UNBRAND") {
|
||||
inventory.BrandedSuits!.splice(
|
||||
inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)),
|
||||
1
|
||||
);
|
||||
resp.BrandedSuits = [toOid2(pendingRecipe.SuitToUnbrand!, account.BuildLabel)];
|
||||
}
|
||||
|
||||
if (recipe.consumeOnUse) {
|
||||
addRecipes(inventory, [
|
||||
{
|
||||
ItemType: pendingRecipe.ItemType,
|
||||
ItemCount: -1
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
if (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 =
|
||||
progress > 0.5 ? Math.round(recipe.skipBuildTimePrice * (1 - (progress - 0.5))) : recipe.skipBuildTimePrice;
|
||||
combineInventoryChanges(resp.InventoryChanges, updateCurrency(inventory, cost, true));
|
||||
}
|
||||
|
||||
if (recipe.secretIngredientAction == "SIA_CREATE_KUBROW") {
|
||||
const pet = inventory.KubrowPets.id(pendingRecipe.KubrowPet!)!;
|
||||
if (pet.Details!.HatchDate!.getTime() > Date.now()) {
|
||||
pet.Details!.HatchDate = new Date();
|
||||
}
|
||||
let canSetActive = true;
|
||||
for (const pet of inventory.KubrowPets) {
|
||||
if (pet.Details!.Status == Status.StatusAvailable) {
|
||||
canSetActive = false;
|
||||
break;
|
||||
if (pendingLoadoutIndex != -1) {
|
||||
const loadoutIndex = inventory.SpectreLoadouts.findIndex(x => x.ItemType == recipe.resultType);
|
||||
if (loadoutIndex != -1) {
|
||||
inventory.SpectreLoadouts.splice(loadoutIndex, 1);
|
||||
}
|
||||
logger.debug(
|
||||
"moving spectre loadout from pending to active",
|
||||
inventory.toJSON().PendingSpectreLoadouts![pendingLoadoutIndex]
|
||||
);
|
||||
inventory.SpectreLoadouts.push(inventory.PendingSpectreLoadouts[pendingLoadoutIndex]);
|
||||
inventory.PendingSpectreLoadouts.splice(pendingLoadoutIndex, 1);
|
||||
}
|
||||
}
|
||||
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, resp.InventoryChanges);
|
||||
} else if (recipe.secretIngredientAction != "SIA_UNBRAND") {
|
||||
if (recipe.resultType == "/Lotus/Powersuits/Excalibur/ExcaliburUmbra") {
|
||||
// Quite the special case here...
|
||||
// We don't just get Umbra, but also Skiajati and Umbra Mods. Both items are max rank, potatoed, and with the mods are pre-installed.
|
||||
// Source: https://wiki.warframe.com/w/The_Sacrifice, https://wiki.warframe.com/w/Excalibur/Umbra, https://wiki.warframe.com/w/Skiajati
|
||||
|
||||
const umbraModA = (
|
||||
await addItem(
|
||||
inventory,
|
||||
"/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModA",
|
||||
1,
|
||||
false,
|
||||
undefined,
|
||||
`{"lvl":5}`
|
||||
)
|
||||
).Upgrades![0];
|
||||
const umbraModB = (
|
||||
await addItem(
|
||||
inventory,
|
||||
"/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModB",
|
||||
1,
|
||||
false,
|
||||
undefined,
|
||||
`{"lvl":5}`
|
||||
)
|
||||
).Upgrades![0];
|
||||
const umbraModC = (
|
||||
await addItem(
|
||||
inventory,
|
||||
"/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModC",
|
||||
1,
|
||||
false,
|
||||
undefined,
|
||||
`{"lvl":5}`
|
||||
)
|
||||
).Upgrades![0];
|
||||
const sacrificeModA = (
|
||||
await addItem(
|
||||
inventory,
|
||||
"/Lotus/Upgrades/Mods/Sets/Sacrifice/MeleeSacrificeModA",
|
||||
1,
|
||||
false,
|
||||
undefined,
|
||||
`{"lvl":5}`
|
||||
)
|
||||
).Upgrades![0];
|
||||
const sacrificeModB = (
|
||||
await addItem(
|
||||
inventory,
|
||||
"/Lotus/Upgrades/Mods/Sets/Sacrifice/MeleeSacrificeModB",
|
||||
1,
|
||||
false,
|
||||
undefined,
|
||||
`{"lvl":5}`
|
||||
)
|
||||
).Upgrades![0];
|
||||
resp.InventoryChanges.Upgrades ??= [];
|
||||
resp.InventoryChanges.Upgrades.push(umbraModA, umbraModB, umbraModC, sacrificeModA, sacrificeModB);
|
||||
|
||||
await addPowerSuit(
|
||||
inventory,
|
||||
"/Lotus/Powersuits/Excalibur/ExcaliburUmbra",
|
||||
let InventoryChanges = {};
|
||||
if (recipe.consumeOnUse) {
|
||||
addRecipes(inventory, [
|
||||
{
|
||||
Configs: [
|
||||
{
|
||||
Upgrades: [
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
umbraModA.ItemId.$oid,
|
||||
umbraModB.ItemId.$oid,
|
||||
umbraModC.ItemId.$oid
|
||||
]
|
||||
}
|
||||
],
|
||||
XP: 900_000,
|
||||
Features: EquipmentFeatures.DOUBLE_CAPACITY
|
||||
},
|
||||
resp.InventoryChanges
|
||||
);
|
||||
inventory.XPInfo.push({
|
||||
ItemType: "/Lotus/Powersuits/Excalibur/ExcaliburUmbra",
|
||||
XP: 900_000
|
||||
});
|
||||
|
||||
addEquipment(
|
||||
inventory,
|
||||
"Melee",
|
||||
"/Lotus/Weapons/Tenno/Melee/Swords/UmbraKatana/UmbraKatana",
|
||||
{
|
||||
Configs: [
|
||||
{ Upgrades: ["", "", "", "", "", "", sacrificeModA.ItemId.$oid, sacrificeModB.ItemId.$oid] }
|
||||
],
|
||||
XP: 450_000,
|
||||
Features: EquipmentFeatures.DOUBLE_CAPACITY
|
||||
},
|
||||
resp.InventoryChanges
|
||||
);
|
||||
inventory.XPInfo.push({
|
||||
ItemType: "/Lotus/Weapons/Tenno/Melee/Swords/UmbraKatana/UmbraKatana",
|
||||
XP: 450_000
|
||||
});
|
||||
} else {
|
||||
combineInventoryChanges(
|
||||
resp.InventoryChanges,
|
||||
await addItem(
|
||||
inventory,
|
||||
recipe.resultType,
|
||||
recipe.num,
|
||||
false,
|
||||
undefined,
|
||||
pendingRecipe.TargetFingerprint
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (
|
||||
inventory.claimingBlueprintRefundsIngredients &&
|
||||
recipe.secretIngredientAction != "SIA_CREATE_KUBROW" // Can't refund the egg
|
||||
) {
|
||||
await refundRecipeIngredients(inventory, resp.InventoryChanges, recipe, pendingRecipe);
|
||||
}
|
||||
};
|
||||
|
||||
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)
|
||||
);
|
||||
ItemType: pendingRecipe.ItemType,
|
||||
ItemCount: -1
|
||||
}
|
||||
]);
|
||||
}
|
||||
if (req.query.rush) {
|
||||
InventoryChanges = {
|
||||
...InventoryChanges,
|
||||
...updateCurrency(inventory, recipe.skipBuildTimePrice, true)
|
||||
};
|
||||
}
|
||||
InventoryChanges = {
|
||||
...InventoryChanges,
|
||||
...(await addItem(inventory, recipe.resultType, recipe.num, false)).InventoryChanges
|
||||
};
|
||||
await inventory.save();
|
||||
res.json({ InventoryChanges });
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,35 +0,0 @@
|
||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
|
||||
import { combineInventoryChanges, getInventory } from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { handleStoreItemAcquisition } from "../../services/purchaseService.ts";
|
||||
import type { 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,6 +1,6 @@
|
||||
import { addFusionPoints, getInventory } from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const claimLibraryDailyTaskRewardController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
@ -17,7 +17,7 @@ export const claimLibraryDailyTaskRewardController: RequestHandler = async (req,
|
||||
}
|
||||
syndicate.Standing += rewardStanding;
|
||||
|
||||
addFusionPoints(inventory, 80 * rewardQuantity);
|
||||
inventory.FusionPoints += 80 * rewardQuantity;
|
||||
await inventory.save();
|
||||
|
||||
res.json({
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
import { getInventory } from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const clearDialogueHistoryController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId);
|
||||
const request = JSON.parse(String(req.body)) as IClearDialogueRequest;
|
||||
if (inventory.DialogueHistory && inventory.DialogueHistory.Dialogues) {
|
||||
inventory.DialogueHistory.Resets ??= 0;
|
||||
inventory.DialogueHistory.Resets += 1;
|
||||
for (const dialogueName of request.Dialogues) {
|
||||
const index = inventory.DialogueHistory.Dialogues.findIndex(x => x.DialogueName == dialogueName);
|
||||
if (index != -1) {
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
import type { 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 { checkCalendarAutoAdvance, getCalendarProgress, getInventory } from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { handleStoreItemAcquisition } from "../../services/purchaseService.ts";
|
||||
import { getWorldState } from "../../services/worldStateService.ts";
|
||||
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
|
||||
import type { 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;
|
||||
checkCalendarAutoAdvance(inventory, currentSeason);
|
||||
await inventory.save();
|
||||
res.json({
|
||||
InventoryChanges: inventoryChanges,
|
||||
CalendarProgress: inventory.CalendarProgress
|
||||
});
|
||||
};
|
||||
@ -1,10 +1,11 @@
|
||||
import type { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { addMiscItems, getInventory, updateCurrency } from "../../services/inventoryService.ts";
|
||||
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
|
||||
import type { IMiscItem } from "../../types/inventoryTypes/inventoryTypes.ts";
|
||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
|
||||
import type { IVeiledRivenFingerprint } from "../../helpers/rivenHelper.ts";
|
||||
import { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { createUnveiledRivenFingerprint } from "@/src/helpers/rivenHelper";
|
||||
import { ExportUpgrades } from "warframe-public-export-plus";
|
||||
|
||||
export const completeRandomModChallengeController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
@ -26,11 +27,10 @@ export const completeRandomModChallengeController: RequestHandler = async (req,
|
||||
inventoryChanges.MiscItems = miscItemChanges;
|
||||
}
|
||||
|
||||
// Complete the riven challenge
|
||||
// Update riven fingerprint to a randomised unveiled state
|
||||
const upgrade = inventory.Upgrades.id(request.ItemId)!;
|
||||
const fp = JSON.parse(upgrade.UpgradeFingerprint!) as IVeiledRivenFingerprint;
|
||||
fp.challenge.Progress = fp.challenge.Required;
|
||||
upgrade.UpgradeFingerprint = JSON.stringify(fp);
|
||||
const meta = ExportUpgrades[upgrade.ItemType];
|
||||
upgrade.UpgradeFingerprint = JSON.stringify(createUnveiledRivenFingerprint(meta));
|
||||
|
||||
await inventory.save();
|
||||
|
||||
|
||||
@ -1,37 +0,0 @@
|
||||
import { Alliance, AllianceMember, Guild, GuildMember } from "../../models/guildModel.ts";
|
||||
import { getAllianceClient } from "../../services/guildService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import type { 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 "../../helpers/stringHelpers.ts";
|
||||
import { Guild, GuildMember } from "../../models/guildModel.ts";
|
||||
import { Account } from "../../models/loginModel.ts";
|
||||
import {
|
||||
deleteGuild,
|
||||
getGuildClient,
|
||||
giveClanKey,
|
||||
hasGuildPermission,
|
||||
removeDojoKeyItems
|
||||
} from "../../services/guildService.ts";
|
||||
import { getInventory } from "../../services/inventoryService.ts";
|
||||
import { getAccountForRequest, getAccountIdForRequest, getSuffixedName } from "../../services/loginService.ts";
|
||||
import { GuildPermission } from "../../types/guildTypes.ts";
|
||||
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
import { Guild, GuildMember } from "@/src/models/guildModel";
|
||||
import { getGuildClient, updateInventoryForConfirmedGuildJoin } from "@/src/services/guildService";
|
||||
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||
import { RequestHandler } from "express";
|
||||
import { Types } from "mongoose";
|
||||
|
||||
// GET request: A player accepting an invite they got in their inbox.
|
||||
export const confirmGuildInvitationGetController: RequestHandler = async (req, res) => {
|
||||
export const confirmGuildInvitationController: RequestHandler = async (req, res) => {
|
||||
const account = await getAccountForRequest(req);
|
||||
const invitedGuildMember = await GuildMember.findOne({
|
||||
const guildMember = await GuildMember.findOne({
|
||||
accountId: account._id,
|
||||
guildId: req.query.clanId as string
|
||||
});
|
||||
if (invitedGuildMember && invitedGuildMember.status == 2) {
|
||||
let inventoryChanges: IInventoryChanges = {};
|
||||
if (guildMember) {
|
||||
guildMember.status = 0;
|
||||
await guildMember.save();
|
||||
|
||||
// If this account is already in a guild, we need to do cleanup first.
|
||||
const guildMember = await GuildMember.findOneAndDelete({ accountId: account._id, status: 0 });
|
||||
if (guildMember) {
|
||||
const inventory = await getInventory(account._id.toString(), "LevelKeys Recipes");
|
||||
inventoryChanges = removeDojoKeyItems(inventory);
|
||||
await inventory.save();
|
||||
await updateInventoryForConfirmedGuildJoin(
|
||||
account._id.toString(),
|
||||
new Types.ObjectId(req.query.clanId as string)
|
||||
);
|
||||
|
||||
if (guildMember.rank == 0) {
|
||||
await deleteGuild(guildMember.guildId);
|
||||
}
|
||||
}
|
||||
const guild = (await Guild.findOne({ _id: req.query.clanId as string }))!;
|
||||
|
||||
// Now that we're sure this account is not in a guild right now, we can just proceed with the normal updates.
|
||||
invitedGuildMember.status = 0;
|
||||
await invitedGuildMember.save();
|
||||
|
||||
// Remove pending applications for this account
|
||||
await GuildMember.deleteMany({ accountId: account._id, status: 1 });
|
||||
|
||||
// Update inventory of new member
|
||||
const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes");
|
||||
inventory.GuildId = new Types.ObjectId(req.query.clanId as string);
|
||||
giveClanKey(inventory, inventoryChanges);
|
||||
await inventory.save();
|
||||
|
||||
const guild = (await Guild.findById(req.query.clanId as string))!;
|
||||
|
||||
// Add join to clan log
|
||||
guild.RosterActivity ??= [];
|
||||
guild.RosterActivity.push({
|
||||
dateTime: new Date(),
|
||||
@ -62,57 +30,17 @@ export const confirmGuildInvitationGetController: RequestHandler = async (req, r
|
||||
await guild.save();
|
||||
|
||||
res.json({
|
||||
...(await getGuildClient(guild, account)),
|
||||
InventoryChanges: inventoryChanges
|
||||
...(await getGuildClient(guild, account._id.toString())),
|
||||
InventoryChanges: {
|
||||
Recipes: [
|
||||
{
|
||||
ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
|
||||
ItemCount: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.end();
|
||||
}
|
||||
};
|
||||
|
||||
// POST request: Clan representative accepting invite(s).
|
||||
export const confirmGuildInvitationPostController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const guild = (await Guild.findById(req.query.clanId as string, "Ranks RosterActivity"))!;
|
||||
if (!(await hasGuildPermission(guild, accountId, GuildPermission.Recruiter))) {
|
||||
res.status(400).json("Invalid permission");
|
||||
return;
|
||||
}
|
||||
const payload = getJSONfromString<{ userId: string }>(String(req.body));
|
||||
const filter: { accountId?: string; status: number } = { status: 1 };
|
||||
if (payload.userId != "all") {
|
||||
filter.accountId = payload.userId;
|
||||
}
|
||||
const guildMembers = await GuildMember.find(filter);
|
||||
const newMembers: string[] = [];
|
||||
for (const guildMember of guildMembers) {
|
||||
guildMember.status = 0;
|
||||
guildMember.RequestMsg = undefined;
|
||||
guildMember.RequestExpiry = undefined;
|
||||
await guildMember.save();
|
||||
|
||||
// Remove other pending applications for this account
|
||||
await GuildMember.deleteMany({ accountId: guildMember.accountId, status: 1 });
|
||||
|
||||
// Update inventory of new member
|
||||
const inventory = await getInventory(guildMember.accountId.toString(), "GuildId LevelKeys Recipes");
|
||||
inventory.GuildId = new Types.ObjectId(req.query.clanId as string);
|
||||
giveClanKey(inventory);
|
||||
await inventory.save();
|
||||
|
||||
// Add join to clan log
|
||||
const account = (await Account.findOne({ _id: guildMember.accountId }))!;
|
||||
guild.RosterActivity ??= [];
|
||||
guild.RosterActivity.push({
|
||||
dateTime: new Date(),
|
||||
entryType: 6,
|
||||
details: getSuffixedName(account)
|
||||
});
|
||||
|
||||
newMembers.push(account._id.toString());
|
||||
}
|
||||
await guild.save();
|
||||
res.json({
|
||||
NewMembers: newMembers
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import { toMongoDate } from "../../helpers/inventoryHelpers.ts";
|
||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
|
||||
import { Guild } from "../../models/guildModel.ts";
|
||||
import { checkClanAscensionHasRequiredContributors } from "../../services/guildService.ts";
|
||||
import { addFusionPoints, getInventory } from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { Guild } from "@/src/models/guildModel";
|
||||
import { config } from "@/src/services/configService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { RequestHandler } from "express";
|
||||
import { Types } from "mongoose";
|
||||
|
||||
export const contributeGuildClassController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const payload = getJSONfromString<IContributeGuildClassRequest>(String(req.body));
|
||||
const guild = (await Guild.findById(payload.GuildId))!;
|
||||
const guild = (await Guild.findOne({ _id: payload.GuildId }))!;
|
||||
|
||||
// First contributor initiates ceremony and locks the pending class.
|
||||
if (!guild.CeremonyContributors) {
|
||||
@ -30,13 +30,18 @@ export const contributeGuildClassController: RequestHandler = async (req, res) =
|
||||
|
||||
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();
|
||||
|
||||
// Either way, endo is given to the contributor.
|
||||
const inventory = await getInventory(accountId, "FusionPoints");
|
||||
addFusionPoints(inventory, guild.CeremonyEndo!);
|
||||
inventory.FusionPoints += guild.CeremonyEndo!;
|
||||
await inventory.save();
|
||||
|
||||
res.json({
|
||||
|
||||
@ -1,23 +1,20 @@
|
||||
import type { TGuildDatabaseDocument } from "../../models/guildModel.ts";
|
||||
import { GuildMember } from "../../models/guildModel.ts";
|
||||
import type { TInventoryDatabaseDocument } from "../../models/inventoryModels/inventoryModel.ts";
|
||||
import { TGuildDatabaseDocument } from "@/src/models/guildModel";
|
||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||
import {
|
||||
addGuildMemberMiscItemContribution,
|
||||
getDojoClient,
|
||||
getGuildForRequestEx,
|
||||
hasAccessToDojo,
|
||||
processDojoBuildMaterialsGathered,
|
||||
scaleRequiredCount,
|
||||
setDojoRoomLogFunded
|
||||
} from "../../services/guildService.ts";
|
||||
import { addMiscItems, getInventory, updateCurrency } from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import type { IDojoContributable, IGuildMemberDatabase } from "../../types/guildTypes.ts";
|
||||
import type { IMiscItem } from "../../types/inventoryTypes/inventoryTypes.ts";
|
||||
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
import type { IDojoBuild } from "warframe-public-export-plus";
|
||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
|
||||
} from "@/src/services/guildService";
|
||||
import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { IDojoContributable } from "@/src/types/guildTypes";
|
||||
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { RequestHandler } from "express";
|
||||
import { ExportDojoRecipes, IDojoBuild } from "warframe-public-export-plus";
|
||||
|
||||
interface IContributeToDojoComponentRequest {
|
||||
ComponentId: string;
|
||||
@ -38,10 +35,6 @@ export const contributeToDojoComponentController: RequestHandler = async (req, r
|
||||
return;
|
||||
}
|
||||
const guild = await getGuildForRequestEx(req, inventory);
|
||||
const guildMember = (await GuildMember.findOne(
|
||||
{ accountId, guildId: guild._id },
|
||||
"RegularCreditsContributed MiscItemsContributed"
|
||||
))!;
|
||||
const request = JSON.parse(String(req.body)) as IContributeToDojoComponentRequest;
|
||||
const component = guild.DojoComponents.id(request.ComponentId)!;
|
||||
|
||||
@ -52,7 +45,7 @@ export const contributeToDojoComponentController: RequestHandler = async (req, r
|
||||
throw new Error("attempt to contribute to a deco in an unfinished room?!");
|
||||
}
|
||||
const meta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!;
|
||||
processContribution(guild, guildMember, request, inventory, inventoryChanges, meta, component);
|
||||
processContribution(guild, request, inventory, inventoryChanges, meta, component);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (component.CompletionTime) {
|
||||
setDojoRoomLogFunded(guild, component);
|
||||
@ -62,11 +55,12 @@ export const contributeToDojoComponentController: RequestHandler = async (req, r
|
||||
if (request.DecoId) {
|
||||
const deco = component.Decos!.find(x => x._id.equals(request.DecoId))!;
|
||||
const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == deco.Type)!;
|
||||
processContribution(guild, 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({
|
||||
...(await getDojoClient(guild, 0, component._id)),
|
||||
InventoryChanges: inventoryChanges
|
||||
@ -75,7 +69,6 @@ export const contributeToDojoComponentController: RequestHandler = async (req, r
|
||||
|
||||
const processContribution = (
|
||||
guild: TGuildDatabaseDocument,
|
||||
guildMember: IGuildMemberDatabase,
|
||||
request: IContributeToDojoComponentRequest,
|
||||
inventory: TInventoryDatabaseDocument,
|
||||
inventoryChanges: IInventoryChanges,
|
||||
@ -87,18 +80,15 @@ const processContribution = (
|
||||
component.RegularCredits += request.RegularCredits;
|
||||
inventoryChanges.RegularCredits = -request.RegularCredits;
|
||||
updateCurrency(inventory, request.RegularCredits, false);
|
||||
|
||||
guildMember.RegularCreditsContributed ??= 0;
|
||||
guildMember.RegularCreditsContributed += request.RegularCredits;
|
||||
}
|
||||
if (request.VaultCredits) {
|
||||
component.RegularCredits += request.VaultCredits;
|
||||
guild.VaultRegularCredits! -= request.VaultCredits;
|
||||
}
|
||||
if (component.RegularCredits > scaleRequiredCount(guild.Tier, meta.price)) {
|
||||
if (component.RegularCredits > scaleRequiredCount(meta.price)) {
|
||||
guild.VaultRegularCredits ??= 0;
|
||||
guild.VaultRegularCredits += component.RegularCredits - scaleRequiredCount(guild.Tier, meta.price);
|
||||
component.RegularCredits = scaleRequiredCount(guild.Tier, meta.price);
|
||||
guild.VaultRegularCredits += component.RegularCredits - scaleRequiredCount(meta.price);
|
||||
component.RegularCredits = scaleRequiredCount(meta.price);
|
||||
}
|
||||
|
||||
component.MiscItems ??= [];
|
||||
@ -109,10 +99,10 @@ const processContribution = (
|
||||
const ingredientMeta = meta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
|
||||
if (
|
||||
componentMiscItem.ItemCount + ingredientContribution.ItemCount >
|
||||
scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount)
|
||||
scaleRequiredCount(ingredientMeta.ItemCount)
|
||||
) {
|
||||
ingredientContribution.ItemCount =
|
||||
scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
|
||||
scaleRequiredCount(ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
|
||||
}
|
||||
componentMiscItem.ItemCount += ingredientContribution.ItemCount;
|
||||
} else {
|
||||
@ -130,10 +120,10 @@ const processContribution = (
|
||||
const ingredientMeta = meta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
|
||||
if (
|
||||
componentMiscItem.ItemCount + ingredientContribution.ItemCount >
|
||||
scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount)
|
||||
scaleRequiredCount(ingredientMeta.ItemCount)
|
||||
) {
|
||||
ingredientContribution.ItemCount =
|
||||
scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
|
||||
scaleRequiredCount(ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
|
||||
}
|
||||
componentMiscItem.ItemCount += ingredientContribution.ItemCount;
|
||||
} else {
|
||||
@ -143,21 +133,16 @@ const processContribution = (
|
||||
ItemType: ingredientContribution.ItemType,
|
||||
ItemCount: ingredientContribution.ItemCount * -1
|
||||
});
|
||||
|
||||
addGuildMemberMiscItemContribution(guildMember, ingredientContribution);
|
||||
}
|
||||
addMiscItems(inventory, miscItemChanges);
|
||||
inventoryChanges.MiscItems = miscItemChanges;
|
||||
}
|
||||
|
||||
if (component.RegularCredits >= scaleRequiredCount(guild.Tier, meta.price)) {
|
||||
if (component.RegularCredits >= scaleRequiredCount(meta.price)) {
|
||||
let fullyFunded = true;
|
||||
for (const ingredient of meta.ingredients) {
|
||||
const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredient.ItemType);
|
||||
if (
|
||||
!componentMiscItem ||
|
||||
componentMiscItem.ItemCount < scaleRequiredCount(guild.Tier, ingredient.ItemCount)
|
||||
) {
|
||||
if (!componentMiscItem || componentMiscItem.ItemCount < scaleRequiredCount(ingredient.ItemCount)) {
|
||||
fullyFunded = false;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1,100 +1,43 @@
|
||||
import type { TGuildDatabaseDocument, TGuildMemberDatabaseDocument } from "../../models/guildModel.ts";
|
||||
import { Alliance, Guild, GuildMember } from "../../models/guildModel.ts";
|
||||
import {
|
||||
addGuildMemberMiscItemContribution,
|
||||
addGuildMemberShipDecoContribution,
|
||||
addVaultFusionTreasures,
|
||||
addVaultMiscItems,
|
||||
addVaultShipDecos,
|
||||
getGuildForRequestEx
|
||||
} from "../../services/guildService.ts";
|
||||
import {
|
||||
addFusionTreasures,
|
||||
addMiscItems,
|
||||
addShipDecorations,
|
||||
getInventory,
|
||||
updateCurrency
|
||||
} from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import type { ITypeCount } from "../../types/commonTypes.ts";
|
||||
import type { IFusionTreasure, IMiscItem } from "../../types/inventoryTypes/inventoryTypes.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
import { getGuildForRequestEx } from "@/src/services/guildService";
|
||||
import { addFusionTreasures, addMiscItems, addShipDecorations, getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { IFusionTreasure, IMiscItem, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const contributeToVaultController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "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;
|
||||
|
||||
if (request.Alliance) {
|
||||
const guild = await getGuildForRequestEx(req, inventory);
|
||||
const alliance = (await Alliance.findById(guild.AllianceId!))!;
|
||||
alliance.VaultRegularCredits ??= 0;
|
||||
alliance.VaultRegularCredits += request.RegularCredits;
|
||||
if (request.FromVault) {
|
||||
guild.VaultRegularCredits! -= request.RegularCredits;
|
||||
await Promise.all([guild.save(), alliance.save()]);
|
||||
} else {
|
||||
updateCurrency(inventory, request.RegularCredits, false);
|
||||
await Promise.all([inventory.save(), alliance.save()]);
|
||||
}
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
let guild: TGuildDatabaseDocument;
|
||||
let guildMember: TGuildMemberDatabaseDocument | undefined;
|
||||
if (request.GuildVault) {
|
||||
guild = (await Guild.findById(request.GuildVault))!;
|
||||
} else {
|
||||
guild = await getGuildForRequestEx(req, inventory);
|
||||
guildMember = (await GuildMember.findOne(
|
||||
{ accountId, guildId: guild._id },
|
||||
"RegularCreditsContributed MiscItemsContributed ShipDecorationsContributed"
|
||||
))!;
|
||||
}
|
||||
|
||||
if (request.RegularCredits) {
|
||||
updateCurrency(inventory, request.RegularCredits, false);
|
||||
|
||||
guild.VaultRegularCredits ??= 0;
|
||||
guild.VaultRegularCredits += request.RegularCredits;
|
||||
|
||||
if (guildMember) {
|
||||
guildMember.RegularCreditsContributed ??= 0;
|
||||
guildMember.RegularCreditsContributed += request.RegularCredits;
|
||||
}
|
||||
}
|
||||
if (request.MiscItems.length) {
|
||||
addVaultMiscItems(guild, request.MiscItems);
|
||||
guild.VaultMiscItems ??= [];
|
||||
for (const item of request.MiscItems) {
|
||||
if (guildMember) {
|
||||
addGuildMemberMiscItemContribution(guildMember, item);
|
||||
}
|
||||
guild.VaultMiscItems.push(item);
|
||||
addMiscItems(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
|
||||
}
|
||||
}
|
||||
if (request.ShipDecorations.length) {
|
||||
addVaultShipDecos(guild, request.ShipDecorations);
|
||||
guild.VaultShipDecorations ??= [];
|
||||
for (const item of request.ShipDecorations) {
|
||||
if (guildMember) {
|
||||
addGuildMemberShipDecoContribution(guildMember, item);
|
||||
}
|
||||
guild.VaultShipDecorations.push(item);
|
||||
addShipDecorations(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
|
||||
}
|
||||
}
|
||||
if (request.FusionTreasures.length) {
|
||||
addVaultFusionTreasures(guild, request.FusionTreasures);
|
||||
guild.VaultFusionTreasures ??= [];
|
||||
for (const item of request.FusionTreasures) {
|
||||
guild.VaultFusionTreasures.push(item);
|
||||
addFusionTreasures(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
|
||||
}
|
||||
}
|
||||
|
||||
const promises: Promise<unknown>[] = [guild.save(), inventory.save()];
|
||||
if (guildMember) {
|
||||
promises.push(guildMember.save());
|
||||
}
|
||||
await Promise.all(promises);
|
||||
|
||||
await guild.save();
|
||||
await inventory.save();
|
||||
res.end();
|
||||
};
|
||||
|
||||
@ -103,7 +46,4 @@ interface IContributeToVaultRequest {
|
||||
MiscItems: IMiscItem[];
|
||||
ShipDecorations: ITypeCount[];
|
||||
FusionTreasures: IFusionTreasure[];
|
||||
Alliance?: boolean;
|
||||
FromVault?: boolean;
|
||||
GuildVault?: string;
|
||||
}
|
||||
|
||||
@ -1,50 +0,0 @@
|
||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
|
||||
import { Alliance, AllianceMember, Guild, GuildMember } from "../../models/guildModel.ts";
|
||||
import { getAllianceClient } from "../../services/guildService.ts";
|
||||
import { getInventory } from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { GuildPermission } from "../../types/guildTypes.ts";
|
||||
import type { 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,30 +1,17 @@
|
||||
import type { RequestHandler } from "express";
|
||||
import { getAccountForRequest } from "../../services/loginService.ts";
|
||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
|
||||
import { Guild, GuildMember } from "../../models/guildModel.ts";
|
||||
import { createUniqueClanName, getGuildClient, giveClanKey } from "../../services/guildService.ts";
|
||||
import { getInventory } from "../../services/inventoryService.ts";
|
||||
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
|
||||
import { sendWsBroadcastTo } from "../../services/wsService.ts";
|
||||
import { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { Guild, GuildMember } from "@/src/models/guildModel";
|
||||
import {
|
||||
createUniqueClanName,
|
||||
getGuildClient,
|
||||
updateInventoryForConfirmedGuildJoin
|
||||
} from "@/src/services/guildService";
|
||||
|
||||
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 inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes");
|
||||
if (inventory.GuildId) {
|
||||
const guild = await Guild.findById(inventory.GuildId);
|
||||
if (guild) {
|
||||
res.json({
|
||||
...(await getGuildClient(guild, account))
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove pending applications for this account
|
||||
await GuildMember.deleteMany({ accountId: account._id, status: 1 });
|
||||
|
||||
// Create guild on database
|
||||
const guild = new Guild({
|
||||
Name: await createUniqueClanName(payload.guildName)
|
||||
@ -33,22 +20,15 @@ export const createGuildController: RequestHandler = async (req, res) => {
|
||||
|
||||
// Create guild member on database
|
||||
await GuildMember.insertOne({
|
||||
accountId: account._id,
|
||||
accountId: accountId,
|
||||
guildId: guild._id,
|
||||
status: 0,
|
||||
rank: 0
|
||||
});
|
||||
|
||||
inventory.GuildId = guild._id;
|
||||
const inventoryChanges: IInventoryChanges = {};
|
||||
giveClanKey(inventory, inventoryChanges);
|
||||
await inventory.save();
|
||||
await updateInventoryForConfirmedGuildJoin(accountId, guild._id);
|
||||
|
||||
res.json({
|
||||
...(await getGuildClient(guild, account)),
|
||||
InventoryChanges: inventoryChanges
|
||||
});
|
||||
sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
|
||||
res.json(await getGuildClient(guild, accountId));
|
||||
};
|
||||
|
||||
interface ICreateGuildRequest {
|
||||
|
||||
@ -1,17 +1,12 @@
|
||||
import type { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { getInventory } from "../../services/inventoryService.ts";
|
||||
import { RequestHandler } from "express";
|
||||
import { config } from "@/src/services/configService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
|
||||
export const creditsController: RequestHandler = async (req, res) => {
|
||||
const inventory = (
|
||||
await Promise.all([
|
||||
getAccountIdForRequest(req),
|
||||
getInventory(
|
||||
req.query.accountId as string,
|
||||
"RegularCredits TradesRemaining PremiumCreditsFree PremiumCredits infiniteCredits infinitePlatinum"
|
||||
)
|
||||
])
|
||||
)[1];
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
|
||||
const inventory = await getInventory(accountId, "RegularCredits TradesRemaining PremiumCreditsFree PremiumCredits");
|
||||
|
||||
const response = {
|
||||
RegularCredits: inventory.RegularCredits,
|
||||
@ -20,10 +15,10 @@ export const creditsController: RequestHandler = async (req, res) => {
|
||||
PremiumCredits: inventory.PremiumCredits
|
||||
};
|
||||
|
||||
if (inventory.infiniteCredits) {
|
||||
if (config.infiniteCredits) {
|
||||
response.RegularCredits = 999999999;
|
||||
}
|
||||
if (inventory.infinitePlatinum) {
|
||||
if (config.infinitePlatinum) {
|
||||
response.PremiumCreditsFree = 0;
|
||||
response.PremiumCredits = 999999999;
|
||||
}
|
||||
|
||||
@ -1,54 +0,0 @@
|
||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
|
||||
import type { TInventoryDatabaseDocument } from "../../models/inventoryModels/inventoryModel.ts";
|
||||
import { getInventory } from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import type { ICrewMemberClient } from "../../types/inventoryTypes/inventoryTypes.ts";
|
||||
import type { 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 "../../helpers/stringHelpers.ts";
|
||||
import { addMiscItems, freeUpSlot, getInventory, updateCurrency } from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import type { IOid } from "../../types/commonTypes.ts";
|
||||
import type { ICrewShipComponentFingerprint } from "../../types/inventoryTypes/inventoryTypes.ts";
|
||||
import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts";
|
||||
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
|
||||
import type { 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 !== undefined) {
|
||||
const useSuperiorSubroutine = tierA < tierB ? !payload.UseSubroutineA : payload.UseSubroutineA;
|
||||
if (!useSuperiorSubroutine) {
|
||||
fingerprint.SubroutineIndex = inferiorFingerprint.SubroutineIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
superiorItem.UpgradeFingerprint = JSON.stringify(fingerprint);
|
||||
inventoryChanges[category] = [superiorItem.toJSON() as any];
|
||||
|
||||
await inventory.save();
|
||||
res.json({
|
||||
InventoryChanges: inventoryChanges
|
||||
});
|
||||
};
|
||||
|
||||
interface ICrewShipFusionRequest {
|
||||
PartA: IOid;
|
||||
PartB: IOid;
|
||||
SourceRecipe: string;
|
||||
UseSubroutineA: boolean;
|
||||
}
|
||||
|
||||
const FUSE_MULTIPLIERS = [1.1, 1.05, 1.02];
|
||||
@ -1,87 +0,0 @@
|
||||
import {
|
||||
addCrewShipSalvagedWeaponSkin,
|
||||
addCrewShipRawSalvage,
|
||||
getInventory,
|
||||
addEquipment
|
||||
} from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
import type {
|
||||
ICrewShipComponentFingerprint,
|
||||
IInnateDamageFingerprint
|
||||
} from "../../types/inventoryTypes/inventoryTypes.ts";
|
||||
import { ExportCustoms, ExportRailjackWeapons, ExportUpgrades } from "warframe-public-export-plus";
|
||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
|
||||
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
|
||||
import { getRandomInt } from "../../services/rngService.ts";
|
||||
import type { IFingerprintStat } from "../../helpers/rivenHelper.ts";
|
||||
import type { IEquipmentDatabase } from "../../types/equipmentTypes.ts";
|
||||
|
||||
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,64 +0,0 @@
|
||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
|
||||
import { Guild } from "../../models/guildModel.ts";
|
||||
import { hasAccessToDojo, hasGuildPermission } from "../../services/guildService.ts";
|
||||
import { getInventory } from "../../services/inventoryService.ts";
|
||||
import { getAccountForRequest, getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { GuildPermission } from "../../types/guildTypes.ts";
|
||||
import { logger } from "../../utils/logger.ts";
|
||||
import type { 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 Ranks"))!;
|
||||
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 if (req.query.act == "c") {
|
||||
// TOVERIFY: What clan permission is actually needed for this?
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "GuildId LevelKeys");
|
||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Decorator))) {
|
||||
res.status(400).end();
|
||||
return;
|
||||
}
|
||||
|
||||
component.Leaderboard = undefined;
|
||||
await guild.save();
|
||||
|
||||
res.status(200).end();
|
||||
} else {
|
||||
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
||||
throw new Error(`unknown customObstacleCourseLeaderboard act: ${String(req.query.act)}`);
|
||||
}
|
||||
};
|
||||
|
||||
interface ICustomObstacleCourseLeaderboardRequest {
|
||||
g: string;
|
||||
c: string;
|
||||
s?: number; // act=p
|
||||
}
|
||||
@ -1,8 +1,7 @@
|
||||
import { getGuildForRequest, hasGuildPermission } from "../../services/guildService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import type { IGuildRank } from "../../types/guildTypes.ts";
|
||||
import { GuildPermission } from "../../types/guildTypes.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
import { getGuildForRequest, hasGuildPermission } from "@/src/services/guildService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { GuildPermission, IGuildRank } from "@/src/types/guildTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const customizeGuildRanksController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
import { AllianceMember, GuildMember } from "../../models/guildModel.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import type { 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,6 +1,6 @@
|
||||
import { GuildMember } from "../../models/guildModel.ts";
|
||||
import { getAccountForRequest } from "../../services/loginService.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
import { GuildMember } from "@/src/models/guildModel";
|
||||
import { getAccountForRequest } from "@/src/services/loginService";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const declineGuildInviteController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountForRequest(req);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { RequestHandler } from "express";
|
||||
import { deleteSession } from "../../managers/sessionManager.ts";
|
||||
import { RequestHandler } from "express";
|
||||
import { deleteSession } from "@/src/managers/sessionManager";
|
||||
|
||||
const deleteSessionController: RequestHandler = (_req, res) => {
|
||||
deleteSession(_req.query.sessionId as string);
|
||||
|
||||
@ -3,14 +3,12 @@ import {
|
||||
getGuildForRequestEx,
|
||||
hasAccessToDojo,
|
||||
hasGuildPermission,
|
||||
refundDojoDeco,
|
||||
removeDojoDeco
|
||||
} from "../../services/guildService.ts";
|
||||
import { getInventory } from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { GuildPermission } from "../../types/guildTypes.ts";
|
||||
import { logger } from "../../utils/logger.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
} 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 destroyDojoDecoController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
@ -20,20 +18,9 @@ export const destroyDojoDecoController: RequestHandler = async (req, res) => {
|
||||
res.json({ DojoRequestStatus: -1 });
|
||||
return;
|
||||
}
|
||||
const request = JSON.parse(String(req.body)) as IDestroyDojoDecoRequest | IClearObstacleCourseRequest;
|
||||
if ("DecoType" in request) {
|
||||
removeDojoDeco(guild, request.ComponentId, request.DecoId);
|
||||
} else if (request.Act == "cObst") {
|
||||
const component = guild.DojoComponents.id(request.ComponentId)!;
|
||||
if (component.Decos) {
|
||||
for (const deco of component.Decos) {
|
||||
refundDojoDeco(guild, component, deco);
|
||||
}
|
||||
component.Decos.splice(0, component.Decos.length);
|
||||
}
|
||||
} else {
|
||||
logger.error(`unhandled destroyDojoDeco request`, request);
|
||||
}
|
||||
const request = JSON.parse(String(req.body)) as IDestroyDojoDecoRequest;
|
||||
|
||||
removeDojoDeco(guild, request.ComponentId, request.DecoId);
|
||||
|
||||
await guild.save();
|
||||
res.json(await getDojoClient(guild, 0, request.ComponentId));
|
||||
@ -44,8 +31,3 @@ interface IDestroyDojoDecoRequest {
|
||||
ComponentId: string;
|
||||
DecoId: string;
|
||||
}
|
||||
|
||||
interface IClearObstacleCourseRequest {
|
||||
ComponentId: string;
|
||||
Act: "cObst" | "maybesomethingelsewedontknowabout";
|
||||
}
|
||||
|
||||
@ -1,67 +0,0 @@
|
||||
import { Alliance, AllianceMember, Guild, GuildMember } from "../../models/guildModel.ts";
|
||||
import { getAccountForRequest } from "../../services/loginService.ts";
|
||||
import { GuildPermission } from "../../types/guildTypes.ts";
|
||||
import { parallelForeach } from "../../utils/async-utils.ts";
|
||||
import { logger } from "../../utils/logger.ts";
|
||||
import type { 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,17 +1,9 @@
|
||||
import type { TGuildDatabaseDocument } from "../../models/guildModel.ts";
|
||||
import { GuildMember } from "../../models/guildModel.ts";
|
||||
import {
|
||||
getDojoClient,
|
||||
getGuildForRequestEx,
|
||||
hasAccessToDojo,
|
||||
scaleRequiredCount
|
||||
} from "../../services/guildService.ts";
|
||||
import { getInventory, updateCurrency } from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import type { IDojoContributable } from "../../types/guildTypes.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
import type { IDojoBuild } from "warframe-public-export-plus";
|
||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
|
||||
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, scaleRequiredCount } from "@/src/services/guildService";
|
||||
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { IDojoContributable } from "@/src/types/guildTypes";
|
||||
import { RequestHandler } from "express";
|
||||
import { ExportDojoRecipes, IDojoBuild } from "warframe-public-export-plus";
|
||||
|
||||
interface IDojoComponentRushRequest {
|
||||
DecoType?: string;
|
||||
@ -43,10 +35,10 @@ export const dojoComponentRushController: RequestHandler = async (req, res) => {
|
||||
if (request.DecoId) {
|
||||
const deco = component.Decos!.find(x => x._id.equals(request.DecoId))!;
|
||||
const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == deco.Type)!;
|
||||
processContribution(guild, deco, meta, platinumDonated);
|
||||
processContribution(deco, meta, platinumDonated);
|
||||
} else {
|
||||
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));
|
||||
if (entry) {
|
||||
@ -54,25 +46,16 @@ export const dojoComponentRushController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
}
|
||||
|
||||
const guildMember = (await GuildMember.findOne({ accountId, guildId: guild._id }, "PremiumCreditsContributed"))!;
|
||||
guildMember.PremiumCreditsContributed ??= 0;
|
||||
guildMember.PremiumCreditsContributed += request.Amount;
|
||||
|
||||
await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
|
||||
|
||||
await guild.save();
|
||||
await inventory.save();
|
||||
res.json({
|
||||
...(await getDojoClient(guild, 0, component._id)),
|
||||
InventoryChanges: inventoryChanges
|
||||
});
|
||||
};
|
||||
|
||||
const processContribution = (
|
||||
guild: TGuildDatabaseDocument,
|
||||
component: IDojoContributable,
|
||||
meta: IDojoBuild,
|
||||
platinumDonated: number
|
||||
): void => {
|
||||
const fullPlatinumCost = scaleRequiredCount(guild.Tier, meta.skipTimePrice);
|
||||
const processContribution = (component: IDojoContributable, meta: IDojoBuild, platinumDonated: number): void => {
|
||||
const fullPlatinumCost = scaleRequiredCount(meta.skipTimePrice);
|
||||
const fullDurationSeconds = meta.time;
|
||||
const secondsPerPlatinum = fullDurationSeconds / fullPlatinumCost;
|
||||
component.CompletionTime = new Date(
|
||||
|
||||
@ -1,11 +1,5 @@
|
||||
import type { RequestHandler } from "express";
|
||||
|
||||
// Arbiter Dojo endpoints, not really used by us as we don't provide a ContentURL.
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const dojoController: RequestHandler = (_req, res) => {
|
||||
res.json("-1"); // Tell client to use authorised request.
|
||||
};
|
||||
|
||||
export const setDojoURLController: RequestHandler = (_req, res) => {
|
||||
res.end();
|
||||
};
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
import { toMongoDate, toOid } from "../../helpers/inventoryHelpers.ts";
|
||||
import { addMiscItems, getInventory } from "../../services/inventoryService.ts";
|
||||
import { fromStoreItem } from "../../services/itemDataService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { getRandomInt, getRandomWeightedRewardUc } from "../../services/rngService.ts";
|
||||
import type { IMongoDate, IOid } from "../../types/commonTypes.ts";
|
||||
import type { IDroneClient } from "../../types/inventoryTypes/inventoryTypes.ts";
|
||||
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
|
||||
import { config } from "@/src/services/configService";
|
||||
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
||||
import { fromStoreItem } from "@/src/services/itemDataService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getRandomInt, getRandomWeightedRewardUc } from "@/src/services/rngService";
|
||||
import { IMongoDate, IOid } from "@/src/types/commonTypes";
|
||||
import { IDroneClient } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { RequestHandler } from "express";
|
||||
import { ExportDrones, ExportResources, ExportSystems } from "warframe-public-export-plus";
|
||||
|
||||
export const dronesController: RequestHandler = async (req, res) => {
|
||||
@ -38,13 +39,10 @@ export const dronesController: RequestHandler = async (req, res) => {
|
||||
ActiveDrones: activeDrones
|
||||
});
|
||||
} else if ("droneId" in req.query && "systemIndex" in req.query) {
|
||||
const inventory = await getInventory(
|
||||
accountId,
|
||||
"Drones instantResourceExtractorDrones noResourceExtractorDronesDamage"
|
||||
);
|
||||
const inventory = await getInventory(accountId, "Drones");
|
||||
const drone = inventory.Drones.id(req.query.droneId as string)!;
|
||||
const droneMeta = ExportDrones[drone.ItemType];
|
||||
drone.DeployTime = inventory.instantResourceExtractorDrones ? new Date(0) : new Date();
|
||||
drone.DeployTime = config.instantResourceExtractorDrones ? new Date(0) : new Date();
|
||||
if (drone.RepairStart) {
|
||||
const repairMinutes = (Date.now() - drone.RepairStart.getTime()) / 60_000;
|
||||
const hpPerMinute = droneMeta.repairRate / 60;
|
||||
@ -53,11 +51,11 @@ export const dronesController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
drone.System = parseInt(req.query.systemIndex as string);
|
||||
const system = ExportSystems[drone.System - 1];
|
||||
drone.DamageTime = inventory.instantResourceExtractorDrones
|
||||
drone.DamageTime = config.instantResourceExtractorDrones
|
||||
? new Date()
|
||||
: new Date(Date.now() + getRandomInt(3 * 3600 * 1000, 4 * 3600 * 1000));
|
||||
drone.PendingDamage =
|
||||
!inventory.noResourceExtractorDronesDamage && Math.random() < system.damageChance
|
||||
Math.random() < system.damageChance
|
||||
? getRandomInt(system.droneDamage.minValue, system.droneDamage.maxValue)
|
||||
: 0;
|
||||
const resource = getRandomWeightedRewardUc(system.resources, droneMeta.probabilities)!;
|
||||
@ -74,7 +72,7 @@ export const dronesController: RequestHandler = async (req, res) => {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
drone.ResourceCount = droneMeta.binCapacity * droneMeta.capacityMultipliers[resource.Rarity];
|
||||
drone.ResourceCount = 1;
|
||||
}
|
||||
await inventory.save();
|
||||
res.json({});
|
||||
|
||||
@ -1,534 +1,60 @@
|
||||
import type { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { combineInventoryChanges, getInventory } from "../../services/inventoryService.ts";
|
||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
|
||||
import type {
|
||||
IEndlessXpReward,
|
||||
IInventoryClient,
|
||||
TEndlessXpCategory
|
||||
} from "../../types/inventoryTypes/inventoryTypes.ts";
|
||||
import { logger } from "../../utils/logger.ts";
|
||||
import type { ICountedStoreItem } from "warframe-public-export-plus";
|
||||
import { ExportRewards } from "warframe-public-export-plus";
|
||||
import { getRandomElement } from "../../services/rngService.ts";
|
||||
import { handleStoreItemAcquisition } from "../../services/purchaseService.ts";
|
||||
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
|
||||
import { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { TEndlessXpCategory } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
|
||||
export const endlessXpController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId);
|
||||
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;
|
||||
const weekEnd = weekStart + 604800000;
|
||||
|
||||
entry.Earn = 0;
|
||||
entry.Claim = 0;
|
||||
entry.BonusAvailable = new Date(weekStart);
|
||||
entry.Expiry = new Date(weekEnd);
|
||||
inventory.EndlessXP ??= [];
|
||||
const entry = inventory.EndlessXP.find(x => x.Category == payload.Category);
|
||||
if (entry) {
|
||||
entry.Choices = payload.Choices;
|
||||
entry.PendingRewards =
|
||||
payload.Category == "EXC_HARD"
|
||||
? generateHardModeRewards(payload.Choices)
|
||||
: generateNormalModeRewards(payload.Choices);
|
||||
|
||||
await inventory.save();
|
||||
res.json({
|
||||
NewProgress: inventory.toJSON<IInventoryClient>().EndlessXP!.find(x => x.Category == payload.Category)!
|
||||
});
|
||||
} else if (payload.Mode == "c") {
|
||||
const inventory = await getInventory(accountId);
|
||||
const entry = inventory.EndlessXP!.find(x => x.Category == payload.Category)!;
|
||||
const inventoryChanges: IInventoryChanges = {};
|
||||
for (const reward of entry.PendingRewards) {
|
||||
if (entry.Claim < reward.RequiredTotalXp && reward.RequiredTotalXp <= entry.Earn) {
|
||||
combineInventoryChanges(
|
||||
inventoryChanges,
|
||||
(
|
||||
await handleStoreItemAcquisition(
|
||||
reward.Rewards[0].StoreItem,
|
||||
inventory,
|
||||
reward.Rewards[0].ItemCount
|
||||
)
|
||||
).InventoryChanges
|
||||
);
|
||||
}
|
||||
}
|
||||
entry.Claim = entry.Earn;
|
||||
await inventory.save();
|
||||
res.json({
|
||||
InventoryChanges: inventoryChanges,
|
||||
ClaimedXp: entry.Claim
|
||||
});
|
||||
} else {
|
||||
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
||||
throw new Error(`unexpected endlessXp mode: ${payload.Mode}`);
|
||||
inventory.EndlessXP.push({
|
||||
Category: payload.Category,
|
||||
Choices: payload.Choices
|
||||
});
|
||||
}
|
||||
};
|
||||
await inventory.save();
|
||||
|
||||
type IEndlessXpRequest =
|
||||
| {
|
||||
Mode: "r";
|
||||
Category: TEndlessXpCategory;
|
||||
Choices: string[];
|
||||
}
|
||||
| {
|
||||
Mode: "c" | "something else";
|
||||
Category: TEndlessXpCategory;
|
||||
};
|
||||
|
||||
const generateRandomRewards = (deckName: string): ICountedStoreItem[] => {
|
||||
const reward = getRandomElement(ExportRewards[deckName][0])!;
|
||||
return [
|
||||
{
|
||||
StoreItem: reward.type,
|
||||
ItemCount: reward.itemCount
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
const normalModeChosenRewards: Record<string, string[]> = {
|
||||
Excalibur: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Excalibur/RadialJavelinAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburBlueprint"
|
||||
],
|
||||
Trinity: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinityHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinityChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Trinity/EnergyVampireAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinitySystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinityBlueprint"
|
||||
],
|
||||
Ember: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Ember/WorldOnFireAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberBlueprint"
|
||||
],
|
||||
Loki: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKIHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKIChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Loki/InvisibilityAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKISystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKIBlueprint"
|
||||
],
|
||||
Mag: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Mag/CrushAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagBlueprint"
|
||||
],
|
||||
Rhino: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Rhino/RhinoChargeAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoBlueprint"
|
||||
],
|
||||
Ash: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Ninja/GlaiveAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshBlueprint"
|
||||
],
|
||||
Frost: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Frost/IceShieldAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostBlueprint"
|
||||
],
|
||||
Nyx: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Jade/SelfBulletAttractorAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxBlueprint"
|
||||
],
|
||||
Saryn: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Saryn/PoisonAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynBlueprint"
|
||||
],
|
||||
Vauban: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Trapper/LevTrapAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperBlueprint"
|
||||
],
|
||||
Nova: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/AntiMatter/MolecularPrimeAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaBlueprint"
|
||||
],
|
||||
Nekros: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Necro/CloneTheDeadAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroBlueprint"
|
||||
],
|
||||
Valkyr: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Berserker/IntimidateAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerBlueprint"
|
||||
],
|
||||
Oberon: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Paladin/RegenerationAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinBlueprint"
|
||||
],
|
||||
Hydroid: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Pirate/CannonBarrageAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidBlueprint"
|
||||
],
|
||||
Mirage: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Harlequin/LightAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinBlueprint"
|
||||
],
|
||||
Limbo: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Magician/TearInSpaceAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianBlueprint"
|
||||
],
|
||||
Mesa: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Cowgirl/GunFuPvPAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerBlueprint"
|
||||
],
|
||||
Chroma: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Dragon/DragonLuckAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaBlueprint"
|
||||
],
|
||||
Atlas: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Brawler/BrawlerPassiveAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerBlueprint"
|
||||
],
|
||||
Ivara: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Ranger/RangerStealAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerBlueprint"
|
||||
],
|
||||
Inaros: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummyHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummyChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Sandman/SandmanSwarmAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummySystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummyBlueprint"
|
||||
],
|
||||
Titania: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairyHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairyChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Fairy/FairyFlightAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairySystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairyBlueprint"
|
||||
],
|
||||
Nidus: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Infestation/InfestPodsAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusBlueprint"
|
||||
],
|
||||
Octavia: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Bard/BardCharmAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaBlueprint"
|
||||
],
|
||||
Harrow: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Priest/PriestPactAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestBlueprint"
|
||||
],
|
||||
Gara: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Glass/GlassFragmentAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassBlueprint"
|
||||
],
|
||||
Khora: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Khora/KhoraCrackAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraBlueprint"
|
||||
],
|
||||
Revenant: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Revenant/RevenantMarkAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantBlueprint"
|
||||
],
|
||||
Garuda: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Garuda/GarudaUnstoppableAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaBlueprint"
|
||||
],
|
||||
Baruuk: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Pacifist/PacifistFistAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistBlueprint"
|
||||
],
|
||||
Hildryn: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/IronFrame/IronFrameStripAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeBlueprint"
|
||||
]
|
||||
};
|
||||
|
||||
const generateNormalModeRewards = (choices: string[]): IEndlessXpReward[] => {
|
||||
const choiceRewards = normalModeChosenRewards[choices[0]];
|
||||
return [
|
||||
{
|
||||
RequiredTotalXp: 190,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalSilverRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 400,
|
||||
Rewards: [
|
||||
{
|
||||
StoreItem: choiceRewards[0],
|
||||
ItemCount: 1
|
||||
res.json({
|
||||
NewProgress: {
|
||||
Category: payload.Category,
|
||||
Earn: 0,
|
||||
Claim: 0,
|
||||
BonusAvailable: {
|
||||
$date: {
|
||||
$numberLong: "9999999999999"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 630,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalSilverRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 890,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalMODRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 1190,
|
||||
Rewards: [
|
||||
{
|
||||
StoreItem: choiceRewards[1],
|
||||
ItemCount: 1
|
||||
},
|
||||
Expiry: {
|
||||
$date: {
|
||||
$numberLong: "9999999999999"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 1540,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalGoldRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 1950,
|
||||
Rewards: [
|
||||
},
|
||||
Choices: payload.Choices,
|
||||
PendingRewards: [
|
||||
{
|
||||
StoreItem: choiceRewards[2],
|
||||
ItemCount: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 2430,
|
||||
Rewards: [
|
||||
{
|
||||
StoreItem: choiceRewards[3],
|
||||
ItemCount: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 2990,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalArcaneRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 3640,
|
||||
Rewards: [
|
||||
{
|
||||
StoreItem: choiceRewards[4],
|
||||
ItemCount: 1
|
||||
RequiredTotalXp: 190,
|
||||
Rewards: [
|
||||
{
|
||||
StoreItem: "/Lotus/StoreItems/Upgrades/Mods/Aura/PlayerHealthAuraMod",
|
||||
ItemCount: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
// ...
|
||||
]
|
||||
}
|
||||
];
|
||||
});
|
||||
};
|
||||
|
||||
const hardModeChosenRewards: Record<string, string> = {
|
||||
Braton: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BratonIncarnonUnlocker",
|
||||
Lato: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/LatoIncarnonUnlocker",
|
||||
Skana: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/SkanaIncarnonUnlocker",
|
||||
Paris: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/ParisIncarnonUnlocker",
|
||||
Kunai: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/KunaiIncarnonUnlocker",
|
||||
Boar: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BoarIncarnonUnlocker",
|
||||
Gammacor: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/GammacorIncarnonUnlocker",
|
||||
Anku: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/AnkuIncarnonUnlocker",
|
||||
Gorgon: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/GorgonIncarnonUnlocker",
|
||||
Angstrum: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/AngstrumIncarnonUnlocker",
|
||||
Bo: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/BoIncarnonUnlocker",
|
||||
Latron: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/LatronIncarnonUnlocker",
|
||||
Furis: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/FurisIncarnonUnlocker",
|
||||
Furax: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/FuraxIncarnonUnlocker",
|
||||
Strun: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/StrunIncarnonUnlocker",
|
||||
Lex: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/LexIncarnonUnlocker",
|
||||
Magistar: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/MagistarIncarnonUnlocker",
|
||||
Boltor: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BoltorIncarnonUnlocker",
|
||||
Bronco: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/BroncoIncarnonUnlocker",
|
||||
CeramicDagger: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/CeramicDaggerIncarnonUnlocker",
|
||||
Torid: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/ToridIncarnonUnlocker",
|
||||
DualToxocyst: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/DualToxocystIncarnonUnlocker",
|
||||
DualIchor: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/DualIchorIncarnonUnlocker",
|
||||
Miter: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/MiterIncarnonUnlocker",
|
||||
Atomos: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/AtomosIncarnonUnlocker",
|
||||
AckAndBrunt: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/AckAndBruntIncarnonUnlocker",
|
||||
Soma: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/SomaIncarnonUnlocker",
|
||||
Vasto: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/VastoIncarnonUnlocker",
|
||||
NamiSolo: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/NamiSoloIncarnonUnlocker",
|
||||
Burston: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BurstonIncarnonUnlocker",
|
||||
Zylok: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/ZylokIncarnonUnlocker",
|
||||
Sibear: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/SibearIncarnonUnlocker",
|
||||
Dread: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/DreadIncarnonUnlocker",
|
||||
Despair: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/DespairIncarnonUnlocker",
|
||||
Hate: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/HateIncarnonUnlocker",
|
||||
Dera: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/DeraIncarnonUnlocker",
|
||||
Cestra: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/CestraIncarnonUnlocker",
|
||||
Okina: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/OkinaIncarnonUnlocker",
|
||||
Sybaris: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/SybarisIncarnonUnlocker",
|
||||
Sicarus: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/SicarusIncarnonUnlocker",
|
||||
RivenPrimary: "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawRifleRandomMod",
|
||||
RivenSecondary: "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawPistolRandomMod",
|
||||
RivenMelee: "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawMeleeRandomMod",
|
||||
Kuva: "/Lotus/Types/Game/DuviriEndless/CircuitSteelPathBIGKuvaReward"
|
||||
};
|
||||
|
||||
const generateHardModeRewards = (choices: string[]): IEndlessXpReward[] => {
|
||||
return [
|
||||
{
|
||||
RequiredTotalXp: 285,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSilverRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 600,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathArcaneRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 945,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSilverRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 1335,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSilverRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 1785,
|
||||
Rewards: [
|
||||
{
|
||||
StoreItem: hardModeChosenRewards[choices[0]],
|
||||
ItemCount: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 2310,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathGoldRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 2925,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathGoldRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 3645,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathArcaneRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 4485,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSteelEssenceRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 5460,
|
||||
Rewards: [
|
||||
{
|
||||
StoreItem: hardModeChosenRewards[choices[1]],
|
||||
ItemCount: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
};
|
||||
interface IEndlessXpRequest {
|
||||
Mode: string; // "r"
|
||||
Category: TEndlessXpCategory;
|
||||
Choices: string[];
|
||||
}
|
||||
|
||||
@ -1,52 +0,0 @@
|
||||
import { toMongoDate } from "../../helpers/inventoryHelpers.ts";
|
||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
|
||||
import { getInventory, updateEntratiVault } from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import type { 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));
|
||||
updateEntratiVault(inventory);
|
||||
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[];
|
||||
}
|
||||
@ -1,10 +1,9 @@
|
||||
import type { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { addMiscItems, getInventory } from "../../services/inventoryService.ts";
|
||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
|
||||
import type { WeaponTypeInternal } from "../../services/itemDataService.ts";
|
||||
import { getRecipe } from "../../services/itemDataService.ts";
|
||||
import { EquipmentFeatures } from "../../types/equipmentTypes.ts";
|
||||
import { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getRecipe, WeaponTypeInternal } from "@/src/services/itemDataService";
|
||||
import { EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
|
||||
export const evolveWeaponController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
@ -18,7 +17,7 @@ export const evolveWeaponController: RequestHandler = async (req, res) => {
|
||||
recipe.ingredients.map(x => ({ ItemType: x.ItemType, ItemCount: x.ItemCount * -1 }))
|
||||
);
|
||||
|
||||
const item = inventory[payload.Category].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 |= EquipmentFeatures.INCARNON_GENESIS;
|
||||
|
||||
@ -40,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;
|
||||
} else {
|
||||
throw new Error(`unexpected evolve weapon action: ${payload.Action}`);
|
||||
|
||||
@ -1,62 +0,0 @@
|
||||
import type { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { addMiscItem, getInventory } from "../../services/inventoryService.ts";
|
||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
|
||||
import { logger } from "../../utils/logger.ts";
|
||||
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
|
||||
|
||||
export const feedPrinceController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "MiscItems NokkoColony NodeIntrosCompleted");
|
||||
const payload = getJSONfromString<IFeedPrinceRequest>(String(req.body));
|
||||
|
||||
switch (payload.Mode) {
|
||||
case "r": {
|
||||
inventory.NokkoColony ??= {
|
||||
FeedLevel: 0,
|
||||
JournalEntries: []
|
||||
};
|
||||
const InventoryChanges: IInventoryChanges = {};
|
||||
inventory.NokkoColony.FeedLevel += payload.Amount;
|
||||
if (
|
||||
(!inventory.NodeIntrosCompleted.includes("CompletedVision1") && inventory.NokkoColony.FeedLevel > 20) ||
|
||||
(!inventory.NodeIntrosCompleted.includes("CompletedVision2") && inventory.NokkoColony.FeedLevel > 60)
|
||||
) {
|
||||
res.json({
|
||||
FeedSucceeded: false,
|
||||
FeedLevel: inventory.NokkoColony.FeedLevel - payload.Amount,
|
||||
InventoryChanges
|
||||
} satisfies IFeedPrinceResponse);
|
||||
} else {
|
||||
addMiscItem(
|
||||
inventory,
|
||||
"/Lotus/Types/Items/MiscItems/MushroomFood",
|
||||
payload.Amount * -1,
|
||||
InventoryChanges
|
||||
);
|
||||
await inventory.save();
|
||||
res.json({
|
||||
FeedSucceeded: true,
|
||||
FeedLevel: inventory.NokkoColony.FeedLevel,
|
||||
InventoryChanges
|
||||
} satisfies IFeedPrinceResponse);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
||||
throw new Error(`unknown feedPrince mode: ${payload.Mode}`);
|
||||
}
|
||||
};
|
||||
|
||||
interface IFeedPrinceRequest {
|
||||
Mode: string; // r
|
||||
Amount: number;
|
||||
}
|
||||
|
||||
interface IFeedPrinceResponse {
|
||||
FeedSucceeded: boolean;
|
||||
FeedLevel: number;
|
||||
InventoryChanges: IInventoryChanges;
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import type { RequestHandler } from "express";
|
||||
import { getSession } from "../../managers/sessionManager.ts";
|
||||
import { logger } from "../../utils/logger.ts";
|
||||
import type { IFindSessionRequest } from "../../types/session.ts";
|
||||
import { RequestHandler } from "express";
|
||||
import { getSession } from "@/src/managers/sessionManager";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { IFindSessionRequest } from "@/src/types/session";
|
||||
|
||||
export const findSessionsController: RequestHandler = (_req, res) => {
|
||||
const req = JSON.parse(String(_req.body)) as IFindSessionRequest;
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
|
||||
import { addMiscItems, addStanding, getInventory } from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import type { IMiscItem } from "../../types/inventoryTypes/inventoryTypes.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
import { ExportResources } from "warframe-public-export-plus";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper";
|
||||
import { addMiscItems, getInventory, getStandingLimit, updateStandingLimit } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { RequestHandler } from "express";
|
||||
import { ExportResources, ExportSyndicates } from "warframe-public-export-plus";
|
||||
|
||||
export const fishmongerController: RequestHandler = async (req, res) => {
|
||||
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 });
|
||||
}
|
||||
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();
|
||||
res.json({
|
||||
InventoryChanges: {
|
||||
|
||||
@ -1,100 +1,34 @@
|
||||
import type { RequestHandler } from "express";
|
||||
import { getAccountForRequest } from "../../services/loginService.ts";
|
||||
import { getInventory, addMiscItems, addEquipment, occupySlot } from "../../services/inventoryService.ts";
|
||||
import type { IMiscItem, TFocusPolarity, TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
|
||||
import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts";
|
||||
import { logger } from "../../utils/logger.ts";
|
||||
import { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getInventory, addMiscItems, addEquipment, occupySlot } from "@/src/services/inventoryService";
|
||||
import { IMiscItem, TFocusPolarity, TEquipmentKey, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { ExportFocusUpgrades } from "warframe-public-export-plus";
|
||||
import { Inventory } from "../../models/inventoryModels/inventoryModel.ts";
|
||||
import { version_compare } from "../../helpers/inventoryHelpers.ts";
|
||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
||||
|
||||
export const focusController: RequestHandler = async (req, res) => {
|
||||
const account = await getAccountForRequest(req);
|
||||
|
||||
let op = req.query.op as string;
|
||||
const focus2 = account.BuildLabel && version_compare(account.BuildLabel, "2022.04.29.12.53") < 0;
|
||||
if (focus2) {
|
||||
// Focus 2.0
|
||||
switch (req.query.op) {
|
||||
case Focus2Operation.InstallLens:
|
||||
op = "InstallLens";
|
||||
break;
|
||||
case Focus2Operation.UnlockWay:
|
||||
op = "UnlockWay";
|
||||
break;
|
||||
case Focus2Operation.UnlockUpgrade:
|
||||
op = "UnlockUpgrade";
|
||||
break;
|
||||
case Focus2Operation.IncreasePool:
|
||||
op = "IncreasePool";
|
||||
break;
|
||||
case Focus2Operation.LevelUpUpgrade:
|
||||
op = "LevelUpUpgrade";
|
||||
break;
|
||||
case Focus2Operation.ActivateWay:
|
||||
op = "ActivateWay";
|
||||
break;
|
||||
case Focus2Operation.UpdateUpgrade:
|
||||
op = "UpdateUpgrade";
|
||||
break;
|
||||
case Focus2Operation.SentTrainingAmplifier:
|
||||
op = "SentTrainingAmplifier";
|
||||
break;
|
||||
case Focus2Operation.UnbindUpgrade:
|
||||
op = "UnbindUpgrade";
|
||||
break;
|
||||
case Focus2Operation.ConvertShard:
|
||||
op = "ConvertShard";
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Focus 3.0
|
||||
switch (req.query.op) {
|
||||
case Focus3Operation.InstallLens:
|
||||
op = "InstallLens";
|
||||
break;
|
||||
case Focus3Operation.UnlockWay:
|
||||
op = "UnlockWay";
|
||||
break;
|
||||
case Focus3Operation.UnlockUpgrade:
|
||||
op = "UnlockUpgrade";
|
||||
break;
|
||||
case Focus3Operation.LevelUpUpgrade:
|
||||
op = "LevelUpUpgrade";
|
||||
break;
|
||||
case Focus3Operation.ActivateWay:
|
||||
op = "ActivateWay";
|
||||
break;
|
||||
case Focus3Operation.SentTrainingAmplifier:
|
||||
op = "SentTrainingAmplifier";
|
||||
break;
|
||||
case Focus3Operation.UnbindUpgrade:
|
||||
op = "UnbindUpgrade";
|
||||
break;
|
||||
case Focus3Operation.ConvertShard:
|
||||
op = "ConvertShard";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (op) {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
switch (req.query.op) {
|
||||
default:
|
||||
logger.error("Unhandled focus op type: " + String(req.query.op));
|
||||
logger.debug(String(req.body));
|
||||
res.end();
|
||||
break;
|
||||
case "InstallLens": {
|
||||
case FocusOperation.InstallLens: {
|
||||
const request = JSON.parse(String(req.body)) as ILensInstallRequest;
|
||||
const inventory = await getInventory(account._id.toString());
|
||||
const item = inventory[request.Category].id(request.WeaponId);
|
||||
if (item) {
|
||||
item.FocusLens = request.LensType;
|
||||
addMiscItems(inventory, [
|
||||
{
|
||||
ItemType: request.LensType,
|
||||
ItemCount: -1
|
||||
} satisfies IMiscItem
|
||||
]);
|
||||
const inventory = await getInventory(accountId);
|
||||
for (const item of inventory[request.Category]) {
|
||||
if (item._id.toString() == request.WeaponId) {
|
||||
item.FocusLens = request.LensType;
|
||||
addMiscItems(inventory, [
|
||||
{
|
||||
ItemType: request.LensType,
|
||||
ItemCount: -1
|
||||
} satisfies IMiscItem
|
||||
]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
await inventory.save();
|
||||
res.json({
|
||||
@ -103,15 +37,15 @@ export const focusController: RequestHandler = async (req, res) => {
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "UnlockWay": {
|
||||
case FocusOperation.UnlockWay: {
|
||||
const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType;
|
||||
const focusPolarity = focusTypeToPolarity(focusType);
|
||||
const inventory = await getInventory(account._id.toString(), "FocusAbility FocusUpgrades FocusXP");
|
||||
const inventory = await getInventory(accountId);
|
||||
const cost = inventory.FocusAbility ? 50_000 : 0;
|
||||
inventory.FocusAbility ??= focusType;
|
||||
inventory.FocusUpgrades.push({ ItemType: focusType });
|
||||
if (cost) {
|
||||
inventory.FocusXP![focusPolarity]! -= cost;
|
||||
if (inventory.FocusXP) {
|
||||
inventory.FocusXP[focusPolarity] -= cost;
|
||||
}
|
||||
await inventory.save();
|
||||
res.json({
|
||||
@ -120,57 +54,31 @@ export const focusController: RequestHandler = async (req, res) => {
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "IncreasePool": {
|
||||
const request = JSON.parse(String(req.body)) as IIncreasePoolRequest;
|
||||
const focusPolarity = focusTypeToPolarity(request.FocusType);
|
||||
const inventory = await getInventory(account._id.toString(), "FocusXP FocusCapacity");
|
||||
let cost = 0;
|
||||
for (let capacity = request.CurrentTotalCapacity; capacity != request.NewTotalCapacity; ++capacity) {
|
||||
cost += increasePoolCost[capacity - 5];
|
||||
}
|
||||
inventory.FocusXP![focusPolarity]! -= cost;
|
||||
inventory.FocusCapacity = request.NewTotalCapacity;
|
||||
await inventory.save();
|
||||
res.json({
|
||||
TotalCapacity: request.NewTotalCapacity,
|
||||
FocusPointCosts: { [focusPolarity]: cost }
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "ActivateWay": {
|
||||
case FocusOperation.ActivateWay: {
|
||||
const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType;
|
||||
|
||||
await Inventory.updateOne(
|
||||
{
|
||||
accountOwnerId: account._id.toString()
|
||||
accountOwnerId: accountId
|
||||
},
|
||||
{
|
||||
FocusAbility: focusType
|
||||
}
|
||||
);
|
||||
|
||||
res.json({
|
||||
FocusUpgrade: { ItemType: focusType }
|
||||
});
|
||||
res.end();
|
||||
break;
|
||||
}
|
||||
case "UnlockUpgrade": {
|
||||
case FocusOperation.UnlockUpgrade: {
|
||||
const request = JSON.parse(String(req.body)) as IUnlockUpgradeRequest;
|
||||
const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]);
|
||||
const inventory = await getInventory(account._id.toString());
|
||||
const inventory = await getInventory(accountId);
|
||||
let cost = 0;
|
||||
for (const focusType of request.FocusTypes) {
|
||||
if (focusType in ExportFocusUpgrades) {
|
||||
cost += ExportFocusUpgrades[focusType].baseFocusPointCost;
|
||||
} else if (focusType == "/Lotus/Upgrades/Focus/Power/Residual/ChannelEfficiencyFocusUpgrade") {
|
||||
// Zenurik's Inner Might (Focus 2.0)
|
||||
cost += 50_000;
|
||||
} else {
|
||||
logger.warn(`unknown focus upgrade ${focusType}, will unlock it for free`);
|
||||
}
|
||||
cost += ExportFocusUpgrades[focusType].baseFocusPointCost;
|
||||
inventory.FocusUpgrades.push({ ItemType: focusType, Level: 0 });
|
||||
}
|
||||
inventory.FocusXP![focusPolarity]! -= cost;
|
||||
inventory.FocusXP![focusPolarity] -= cost;
|
||||
await inventory.save();
|
||||
res.json({
|
||||
FocusTypes: request.FocusTypes,
|
||||
@ -178,22 +86,17 @@ export const focusController: RequestHandler = async (req, res) => {
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "LevelUpUpgrade":
|
||||
case "UpdateUpgrade": {
|
||||
case FocusOperation.LevelUpUpgrade: {
|
||||
const request = JSON.parse(String(req.body)) as ILevelUpUpgradeRequest;
|
||||
const focusPolarity = focusTypeToPolarity(request.FocusInfos[0].ItemType);
|
||||
const inventory = await getInventory(account._id.toString());
|
||||
const inventory = await getInventory(accountId);
|
||||
let cost = 0;
|
||||
for (const focusUpgrade of request.FocusInfos) {
|
||||
cost += focusUpgrade.FocusXpCost;
|
||||
const focusUpgradeDb = inventory.FocusUpgrades.find(entry => entry.ItemType == focusUpgrade.ItemType)!;
|
||||
if (op == "UpdateUpgrade") {
|
||||
focusUpgradeDb.IsActive = focusUpgrade.IsActive;
|
||||
} else {
|
||||
focusUpgradeDb.Level = focusUpgrade.Level;
|
||||
}
|
||||
focusUpgradeDb.Level = focusUpgrade.Level;
|
||||
}
|
||||
inventory.FocusXP![focusPolarity]! -= cost;
|
||||
inventory.FocusXP![focusPolarity] -= cost;
|
||||
await inventory.save();
|
||||
res.json({
|
||||
FocusInfos: request.FocusInfos,
|
||||
@ -201,26 +104,25 @@ export const focusController: RequestHandler = async (req, res) => {
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "SentTrainingAmplifier": {
|
||||
case FocusOperation.SentTrainingAmplifier: {
|
||||
const request = JSON.parse(String(req.body)) as ISentTrainingAmplifierRequest;
|
||||
const inventory = await getInventory(account._id.toString());
|
||||
const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, {
|
||||
ModularParts: [
|
||||
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingGrip",
|
||||
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingChassis",
|
||||
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingBarrel"
|
||||
]
|
||||
});
|
||||
const parts: string[] = [
|
||||
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingGrip",
|
||||
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingChassis",
|
||||
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingBarrel"
|
||||
];
|
||||
const inventory = await getInventory(accountId);
|
||||
const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, parts);
|
||||
occupySlot(inventory, InventorySlot.AMPS, false);
|
||||
await inventory.save();
|
||||
res.json(inventoryChanges.OperatorAmps![0]);
|
||||
res.json((inventoryChanges.OperatorAmps as IEquipmentClient[])[0]);
|
||||
break;
|
||||
}
|
||||
case "UnbindUpgrade": {
|
||||
case FocusOperation.UnbindUpgrade: {
|
||||
const request = JSON.parse(String(req.body)) as IUnbindUpgradeRequest;
|
||||
const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]);
|
||||
const inventory = await getInventory(account._id.toString());
|
||||
inventory.FocusXP![focusPolarity]! -= 750_000 * request.FocusTypes.length;
|
||||
const inventory = await getInventory(accountId);
|
||||
inventory.FocusXP![focusPolarity] -= 750_000 * request.FocusTypes.length;
|
||||
addMiscItems(inventory, [
|
||||
{
|
||||
ItemType: "/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantItem",
|
||||
@ -246,7 +148,7 @@ export const focusController: RequestHandler = async (req, res) => {
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "ConvertShard": {
|
||||
case FocusOperation.ConvertShard: {
|
||||
const request = JSON.parse(String(req.body)) as IConvertShardRequest;
|
||||
// Tally XP
|
||||
let xp = 0;
|
||||
@ -264,11 +166,9 @@ export const focusController: RequestHandler = async (req, res) => {
|
||||
for (const shard of request.Shards) {
|
||||
shard.ItemCount *= -1;
|
||||
}
|
||||
const inventory = await getInventory(account._id.toString());
|
||||
const polarity = request.Polarity;
|
||||
inventory.FocusXP ??= {};
|
||||
inventory.FocusXP[polarity] ??= 0;
|
||||
inventory.FocusXP[polarity] += xp;
|
||||
const inventory = await getInventory(accountId);
|
||||
inventory.FocusXP ??= { AP_POWER: 0, AP_TACTIC: 0, AP_DEFENSE: 0, AP_ATTACK: 0, AP_WARD: 0 };
|
||||
inventory.FocusXP[request.Polarity] += xp;
|
||||
addMiscItems(inventory, request.Shards);
|
||||
await inventory.save();
|
||||
break;
|
||||
@ -276,8 +176,7 @@ export const focusController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Focus 3.0
|
||||
enum Focus3Operation {
|
||||
enum FocusOperation {
|
||||
InstallLens = "1",
|
||||
UnlockWay = "2",
|
||||
UnlockUpgrade = "3",
|
||||
@ -288,20 +187,6 @@ enum Focus3Operation {
|
||||
ConvertShard = "9"
|
||||
}
|
||||
|
||||
// Focus 2.0
|
||||
enum Focus2Operation {
|
||||
InstallLens = "1",
|
||||
UnlockWay = "2",
|
||||
UnlockUpgrade = "3",
|
||||
IncreasePool = "4",
|
||||
LevelUpUpgrade = "5",
|
||||
ActivateWay = "6",
|
||||
UpdateUpgrade = "7", // used to change the IsActive state, same format as ILevelUpUpgradeRequest
|
||||
SentTrainingAmplifier = "9",
|
||||
UnbindUpgrade = "10",
|
||||
ConvertShard = "11"
|
||||
}
|
||||
|
||||
// For UnlockWay & ActivateWay
|
||||
interface IWayRequest {
|
||||
FocusType: string;
|
||||
@ -311,13 +196,6 @@ interface IUnlockUpgradeRequest {
|
||||
FocusTypes: string[];
|
||||
}
|
||||
|
||||
// Focus 2.0
|
||||
interface IIncreasePoolRequest {
|
||||
FocusType: string;
|
||||
CurrentTotalCapacity: number;
|
||||
NewTotalCapacity: number;
|
||||
}
|
||||
|
||||
interface ILevelUpUpgradeRequest {
|
||||
FocusInfos: {
|
||||
ItemType: string;
|
||||
@ -325,7 +203,6 @@ interface ILevelUpUpgradeRequest {
|
||||
IsUniversal: boolean;
|
||||
Level: number;
|
||||
IsActiveAbility: boolean;
|
||||
IsActive?: number; // Focus 2.0
|
||||
}[];
|
||||
}
|
||||
|
||||
@ -351,7 +228,7 @@ interface ILensInstallRequest {
|
||||
|
||||
// Works for ways & upgrades
|
||||
const focusTypeToPolarity = (type: string): TFocusPolarity => {
|
||||
return ("AP_" + type.substring(1).split("/")[3].toUpperCase()) as TFocusPolarity;
|
||||
return ("AP_" + type.substr(1).split("/")[3].toUpperCase()) as TFocusPolarity;
|
||||
};
|
||||
|
||||
const shardValues = {
|
||||
@ -360,19 +237,3 @@ const shardValues = {
|
||||
"/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantItem": 25_000,
|
||||
"/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantTierTwoItem": 40_000
|
||||
};
|
||||
|
||||
// Starting at a capacity of 5 (Source: https://wiki.warframe.com/w/Focus_2.0)
|
||||
const increasePoolCost = [
|
||||
2576, 3099, 3638, 4190, 4755, 5331, 5918, 6514, 7120, 7734, 8357, 8988, 9626, 10271, 10923, 11582, 12247, 12918,
|
||||
13595, 14277, 14965, 15659, 16357, 17061, 17769, 18482, 19200, 19922, 20649, 21380, 22115, 22854, 23597, 24344,
|
||||
25095, 25850, 26609, 27371, 28136, 28905, 29678, 30454, 31233, 32015, 32801, 33590, 34382, 35176, 35974, 36775,
|
||||
37579, 38386, 39195, 40008, 40823, 41641, 42461, 43284, 44110, 44938, 45769, 46603, 47439, 48277, 49118, 49961,
|
||||
50807, 51655, 52505, 53357, 54212, 55069, 55929, 56790, 57654, 58520, 59388, 60258, 61130, 62005, 62881, 63759,
|
||||
64640, 65522, 66407, 67293, 68182, 69072, 69964, 70858, 71754, 72652, 73552, 74453, 75357, 76262, 77169, 78078,
|
||||
78988, 79900, 80814, 81730, 82648, 83567, 84488, 85410, 86334, 87260, 88188, 89117, 90047, 90980, 91914, 92849,
|
||||
93786, 94725, 95665, 96607, 97550, 98495, 99441, 100389, 101338, 102289, 103241, 104195, 105150, 106107, 107065,
|
||||
108024, 108985, 109948, 110911, 111877, 112843, 113811, 114780, 115751, 116723, 117696, 118671, 119647, 120624,
|
||||
121603, 122583, 123564, 124547, 125531, 126516, 127503, 128490, 129479, 130470, 131461, 132454, 133448, 134443,
|
||||
135440, 136438, 137437, 138437, 139438, 140441, 141444, 142449, 143455, 144463, 145471, 146481, 147492, 148503,
|
||||
149517
|
||||
];
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
import type { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
|
||||
import { getInventory } from "../../services/inventoryService.ts";
|
||||
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
|
||||
|
||||
export const forceRemoveItemController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "MiscItems");
|
||||
const body = getJSONfromString<IForceRemoveItemRequest>(String(req.body));
|
||||
const inventoryChanges: IInventoryChanges = {};
|
||||
for (const item of body.items) {
|
||||
const index = inventory.MiscItems.findIndex(x => x.ItemType == item);
|
||||
if (index != -1) {
|
||||
inventoryChanges.MiscItems ??= [];
|
||||
inventoryChanges.MiscItems.push({ ItemType: item, ItemCount: inventory.MiscItems[index].ItemCount * -1 });
|
||||
|
||||
inventory.MiscItems.splice(index, 1);
|
||||
}
|
||||
}
|
||||
await inventory.save();
|
||||
res.json({ InventoryChanges: inventoryChanges });
|
||||
};
|
||||
|
||||
interface IForceRemoveItemRequest {
|
||||
items: string[];
|
||||
}
|
||||
@ -1,15 +1,23 @@
|
||||
import type { RequestHandler } from "express";
|
||||
import { RequestHandler } from "express";
|
||||
import { ExportResources } from "warframe-public-export-plus";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { addFusionTreasures, addMiscItems, getInventory } from "../../services/inventoryService.ts";
|
||||
import type { IMiscItem } from "../../types/inventoryTypes/inventoryTypes.ts";
|
||||
import { parseFusionTreasure } from "../../helpers/inventoryHelpers.ts";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { addFusionTreasures, addMiscItems, getInventory } from "@/src/services/inventoryService";
|
||||
import { IFusionTreasure, IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
|
||||
interface IFusionTreasureRequest {
|
||||
oldTreasureName: string;
|
||||
newTreasureName: string;
|
||||
}
|
||||
|
||||
const parseFusionTreasure = (name: string, count: number): IFusionTreasure => {
|
||||
const arr = name.split("_");
|
||||
return {
|
||||
ItemType: arr[0],
|
||||
Sockets: parseInt(arr[1], 16),
|
||||
ItemCount: count
|
||||
};
|
||||
};
|
||||
|
||||
export const fusionTreasuresController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId);
|
||||
|
||||
@ -1,83 +0,0 @@
|
||||
import { toMongoDate } from "../../helpers/inventoryHelpers.ts";
|
||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
|
||||
import { addMiscItem, getInventory } from "../../services/inventoryService.ts";
|
||||
import { toStoreItem } from "../../services/itemDataService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { createGarden, getPersonalRooms } from "../../services/personalRoomsService.ts";
|
||||
import type { IMongoDate } from "../../types/commonTypes.ts";
|
||||
import type { IMissionReward } from "../../types/missionTypes.ts";
|
||||
import type { IGardeningClient, IPersonalRoomsClient } from "../../types/personalRoomsTypes.ts";
|
||||
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
|
||||
import type { 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[][]>;
|
||||
}
|
||||
@ -1,8 +1,8 @@
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { updateGeneric } from "../../services/inventoryService.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
|
||||
import type { IGenericUpdate } from "../../types/genericUpdate.ts";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { updateGeneric } from "@/src/services/inventoryService";
|
||||
import { RequestHandler } from "express";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { IGenericUpdate } from "@/src/types/genericUpdate";
|
||||
|
||||
// This endpoint used to be /api/genericUpdate.php, but sometime around the Jade Shadows update, it was changed to /api/updateNodeIntros.php.
|
||||
// SpaceNinjaServer supports both endpoints right now.
|
||||
@ -10,7 +10,8 @@ import type { IGenericUpdate } from "../../types/genericUpdate.ts";
|
||||
const genericUpdateController: RequestHandler = async (request, response) => {
|
||||
const accountId = await getAccountIdForRequest(request);
|
||||
const update = getJSONfromString<IGenericUpdate>(String(request.body));
|
||||
response.json(await updateGeneric(update, accountId));
|
||||
await updateGeneric(update, accountId);
|
||||
response.json(update);
|
||||
};
|
||||
|
||||
export { genericUpdateController };
|
||||
|
||||
@ -1,26 +1,7 @@
|
||||
import { Alliance, Guild } from "../../models/guildModel.ts";
|
||||
import { getAllianceClient } from "../../services/guildService.ts";
|
||||
import { getInventory } from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const getAllianceController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "GuildId");
|
||||
if (inventory.GuildId) {
|
||||
const guild = (await Guild.findById(inventory.GuildId, "Name Tier AllianceId"))!;
|
||||
if (guild.AllianceId) {
|
||||
const alliance = (await Alliance.findById(guild.AllianceId))!;
|
||||
res.json(await getAllianceClient(alliance, guild));
|
||||
return;
|
||||
}
|
||||
}
|
||||
res.end();
|
||||
const getAllianceController: RequestHandler = (_req, res) => {
|
||||
res.sendStatus(200);
|
||||
};
|
||||
|
||||
// POST request since U27
|
||||
/*interface IGetAllianceRequest {
|
||||
memberCount: number;
|
||||
clanLeaderName: string;
|
||||
clanLeaderId: string;
|
||||
}*/
|
||||
export { getAllianceController };
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
import { DailyDeal } from "../../models/worldStateModel.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const getDailyDealStockLevelsController: RequestHandler = async (req, res) => {
|
||||
const dailyDeal = (await DailyDeal.findOne({ StoreItem: req.query.productName }, "AmountSold"))!;
|
||||
export const getDailyDealStockLevelsController: RequestHandler = (req, res) => {
|
||||
res.json({
|
||||
StoreItem: req.query.productName,
|
||||
AmountSold: dailyDeal.AmountSold
|
||||
AmountSold: 0
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,54 +1,14 @@
|
||||
import { toOid } from "../../helpers/inventoryHelpers.ts";
|
||||
import { Friendship } from "../../models/friendModel.ts";
|
||||
import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "../../services/friendService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import type { IFriendInfo } from "../../types/friendTypes.ts";
|
||||
import type { Request, RequestHandler, Response } from "express";
|
||||
import { Request, Response } from "express";
|
||||
|
||||
// POST with {} instead of GET as of 38.5.0
|
||||
export const getFriendsController: RequestHandler = async (req: Request, res: Response) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const response: IGetFriendsResponse = {
|
||||
Current: [],
|
||||
IncomingFriendRequests: [],
|
||||
OutgoingFriendRequests: []
|
||||
};
|
||||
const [internalFriendships, externalFriendships] = await Promise.all([
|
||||
Friendship.find({ owner: accountId }),
|
||||
Friendship.find({ friend: accountId }, "owner Note")
|
||||
]);
|
||||
for (const externalFriendship of externalFriendships) {
|
||||
if (!internalFriendships.find(x => x.friend.equals(externalFriendship.owner))) {
|
||||
response.IncomingFriendRequests.push({
|
||||
_id: toOid(externalFriendship.owner),
|
||||
Note: externalFriendship.Note
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const internalFriendship of internalFriendships) {
|
||||
const friendInfo: IFriendInfo = {
|
||||
_id: toOid(internalFriendship.friend)
|
||||
};
|
||||
if (externalFriendships.find(x => x.owner.equals(internalFriendship.friend))) {
|
||||
response.Current.push(friendInfo);
|
||||
} else {
|
||||
response.OutgoingFriendRequests.push(friendInfo);
|
||||
}
|
||||
}
|
||||
const promises: Promise<void>[] = [];
|
||||
for (const arr of Object.values(response)) {
|
||||
for (const friendInfo of arr) {
|
||||
promises.push(addAccountDataToFriendInfo(friendInfo));
|
||||
promises.push(addInventoryDataToFriendInfo(friendInfo));
|
||||
}
|
||||
}
|
||||
await Promise.all(promises);
|
||||
res.json(response);
|
||||
const getFriendsController = (_request: Request, response: Response): void => {
|
||||
response.writeHead(200, {
|
||||
//Connection: "keep-alive",
|
||||
//"Content-Encoding": "gzip",
|
||||
"Content-Type": "text/html",
|
||||
// charset: "UTF - 8",
|
||||
"Content-Length": "3"
|
||||
});
|
||||
response.end(Buffer.from([0x7b, 0x7d, 0x0a]));
|
||||
};
|
||||
|
||||
// interface IGetFriendsResponse {
|
||||
// Current: IFriendInfo[];
|
||||
// IncomingFriendRequests: IFriendInfo[];
|
||||
// OutgoingFriendRequests: IFriendInfo[];
|
||||
// }
|
||||
type IGetFriendsResponse = Record<"Current" | "IncomingFriendRequests" | "OutgoingFriendRequests", IFriendInfo[]>;
|
||||
export { getFriendsController };
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
import { GuildMember } from "../../models/guildModel.ts";
|
||||
import { getInventory } from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import type { IGuildMemberClient } from "../../types/guildTypes.ts";
|
||||
import type { 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 type { RequestHandler } from "express";
|
||||
import { Guild } from "../../models/guildModel.ts";
|
||||
import { getAccountForRequest } from "../../services/loginService.ts";
|
||||
import { logger } from "../../utils/logger.ts";
|
||||
import { getInventory } from "../../services/inventoryService.ts";
|
||||
import { createUniqueClanName, getGuildClient } from "../../services/guildService.ts";
|
||||
import { RequestHandler } from "express";
|
||||
import { Guild } from "@/src/models/guildModel";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { createUniqueClanName, getGuildClient } from "@/src/services/guildService";
|
||||
|
||||
export const getGuildController: RequestHandler = async (req, res) => {
|
||||
const account = await getAccountForRequest(req);
|
||||
const inventory = await getInventory(account._id.toString(), "GuildId");
|
||||
const getGuildController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "GuildId");
|
||||
if (inventory.GuildId) {
|
||||
const guild = await Guild.findById(inventory.GuildId);
|
||||
const guild = await Guild.findOne({ _id: inventory.GuildId });
|
||||
if (guild) {
|
||||
// Handle guilds created before we added discriminators
|
||||
if (guild.Name.indexOf("#") == -1) {
|
||||
@ -24,9 +24,11 @@ export const getGuildController: RequestHandler = async (req, res) => {
|
||||
guild.CeremonyResetDate = undefined;
|
||||
await guild.save();
|
||||
}
|
||||
res.json(await getGuildClient(guild, account));
|
||||
res.json(await getGuildClient(guild, accountId));
|
||||
return;
|
||||
}
|
||||
}
|
||||
res.end();
|
||||
res.sendStatus(200);
|
||||
};
|
||||
|
||||
export { getGuildController };
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import type { RequestHandler } from "express";
|
||||
import { RequestHandler } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import { Guild } from "../../models/guildModel.ts";
|
||||
import { getDojoClient } from "../../services/guildService.ts";
|
||||
import { Account } from "../../models/loginModel.ts";
|
||||
import { Guild } from "@/src/models/guildModel";
|
||||
import { getDojoClient } from "@/src/services/guildService";
|
||||
|
||||
export const getGuildDojoController: RequestHandler = async (req, res) => {
|
||||
const guildId = req.query.guildId as string;
|
||||
|
||||
const guild = await Guild.findById(guildId);
|
||||
const guild = await Guild.findOne({ _id: guildId });
|
||||
if (!guild) {
|
||||
res.status(404).end();
|
||||
return;
|
||||
@ -19,15 +18,14 @@ export const getGuildDojoController: RequestHandler = async (req, res) => {
|
||||
_id: new Types.ObjectId(),
|
||||
pf: "/Lotus/Levels/ClanDojo/DojoHall.level",
|
||||
ppf: "",
|
||||
CompletionTime: new Date(Date.now() - 1000),
|
||||
CompletionTime: new Date(Date.now()),
|
||||
DecoCapacity: 600
|
||||
});
|
||||
await guild.save();
|
||||
}
|
||||
|
||||
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, account?.BuildLabel));
|
||||
res.json(await getDojoClient(guild, 0, payload.ComponentId));
|
||||
};
|
||||
|
||||
interface IGetGuildDojoRequest {
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
import type { RequestHandler } from "express";
|
||||
import { getAccountForRequest } from "../../services/loginService.ts";
|
||||
import { getInventory } from "../../services/inventoryService.ts";
|
||||
import { Guild } from "../../models/guildModel.ts";
|
||||
|
||||
export const getGuildEventScoreController: RequestHandler = async (req, res) => {
|
||||
const account = await getAccountForRequest(req);
|
||||
const inventory = await getInventory(account._id.toString(), "GuildId");
|
||||
const guild = await Guild.findById(inventory.GuildId);
|
||||
const goalId = req.query.goalId as string;
|
||||
if (guild && guild.GoalProgress && goalId) {
|
||||
const goal = guild.GoalProgress.find(x => x.goalId.toString() == goalId);
|
||||
if (goal) {
|
||||
res.json({
|
||||
Tier: guild.Tier,
|
||||
GoalProgress: {
|
||||
Count: goal.Count,
|
||||
Tag: goal.Tag,
|
||||
_id: { $oid: goal.goalId }
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
res.json({});
|
||||
};
|
||||
@ -1,15 +1,15 @@
|
||||
import { toMongoDate } from "../../helpers/inventoryHelpers.ts";
|
||||
import { Guild } from "../../models/guildModel.ts";
|
||||
import { getInventory } from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import type { IMongoDate } from "../../types/commonTypes.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||
import { Guild } from "@/src/models/guildModel";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { IMongoDate } from "@/src/types/commonTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const getGuildLogController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "GuildId");
|
||||
if (inventory.GuildId) {
|
||||
const guild = await Guild.findById(inventory.GuildId);
|
||||
const guild = await Guild.findOne({ _id: inventory.GuildId });
|
||||
if (guild) {
|
||||
const log: Record<string, IGuildLogEntryClient[]> = {
|
||||
RoomChanges: [],
|
||||
|
||||
@ -1,20 +1,16 @@
|
||||
import { toOid } from "../../helpers/inventoryHelpers.ts";
|
||||
import { Account, Ignore } from "../../models/loginModel.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import type { IFriendInfo } from "../../types/friendTypes.ts";
|
||||
import { parallelForeach } from "../../utils/async-utils.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const getIgnoredUsersController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const ignores = await Ignore.find({ ignorer: accountId });
|
||||
const ignoredUsers: IFriendInfo[] = [];
|
||||
await parallelForeach(ignores, async ignore => {
|
||||
const ignoreeAccount = (await Account.findById(ignore.ignoree, "DisplayName"))!;
|
||||
ignoredUsers.push({
|
||||
_id: toOid(ignore.ignoree),
|
||||
DisplayName: ignoreeAccount.DisplayName + ""
|
||||
});
|
||||
const getIgnoredUsersController: RequestHandler = (_req, res) => {
|
||||
res.writeHead(200, {
|
||||
"Content-Type": "text/html",
|
||||
"Content-Length": "3"
|
||||
});
|
||||
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 "../../models/inventoryModels/inventoryModel.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { generateRewardSeed } from "../../services/rngService.ts";
|
||||
import type { RequestHandler } from "express";
|
||||
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const getNewRewardSeedController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
|
||||
const rewardSeed = generateRewardSeed();
|
||||
logger.debug(`generated new reward seed: ${rewardSeed}`);
|
||||
await Inventory.updateOne(
|
||||
{
|
||||
accountOwnerId: accountId
|
||||
@ -17,3 +18,9 @@ export const getNewRewardSeedController: RequestHandler = async (req, res) => {
|
||||
);
|
||||
res.json({ rewardSeed: rewardSeed });
|
||||
};
|
||||
|
||||
export function generateRewardSeed(): number {
|
||||
const min = -Number.MAX_SAFE_INTEGER;
|
||||
const max = Number.MAX_SAFE_INTEGER;
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
@ -1,62 +0,0 @@
|
||||
import type { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { getInventory } from "../../services/inventoryService.ts";
|
||||
import { getSeasonChallengePools, getWorldState, pushWeeklyActs } from "../../services/worldStateService.ts";
|
||||
import { EPOCH, unixTimesInMs } from "../../constants/timeConstants.ts";
|
||||
import type { ISeasonChallenge } from "../../types/worldStateTypes.ts";
|
||||
import { ExportChallenges } from "warframe-public-export-plus";
|
||||
|
||||
export const getPastWeeklyChallengesController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "SeasonChallengeHistory ChallengeProgress");
|
||||
const worldState = getWorldState(undefined);
|
||||
|
||||
if (worldState.SeasonInfo) {
|
||||
const pools = getSeasonChallengePools(worldState.SeasonInfo.AffiliationTag);
|
||||
const nightwaveStartTimestamp = Number(worldState.SeasonInfo.Activation.$date.$numberLong);
|
||||
const nightwaveSeason = worldState.SeasonInfo.Season;
|
||||
const timeMs = worldState.Time * 1000;
|
||||
const completedChallengesIds = new Set<string>();
|
||||
|
||||
inventory.SeasonChallengeHistory.forEach(challengeHistory => {
|
||||
const entryNightwaveSeason = parseInt(challengeHistory.id.slice(0, 4), 10) - 1;
|
||||
if (nightwaveSeason == entryNightwaveSeason) {
|
||||
const meta = Object.entries(ExportChallenges).find(
|
||||
([key]) => key.split("/").pop() === challengeHistory.challenge
|
||||
);
|
||||
if (meta) {
|
||||
const [, challengeMeta] = meta;
|
||||
const challengeProgress = inventory.ChallengeProgress.find(
|
||||
c => c.Name === challengeHistory.challenge
|
||||
);
|
||||
|
||||
if (challengeProgress && challengeProgress.Progress >= (challengeMeta.requiredCount ?? 1)) {
|
||||
completedChallengesIds.add(challengeHistory.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const PastWeeklyChallenges: ISeasonChallenge[] = [];
|
||||
|
||||
let week = Math.trunc((timeMs - EPOCH) / unixTimesInMs.week) - 1;
|
||||
|
||||
while (EPOCH + week * unixTimesInMs.week >= nightwaveStartTimestamp && PastWeeklyChallenges.length < 3) {
|
||||
const tempActs: ISeasonChallenge[] = [];
|
||||
pushWeeklyActs(tempActs, pools, week, nightwaveStartTimestamp, nightwaveSeason);
|
||||
|
||||
for (const act of tempActs) {
|
||||
if (!completedChallengesIds.has(act._id.$oid) && PastWeeklyChallenges.length < 3) {
|
||||
if (act.Challenge.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanent")) {
|
||||
act.Permanent = true;
|
||||
}
|
||||
PastWeeklyChallenges.push(act);
|
||||
}
|
||||
}
|
||||
|
||||
week--;
|
||||
}
|
||||
|
||||
res.json({ PastWeeklyChallenges: PastWeeklyChallenges });
|
||||
}
|
||||
};
|
||||
@ -1,33 +1,40 @@
|
||||
import type { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { createGarden, getPersonalRooms } from "../../services/personalRoomsService.ts";
|
||||
import type { IGetShipResponse, IPersonalRoomsClient } from "../../types/personalRoomsTypes.ts";
|
||||
import { getLoadout } from "../../services/loadoutService.ts";
|
||||
import { toOid } from "../../helpers/inventoryHelpers.ts";
|
||||
import { RequestHandler } from "express";
|
||||
import { config } from "@/src/services/configService";
|
||||
import allShipFeatures from "@/static/fixed_responses/allShipFeatures.json";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getPersonalRooms } from "@/src/services/personalRoomsService";
|
||||
import { getShip } from "@/src/services/shipService";
|
||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||
import { IGetShipResponse } from "@/src/types/shipTypes";
|
||||
import { IPersonalRooms } from "@/src/types/personalRoomsTypes";
|
||||
import { getLoadout } from "@/src/services/loadoutService";
|
||||
|
||||
export const getShipController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const personalRoomsDb = await getPersonalRooms(accountId);
|
||||
|
||||
// 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 personalRooms = personalRoomsDb.toJSON<IPersonalRooms>();
|
||||
const loadout = await getLoadout(accountId);
|
||||
const ship = await getShip(personalRoomsDb.activeShipId, "ShipAttachments SkinFlavourItem");
|
||||
|
||||
const getShipResponse: IGetShipResponse = {
|
||||
ShipOwnerId: accountId,
|
||||
LoadOutInventory: { LoadOutPresets: loadout.toJSON() },
|
||||
Ship: {
|
||||
...personalRooms.Ship,
|
||||
ShipId: toOid(personalRoomsDb.activeShipId)
|
||||
ShipId: toOid(personalRoomsDb.activeShipId),
|
||||
ShipInterior: {
|
||||
Colors: personalRooms.ShipInteriorColors,
|
||||
ShipAttachments: ship.ShipAttachments,
|
||||
SkinFlavourItem: ship.SkinFlavourItem
|
||||
}
|
||||
},
|
||||
Apartment: personalRooms.Apartment,
|
||||
TailorShop: personalRooms.TailorShop
|
||||
};
|
||||
|
||||
if (config.unlockAllShipFeatures) {
|
||||
getShipResponse.Ship.Features = allShipFeatures;
|
||||
}
|
||||
|
||||
res.json(getShipResponse);
|
||||
};
|
||||
|
||||
@ -1,29 +1,14 @@
|
||||
import type { RequestHandler } from "express";
|
||||
import { applyStandingToVendorManifest, getVendorManifestByTypeName } from "../../services/serversideVendorsService.ts";
|
||||
import { getInventory } from "../../services/inventoryService.ts";
|
||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
|
||||
import { config } from "../../services/configService.ts";
|
||||
import { RequestHandler } from "express";
|
||||
import { getVendorManifestByTypeName } from "@/src/services/serversideVendorsService";
|
||||
|
||||
export const getVendorInfoController: RequestHandler = async (req, res) => {
|
||||
let manifest = getVendorManifestByTypeName(req.query.vendor as string);
|
||||
if (!manifest) {
|
||||
throw new Error(`Unknown vendor: ${req.query.vendor as string}`);
|
||||
}
|
||||
|
||||
// For testing purposes, authenticating with this endpoint is optional here, but would be required on live.
|
||||
if (req.query.accountId) {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId);
|
||||
manifest = applyStandingToVendorManifest(inventory, manifest);
|
||||
if (config.dev?.keepVendorsExpired) {
|
||||
manifest = {
|
||||
VendorInfo: {
|
||||
...manifest.VendorInfo,
|
||||
Expiry: { $date: { $numberLong: "0" } }
|
||||
}
|
||||
};
|
||||
export const getVendorInfoController: RequestHandler = (req, res) => {
|
||||
if (typeof req.query.vendor == "string") {
|
||||
const manifest = getVendorManifestByTypeName(req.query.vendor);
|
||||
if (!manifest) {
|
||||
throw new Error(`Unknown vendor: ${req.query.vendor}`);
|
||||
}
|
||||
res.json(manifest);
|
||||
} else {
|
||||
res.status(400).end();
|
||||
}
|
||||
|
||||
res.json(manifest);
|
||||
};
|
||||
|
||||
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