Compare commits

..

1 Commits

Author SHA1 Message Date
019ff331b2 chore: allow MT_CAPTURE for sorties 2025-04-22 21:52:17 +02:00
404 changed files with 17359 additions and 32500 deletions

View File

@ -3,6 +3,3 @@
Dockerfile* Dockerfile*
.* .*
docker-data/ docker-data/
node_modules/
static/data/
logs/

View File

@ -1,48 +1,36 @@
{ {
"plugins": ["@typescript-eslint", "prettier", "import"],
"extends": [ "extends": [
"eslint:recommended", "eslint:recommended",
"plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking", "plugin:@typescript-eslint/recommended-requiring-type-checking"
"plugin:import/recommended",
"plugin:import/typescript"
], ],
"plugins": ["@typescript-eslint", "prettier"],
"env": { "env": {
"browser": true, "browser": true,
"es6": true, "es6": true,
"node": true "node": true
}, },
"rules": { "rules": {
"@typescript-eslint/consistent-type-imports": "error", "@typescript-eslint/explicit-function-return-type": "warn",
"@typescript-eslint/explicit-function-return-type": "error", "@typescript-eslint/restrict-template-expressions": "warn",
"@typescript-eslint/restrict-template-expressions": "error", "@typescript-eslint/restrict-plus-operands": "warn",
"@typescript-eslint/restrict-plus-operands": "error", "@typescript-eslint/no-unsafe-member-access": "warn",
"@typescript-eslint/no-unsafe-member-access": "error",
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "caughtErrors": "none" }], "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "caughtErrors": "none" }],
"@typescript-eslint/no-unsafe-argument": "error", "@typescript-eslint/no-unsafe-argument": "error",
"@typescript-eslint/no-unsafe-call": "error", "@typescript-eslint/no-unsafe-call": "warn",
"@typescript-eslint/no-unsafe-assignment": "error", "@typescript-eslint/no-unsafe-assignment": "warn",
"@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-explicit-any": "warn",
"no-loss-of-precision": "error", "no-loss-of-precision": "warn",
"@typescript-eslint/no-unnecessary-condition": "error", "@typescript-eslint/no-unnecessary-condition": "warn",
"@typescript-eslint/no-base-to-string": "off", "@typescript-eslint/no-base-to-string": "off",
"no-case-declarations": "error", "no-case-declarations": "error",
"prettier/prettier": "error", "prettier/prettier": "error",
"no-mixed-spaces-and-tabs": "error", "no-mixed-spaces-and-tabs": "error",
"@typescript-eslint/require-await": "error", "require-await": "off",
"import/no-named-as-default-member": "off", "@typescript-eslint/require-await": "error"
"import/no-cycle": "warn",
"@typescript-eslint/no-deprecated": "warn"
}, },
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"parserOptions": { "parserOptions": {
"project": "./tsconfig.json" "project": "./tsconfig.json"
},
"settings": {
"import/extensions": [ ".ts" ],
"import/resolver": {
"typescript": true,
"node": true
}
} }
} }

View File

@ -1,7 +1,6 @@
name: Build name: Build
on: on:
push: push: {}
branches: ["main"]
pull_request: {} pull_request: {}
jobs: jobs:
build: build:
@ -11,10 +10,8 @@ jobs:
uses: actions/checkout@v4.1.2 uses: actions/checkout@v4.1.2
- name: Setup Node.js environment - name: Setup Node.js environment
uses: actions/setup-node@v4.0.2 uses: actions/setup-node@v4.0.2
with:
node-version: ">=20.18.1"
- run: npm ci - run: npm ci
- run: cp config-vanilla.json config.json - run: cp config.json.example config.json
- run: npm run verify - run: npm run verify
- run: npm run lint:ci - run: npm run lint:ci
- run: npm run prettier - run: npm run prettier

View File

@ -18,10 +18,8 @@ jobs:
- name: Build and push - name: Build and push
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
platforms: linux/arm64,linux/amd64 platforms: linux/amd64,linux/arm64
push: true push: true
tags: | tags: |
openwf/spaceninjaserver:latest openwf/spaceninjaserver:latest
openwf/spaceninjaserver:latest-arm64
openwf/spaceninjaserver:${{ github.sha }} openwf/spaceninjaserver:${{ github.sha }}
openwf/spaceninjaserver:${{ github.sha }}-arm64

View File

@ -2,4 +2,3 @@ src/routes/api.ts
static/webui/libs/ static/webui/libs/
*.html *.html
*.md *.md
config-vanilla.json

3
.vscode/launch.json vendored
View File

@ -8,7 +8,8 @@
"type": "node", "type": "node",
"request": "launch", "request": "launch",
"name": "Debug and Watch", "name": "Debug and Watch",
"args": ["${workspaceFolder}/scripts/dev.js"], "runtimeArgs": ["-r", "tsconfig-paths/register", "-r", "ts-node/register", "--watch-path", "src"],
"args": ["${workspaceFolder}/src/index.ts"],
"console": "integratedTerminal" "console": "integratedTerminal"
} }
] ]

View File

@ -1,3 +0,0 @@
{
"typescript.preferences.preferTypeOnlyAutoImports": true
}

View File

@ -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.

View File

@ -1,11 +1,26 @@
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_INFINITE_RESOURCES=true
ENV APP_UNLOCK_ALL_SHIP_FEATURES=true
ENV APP_UNLOCK_ALL_SHIP_DECORATIONS=true
ENV APP_UNLOCK_ALL_FLAVOUR_ITEMS=true
ENV APP_UNLOCK_ALL_SKINS=true
ENV APP_UNIVERSAL_POLARITY_EVERYWHERE=true
ENV APP_SPOOF_MASTERY_RANK=-1
RUN apk add --no-cache bash sed wget jq
COPY . /app COPY . /app
WORKDIR /app WORKDIR /app
RUN npm i --omit=dev --omit=optional
RUN date '+%d %B %Y' > BUILD_DATE
ENTRYPOINT ["/app/docker-entrypoint.sh"] ENTRYPOINT ["/app/docker-entrypoint.sh"]

View File

@ -6,36 +6,12 @@ More information for the moment here: [https://discord.gg/PNNZ3asUuY](https://di
This project is in active development at <https://onlyg.it/OpenWF/SpaceNinjaServer>. 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 ## 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. SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [config.json.example](config.json.example), 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`. - `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. - `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`.
- `ircAddress`, `hubAddress`, and `nrsAddress` can be provided if these secondary servers are on a different machine. - `worldState.lockTime` will lock the time provided in worldState if nonzero, e.g. `1743202800` for night in POE.
- `worldState.eidolonOverride` can be set to `day` or `night` to lock the time to day/fass and night/vome on Plains of Eidolon/Cambion Drift.
- `worldState.vallisOverride` can be set to `warm` or `cold` to lock the temperature on Orb Vallis.
- `worldState.duviriOverride` can be set to `joy`, `anger`, `envy`, `sorrow`, or `fear` to lock the Duviri spiral.
- `worldState.nightwaveOverride` will lock the nightwave season, assuming the client is new enough for it. Valid values:
- `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`.

View File

@ -1,33 +1,26 @@
@echo off @echo off
echo Updating SpaceNinjaServer... echo Updating SpaceNinjaServer...
git config remote.origin.url https://openwf.io/SpaceNinjaServer.git
git fetch --prune git fetch --prune
if %errorlevel% == 0 ( git stash
git stash git reset --hard origin/main
git checkout -f origin/main
if exist static\data\0\ ( if exist static\data\0\ (
echo Updating stripped assets... echo Updating stripped assets...
cd static\data\0\ cd static\data\0\
git pull git pull
cd ..\..\..\ 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.
) )
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 :a
pause > nul pause > nul
goto a goto a

View File

@ -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

View File

@ -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
}
}

54
config.json.example Normal file
View File

@ -0,0 +1,54 @@
{
"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,
"noDailyFocusLimit": false,
"noArgonCrystalDecay": false,
"noMasteryRankUpCooldown": false,
"noVendorPurchaseLimits": true,
"noDeathMarks": false,
"noKimCooldowns": false,
"instantResourceExtractorDrones": false,
"noResourceExtractorDronesDamage": false,
"noDojoRoomBuildStage": false,
"noDecoBuildStage": false,
"fastDojoRoomDestruction": false,
"noDojoResearchCosts": false,
"noDojoResearchTime": false,
"fastClanAscension": false,
"spoofMasteryRank": -1,
"worldState": {
"creditBoost": false,
"affinityBoost": false,
"resourceBoost": false,
"starDays": true,
"lockTime": 0
}
}

View File

@ -1,23 +1,43 @@
services: services:
spaceninjaserver: spaceninjaserver:
# build: .
image: openwf/spaceninjaserver:latest image: openwf/spaceninjaserver:latest
environment:
APP_MONGODB_URL: mongodb://openwfagent:spaceninjaserver@mongodb:27017/
# Following environment variables are set to default image values.
# Uncomment to edit.
# APP_MY_ADDRESS: localhost
# APP_HTTP_PORT: 80
# APP_HTTPS_PORT: 443
# APP_AUTO_CREATE_ACCOUNT: true
# APP_SKIP_STORY_MODE_CHOICE: true
# APP_SKIP_TUTORIAL: true
# APP_SKIP_ALL_DIALOGUE: true
# APP_UNLOCK_ALL_SCANS: true
# APP_UNLOCK_ALL_MISSIONS: true
# APP_UNLOCK_ALL_QUESTS: true
# APP_COMPLETE_ALL_QUESTS: true
# APP_INFINITE_RESOURCES: true
# APP_UNLOCK_ALL_SHIP_FEATURES: true
# APP_UNLOCK_ALL_SHIP_DECORATIONS: true
# APP_UNLOCK_ALL_FLAVOUR_ITEMS: true
# APP_UNLOCK_ALL_SKINS: true
# APP_UNIVERSAL_POLARITY_EVERYWHERE: true
# APP_SPOOF_MASTERY_RANK: -1
volumes: volumes:
- ./docker-data/conf:/app/conf - ./docker-data/static:/app/static/data
- ./docker-data/static-data:/app/static/data
- ./docker-data/logs:/app/logs - ./docker-data/logs:/app/logs
ports: ports:
- 80:80 - 80:80
- 443:443 - 443:443
# Normally, the image is fetched from Docker Hub, but you can use the local Dockerfile by removing "image" above and adding this:
#build: .
# Works best when using `docker-compose up --force-recreate --build`.
depends_on: depends_on:
- mongodb - mongodb
mongodb: mongodb:
image: docker.io/library/mongo:8.0.0-noble image: docker.io/library/mongo:8.0.0-noble
environment:
MONGO_INITDB_ROOT_USERNAME: openwfagent
MONGO_INITDB_ROOT_PASSWORD: spaceninjaserver
volumes: volumes:
- ./docker-data/database:/data/db - ./docker-data/database:/data/db
command: mongod --quiet --logpath /dev/null

View File

@ -1,8 +1,23 @@
#!/bin/bash #!/bin/bash
set -e set -e
if [ ! -f conf/config.json ]; then # Set up the configuration file using environment variables.
jq --arg value "mongodb://mongodb:27017/openWF" '.mongodbUrl = $value' /app/config-vanilla.json > /app/conf/config.json echo '{
fi "logger": {
"files": true,
"level": "trace",
"__valid_levels": "fatal, error, warn, info, http, debug, trace"
}
}
' > config.json
exec npm run 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

3491
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,63 +4,44 @@
"description": "WF Emulator", "description": "WF Emulator",
"main": "index.ts", "main": "index.ts",
"scripts": { "scripts": {
"start": "node --enable-source-maps build/src/index.js", "start": "node --enable-source-maps --import ./build/src/pathman.js build/src/index.js",
"build": "tsgo --inlineSourceMap && ncp static/webui build/static/webui", "dev": "ts-node-dev --openssl-legacy-provider -r tsconfig-paths/register src/index.ts ",
"build:tsc": "tsc --incremental --inlineSourceMap && ncp static/webui build/static/webui", "build": "tsc --incremental --sourceMap && 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": "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",
"lint": "eslint --ext .ts .", "lint": "eslint --ext .ts .",
"lint:ci": "eslint --ext .ts --rule \"prettier/prettier: off\" .", "lint:ci": "eslint --ext .ts --rule \"prettier/prettier: off\" .",
"lint:fix": "eslint --fix --ext .ts .", "lint:fix": "eslint --fix --ext .ts .",
"prettier": "prettier --write .", "prettier": "prettier --write .",
"update-translations": "cd scripts && node update-translations.cjs", "update-translations": "cd scripts && node update-translations.js"
"fix": "npm run update-translations && npm run prettier"
}, },
"license": "GNU", "license": "GNU",
"type": "module",
"dependencies": { "dependencies": {
"body-parser": "^2.2.0", "@types/express": "^5",
"chokidar": "^4.0.3", "@types/morgan": "^1.9.9",
"crc-32": "^1.2.2", "crc-32": "^1.2.2",
"express": "^5", "express": "^5",
"json-with-bigint": "^3.4.4", "json-with-bigint": "^3.2.2",
"mongoose": "^8.11.0", "mongoose": "^8.11.0",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"ncp": "^2.0.0", "ncp": "^2.0.0",
"undici": "^7.10.0", "typescript": "^5.5",
"warframe-public-export-plus": "^0.5.93", "warframe-public-export-plus": "^0.5.56",
"warframe-riven-info": "^0.1.2", "warframe-riven-info": "^0.1.2",
"winston": "^3.17.0", "winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0", "winston-daily-rotate-file": "^5.0.0"
"ws": "^8.18.2"
},
"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"
}, },
"devDependencies": { "devDependencies": {
"@rxliuli/tsgo": "^2025.3.31",
"@typescript-eslint/eslint-plugin": "^8.28.0", "@typescript-eslint/eslint-plugin": "^8.28.0",
"@typescript-eslint/parser": "^8.28.0", "@typescript-eslint/parser": "^8.28.0",
"eslint": "^8", "eslint": "^8",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-prettier": "^5.2.5", "eslint-plugin-prettier": "^5.2.5",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"tree-kill": "^1.2.2" "ts-node-dev": "^2.0.0",
"tsconfig-paths": "^4.2.0"
}, },
"engines": { "engines": {
"node": ">=20.18.1" "node": ">=18.15.0",
"npm": ">=9.5.0"
} }
} }

View File

@ -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) {}
});

View File

@ -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);
}

View File

@ -1,7 +1,6 @@
// Based on https://onlyg.it/OpenWF/Translations/src/branch/main/update.php // Based on https://onlyg.it/OpenWF/Translations/src/branch/main/update.php
// Converted via ChatGPT-4o // Converted via ChatGPT-4o
/* eslint-disable */
const fs = require("fs"); const fs = require("fs");
function extractStrings(content) { function extractStrings(content) {
@ -31,7 +30,7 @@ fs.readdirSync("../static/webui/translations").forEach(file => {
const strings = extractStrings(line); const strings = extractStrings(line);
if (Object.keys(strings).length > 0) { if (Object.keys(strings).length > 0) {
Object.entries(strings).forEach(([key, value]) => { 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`); fs.writeSync(fileHandle, ` ${key}: \`${targetStrings[key]}\`,\n`);
} else { } else {
fs.writeSync(fileHandle, ` ${key}: \`[UNTRANSLATED] ${value}\`,\n`); fs.writeSync(fileHandle, ` ${key}: \`[UNTRANSLATED] ${value}\`,\n`);

View File

@ -1,33 +1,26 @@
import express from "express"; import express from "express";
import bodyParser from "body-parser"; import bodyParser from "body-parser";
import { unknownEndpointHandler } from "./middleware/middleware.ts"; import { unknownEndpointHandler } from "@/src/middleware/middleware";
import { requestLogger } from "./middleware/morgenMiddleware.ts"; import { requestLogger } from "@/src/middleware/morgenMiddleware";
import { errorHandler } from "./middleware/errorHandler.ts"; import { errorHandler } from "@/src/middleware/errorHandler";
import { apiRouter } from "./routes/api.ts"; import { apiRouter } from "@/src/routes/api";
import { cacheRouter } from "./routes/cache.ts"; import { cacheRouter } from "@/src/routes/cache";
import { customRouter } from "./routes/custom.ts"; import { customRouter } from "@/src/routes/custom";
import { dynamicController } from "./routes/dynamic.ts"; import { dynamicController } from "@/src/routes/dynamic";
import { payRouter } from "./routes/pay.ts"; import { payRouter } from "@/src/routes/pay";
import { statsRouter } from "./routes/stats.ts"; import { statsRouter } from "@/src/routes/stats";
import { webuiRouter } from "./routes/webui.ts"; import { webuiRouter } from "@/src/routes/webui";
const app = express(); const app = express();
app.use((req, _res, next) => { 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). // 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. // The bootstrapper decrypts it for us but having an unsupported Content-Encoding here would still be an issue for Express, so removing it.
if (req.headers["content-encoding"] == "ezip" || req.headers["content-encoding"] == "e") { if (req.headers["content-encoding"] == "ezip" || req.headers["content-encoding"] == "e") {
req.headers["content-encoding"] = undefined; req.headers["content-encoding"] = undefined;
} }
// U18 uses application/x-www-form-urlencoded even tho the data is JSON which Express doesn't like.
// U17 sets no Content-Type at all, which Express also doesn't like.
if (!req.headers["content-type"] || req.headers["content-type"] == "application/x-www-form-urlencoded") {
req.headers["content-type"] = "application/octet-stream";
}
next(); next();
}); });

View File

@ -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 millisecondsPerSecond = 1000;
const secondsPerMinute = 60; const secondsPerMinute = 60;
const minutesPerHour = 60; const minutesPerHour = 60;
const hoursPerDay = 24; const hoursPerDay = 24;
const daysPerWeek = 7;
const unixSecond = millisecondsPerSecond; const unixSecond = millisecondsPerSecond;
const unixMinute = secondsPerMinute * millisecondsPerSecond; const unixMinute = secondsPerMinute * millisecondsPerSecond;
const unixHour = unixMinute * minutesPerHour; const unixHour = unixMinute * minutesPerHour;
const unixDay = hoursPerDay * unixHour; const unixDay = hoursPerDay * unixHour;
const unixWeek = daysPerWeek * unixDay;
export const unixTimesInMs = { export const unixTimesInMs = {
second: unixSecond, second: unixSecond,
minute: unixMinute, minute: unixMinute,
hour: unixHour, hour: unixHour,
day: unixDay, day: unixDay
week: unixWeek
}; };

View File

@ -1,6 +1,6 @@
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
export const abandonLibraryDailyTaskController: RequestHandler = async (req, res) => { export const abandonLibraryDailyTaskController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);

View File

@ -5,11 +5,11 @@ import {
hasGuildPermission, hasGuildPermission,
removeDojoDeco, removeDojoDeco,
removeDojoRoom removeDojoRoom
} from "../../services/guildService.ts"; } from "@/src/services/guildService";
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission } from "../../types/guildTypes.ts"; import { GuildPermission } from "@/src/types/guildTypes";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
export const abortDojoComponentController: RequestHandler = async (req, res) => { export const abortDojoComponentController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -31,13 +31,12 @@ export const abortDojoComponentController: RequestHandler = async (req, res) =>
if (request.DecoId) { if (request.DecoId) {
removeDojoDeco(guild, request.ComponentId, request.DecoId); removeDojoDeco(guild, request.ComponentId, request.DecoId);
await guild.save();
res.json(await getDojoClient(guild, 0, request.ComponentId));
} else { } else {
await removeDojoRoom(guild, request.ComponentId); await removeDojoRoom(guild, request.ComponentId);
await guild.save();
res.json(await getDojoClient(guild, 0));
} }
await guild.save();
res.json(await getDojoClient(guild, 0, request.ComponentId));
}; };
interface IAbortDojoComponentRequest { interface IAbortDojoComponentRequest {

View File

@ -1,13 +1,8 @@
import { import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
getDojoClient, import { getInventory } from "@/src/services/inventoryService";
getGuildForRequestEx, import { getAccountIdForRequest } from "@/src/services/loginService";
hasAccessToDojo, import { GuildPermission } from "@/src/types/guildTypes";
hasGuildPermission import { RequestHandler } from "express";
} 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 abortDojoComponentDestructionController: RequestHandler = async (req, res) => { export const abortDojoComponentDestructionController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);

View File

@ -1,19 +1,15 @@
import { toOid } from "../../helpers/inventoryHelpers.ts"; import { toOid } from "@/src/helpers/inventoryHelpers";
import { import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "@/src/helpers/rivenHelper";
createVeiledRivenFingerprint, import { getJSONfromString } from "@/src/helpers/stringHelpers";
createUnveiledRivenFingerprint, import { addMods, getInventory } from "@/src/services/inventoryService";
rivenRawToRealWeighted import { getAccountIdForRequest } from "@/src/services/loginService";
} from "../../helpers/rivenHelper.ts"; import { getRandomElement } from "@/src/services/rngService";
import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { RequestHandler } from "express";
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 { ExportUpgrades } from "warframe-public-export-plus"; import { ExportUpgrades } from "warframe-public-export-plus";
export const activateRandomModController: RequestHandler = async (req, res) => { export const activateRandomModController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); 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)); const request = getJSONfromString<IActiveRandomModRequest>(String(req.body));
addMods(inventory, [ addMods(inventory, [
{ {
@ -21,10 +17,8 @@ export const activateRandomModController: RequestHandler = async (req, res) => {
ItemCount: -1 ItemCount: -1
} }
]); ]);
const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType])!; const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType]);
const fingerprint = inventory.instantFinishRivenChallenge const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
? createUnveiledRivenFingerprint(ExportUpgrades[rivenType])
: createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
const upgradeIndex = const upgradeIndex =
inventory.Upgrades.push({ inventory.Upgrades.push({
ItemType: rivenType, ItemType: rivenType,

View File

@ -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
}

View File

@ -1,7 +1,7 @@
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { Inventory } from "../../models/inventoryModels/inventoryModel.ts"; import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
export const addFriendImageController: RequestHandler = async (req, res) => { export const addFriendImageController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);

View File

@ -1,9 +1,9 @@
import { toOid } from "../../helpers/inventoryHelpers.ts"; import { toOid } from "@/src/helpers/inventoryHelpers";
import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Account, Ignore } from "../../models/loginModel.ts"; import { Account, Ignore } from "@/src/models/loginModel";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import type { IFriendInfo } from "../../types/friendTypes.ts"; import { IFriendInfo } from "@/src/types/guildTypes";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
export const addIgnoredUserController: RequestHandler = async (req, res) => { export const addIgnoredUserController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);

View File

@ -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;
}

View File

@ -1,11 +1,11 @@
import { getJSONfromString, regexEscape } from "../../helpers/stringHelpers.ts"; import { getJSONfromString, regexEscape } from "@/src/helpers/stringHelpers";
import { Alliance, AllianceMember, Guild, GuildMember } from "../../models/guildModel.ts"; import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
import { createMessage } from "../../services/inboxService.ts"; import { createMessage } from "@/src/services/inboxService";
import { getEffectiveAvatarImageType, getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "@/src/services/inventoryService";
import { getAccountForRequest, getSuffixedName } from "../../services/loginService.ts"; import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
import { GuildPermission } from "../../types/guildTypes.ts"; import { GuildPermission } from "@/src/types/guildTypes";
import { logger } from "../../utils/logger.ts"; import { logger } from "@/src/utils/logger";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import { ExportFlavour } from "warframe-public-export-plus"; import { ExportFlavour } from "warframe-public-export-plus";
export const addToAllianceController: RequestHandler = async (req, res) => { export const addToAllianceController: RequestHandler = async (req, res) => {
@ -75,7 +75,7 @@ export const addToAllianceController: RequestHandler = async (req, res) => {
const invitedClanOwnerMember = (await GuildMember.findOne({ guildId: guilds[0]._id, rank: 0 }))!; const invitedClanOwnerMember = (await GuildMember.findOne({ guildId: guilds[0]._id, rank: 0 }))!;
const senderInventory = await getInventory(account._id.toString(), "ActiveAvatarImageType"); const senderInventory = await getInventory(account._id.toString(), "ActiveAvatarImageType");
const senderGuild = (await Guild.findById(allianceMember.guildId, "Name"))!; const senderGuild = (await Guild.findById(allianceMember.guildId, "Name"))!;
const alliance = (await Alliance.findById(req.query.allianceId as string, "Name"))!; const alliance = (await Alliance.findById(req.query.allianceId, "Name"))!;
await createMessage(invitedClanOwnerMember.accountId, [ await createMessage(invitedClanOwnerMember.accountId, [
{ {
sndr: getSuffixedName(account), sndr: getSuffixedName(account),
@ -95,7 +95,7 @@ export const addToAllianceController: RequestHandler = async (req, res) => {
} }
], ],
sub: "/Lotus/Language/Menu/Mailbox_AllianceInvite_Title", sub: "/Lotus/Language/Menu/Mailbox_AllianceInvite_Title",
icon: ExportFlavour[getEffectiveAvatarImageType(senderInventory)].icon, icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
contextInfo: alliance._id.toString(), contextInfo: alliance._id.toString(),
highPriority: true, highPriority: true,
acceptAction: "ALLIANCE_INVITE", acceptAction: "ALLIANCE_INVITE",

View File

@ -1,16 +1,13 @@
import { toMongoDate } from "../../helpers/inventoryHelpers.ts"; import { Guild, GuildMember } from "@/src/models/guildModel";
import { Guild, GuildMember } from "../../models/guildModel.ts"; import { Account } from "@/src/models/loginModel";
import { Account } from "../../models/loginModel.ts"; import { fillInInventoryDataForGuildMember, hasGuildPermission } from "@/src/services/guildService";
import { addInventoryDataToFriendInfo, areFriends } from "../../services/friendService.ts"; import { createMessage } from "@/src/services/inboxService";
import { hasGuildPermission } from "../../services/guildService.ts"; import { getInventory } from "@/src/services/inventoryService";
import { createMessage } from "../../services/inboxService.ts"; import { getAccountForRequest, getAccountIdForRequest, getSuffixedName } from "@/src/services/loginService";
import { getEffectiveAvatarImageType, getInventory } from "../../services/inventoryService.ts"; import { IOid } from "@/src/types/commonTypes";
import { getAccountForRequest, getAccountIdForRequest, getSuffixedName } from "../../services/loginService.ts"; import { GuildPermission, IGuildMemberClient } from "@/src/types/guildTypes";
import type { IOid } from "../../types/commonTypes.ts"; import { logger } from "@/src/utils/logger";
import type { IGuildMemberClient } from "../../types/guildTypes.ts"; import { RequestHandler } from "express";
import { GuildPermission } from "../../types/guildTypes.ts";
import { logger } from "../../utils/logger.ts";
import type { RequestHandler } from "express";
import { ExportFlavour } from "warframe-public-export-plus"; import { ExportFlavour } from "warframe-public-export-plus";
export const addToGuildController: RequestHandler = async (req, res) => { export const addToGuildController: RequestHandler = async (req, res) => {
@ -25,18 +22,15 @@ export const addToGuildController: RequestHandler = async (req, res) => {
return; return;
} }
const senderAccount = await getAccountForRequest(req);
const inventory = await getInventory(account._id.toString(), "Settings"); const inventory = await getInventory(account._id.toString(), "Settings");
if ( // TODO: Also consider GIFT_MODE_FRIENDS once friends are implemented
inventory.Settings?.GuildInvRestriction == "GIFT_MODE_NONE" || 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"); res.status(400).json("Invite restricted");
return; return;
} }
const guild = (await Guild.findById(payload.GuildId.$oid, "Name Ranks"))!; const guild = (await Guild.findById(payload.GuildId.$oid, "Name Ranks"))!;
const senderAccount = await getAccountForRequest(req);
if (!(await hasGuildPermission(guild, senderAccount._id.toString(), GuildPermission.Recruiter))) { if (!(await hasGuildPermission(guild, senderAccount._id.toString(), GuildPermission.Recruiter))) {
res.status(400).json("Invalid permission"); res.status(400).json("Invalid permission");
} }
@ -65,7 +59,7 @@ export const addToGuildController: RequestHandler = async (req, res) => {
} }
], ],
sub: "/Lotus/Language/Menu/Mailbox_ClanInvite_Title", sub: "/Lotus/Language/Menu/Mailbox_ClanInvite_Title",
icon: ExportFlavour[getEffectiveAvatarImageType(senderInventory)].icon, icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
contextInfo: payload.GuildId.$oid, contextInfo: payload.GuildId.$oid,
highPriority: true, highPriority: true,
acceptAction: "GUILD_INVITE", acceptAction: "GUILD_INVITE",
@ -77,11 +71,10 @@ export const addToGuildController: RequestHandler = async (req, res) => {
const member: IGuildMemberClient = { const member: IGuildMemberClient = {
_id: { $oid: account._id.toString() }, _id: { $oid: account._id.toString() },
DisplayName: account.DisplayName, DisplayName: account.DisplayName,
LastLogin: toMongoDate(account.LastLogin),
Rank: 7, Rank: 7,
Status: 2 Status: 2
}; };
await addInventoryDataToFriendInfo(member); await fillInInventoryDataForGuildMember(member);
res.json({ NewMember: member }); res.json({ NewMember: member });
} else if ("RequestMsg" in payload) { } else if ("RequestMsg" in payload) {
// Player applying to join a clan // Player applying to join a clan

View File

@ -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;
}

View File

@ -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;
}

View File

@ -1,8 +1,8 @@
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory, addMods } from "../../services/inventoryService.ts"; import { getInventory, addMods } from "@/src/services/inventoryService";
import type { IOid } from "../../types/commonTypes.ts"; import { IOid } from "@/src/types/commonTypes";
export const arcaneCommonController: RequestHandler = async (req, res) => { export const arcaneCommonController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);

View File

@ -1,8 +1,8 @@
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { addMiscItems, getInventory } from "../../services/inventoryService.ts"; import { addMiscItems, getInventory } from "@/src/services/inventoryService";
import type { IMiscItem } from "../../types/inventoryTypes/inventoryTypes.ts"; import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import { colorToShard, combineColors, shardToColor } from "../../helpers/shardHelper.ts"; import { colorToShard, combineColors, shardToColor } from "@/src/helpers/shardHelper";
export const archonFusionController: RequestHandler = async (req, res) => { export const archonFusionController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);

View File

@ -1,12 +1,11 @@
import { fromOid, toOid } from "../../helpers/inventoryHelpers.ts"; import { toOid } from "@/src/helpers/inventoryHelpers";
import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "../../helpers/rivenHelper.ts"; import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "@/src/helpers/rivenHelper";
import { addMiscItems, addMods, getInventory } from "../../services/inventoryService.ts"; import { addMiscItems, addMods, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { getRandomElement, getRandomWeightedReward, getRandomWeightedRewardUc } from "../../services/rngService.ts"; import { getRandomElement, getRandomWeightedReward, getRandomWeightedRewardUc } from "@/src/services/rngService";
import type { IUpgradeFromClient } from "../../types/inventoryTypes/inventoryTypes.ts"; import { IOid } from "@/src/types/commonTypes";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import type { TRarity } from "warframe-public-export-plus"; import { ExportBoosterPacks, ExportUpgrades, TRarity } from "warframe-public-export-plus";
import { ExportBoosterPacks, ExportUpgrades } from "warframe-public-export-plus";
export const artifactTransmutationController: RequestHandler = async (req, res) => { export const artifactTransmutationController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -25,11 +24,11 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
]); ]);
payload.Consumed.forEach(upgrade => { payload.Consumed.forEach(upgrade => {
inventory.Upgrades.pull({ _id: fromOid(upgrade.ItemId) }); inventory.Upgrades.pull({ _id: upgrade.ItemId.$oid });
}); });
const rawRivenType = getRandomRawRivenType(); const rawRivenType = getRandomRawRivenType();
const rivenType = getRandomElement(rivenRawToRealWeighted[rawRivenType])!; const rivenType = getRandomElement(rivenRawToRealWeighted[rawRivenType]);
const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]); const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
const upgradeIndex = const upgradeIndex =
@ -58,8 +57,8 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
payload.Consumed.forEach(upgrade => { payload.Consumed.forEach(upgrade => {
const meta = ExportUpgrades[upgrade.ItemType]; const meta = ExportUpgrades[upgrade.ItemType];
counts[meta.rarity] += upgrade.ItemCount; counts[meta.rarity] += upgrade.ItemCount;
if (fromOid(upgrade.ItemId) != "000000000000000000000000") { if (upgrade.ItemId.$oid != "000000000000000000000000") {
inventory.Upgrades.pull({ _id: fromOid(upgrade.ItemId) }); inventory.Upgrades.pull({ _id: upgrade.ItemId.$oid });
} else { } else {
addMods(inventory, [ addMods(inventory, [
{ {
@ -129,14 +128,24 @@ const getRandomRawRivenType = (): string => {
}; };
interface IArtifactTransmutationRequest { interface IArtifactTransmutationRequest {
Upgrade: IUpgradeFromClient; Upgrade: IAgnosticUpgradeClient;
LevelDiff: number; LevelDiff: number;
Consumed: IUpgradeFromClient[]; Consumed: IAgnosticUpgradeClient[];
Cost: number; Cost: number;
FusionPointCost: number; FusionPointCost: number;
RivenTransmute?: boolean; RivenTransmute?: boolean;
} }
interface IAgnosticUpgradeClient {
ItemType: string;
ItemId: IOid;
FromSKU: boolean;
UpgradeFingerprint: string;
PendingRerollFingerprint: string;
ItemCount: number;
LastAdded: IOid;
}
const specialModSets: string[][] = [ const specialModSets: string[][] = [
[ [
"/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod", "/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod",

View File

@ -1,9 +1,9 @@
import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import type { IInventoryClient, IUpgradeClient } from "../../types/inventoryTypes/inventoryTypes.ts"; import { IInventoryClient, IUpgradeClient } from "@/src/types/inventoryTypes/inventoryTypes";
import { addMods, getInventory } from "../../services/inventoryService.ts"; import { addMods, getInventory } from "@/src/services/inventoryService";
import { broadcastInventoryUpdate } from "../../services/wsService.ts"; import { config } from "@/src/services/configService";
export const artifactsController: RequestHandler = async (req, res) => { export const artifactsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -24,6 +24,7 @@ export const artifactsController: RequestHandler = async (req, res) => {
if (itemIndex !== -1) { if (itemIndex !== -1) {
Upgrades[itemIndex].UpgradeFingerprint = stringifiedUpgradeFingerprint; Upgrades[itemIndex].UpgradeFingerprint = stringifiedUpgradeFingerprint;
inventory.markModified(`Upgrades.${itemIndex}.UpgradeFingerprint`);
} else { } else {
itemIndex = itemIndex =
Upgrades.push({ Upgrades.push({
@ -34,10 +35,10 @@ export const artifactsController: RequestHandler = async (req, res) => {
addMods(inventory, [{ ItemType, ItemCount: -1 }]); addMods(inventory, [{ ItemType, ItemCount: -1 }]);
} }
if (!inventory.infiniteCredits) { if (!config.infiniteCredits) {
inventory.RegularCredits -= Cost; inventory.RegularCredits -= Cost;
} }
if (!inventory.infiniteEndo) { if (!config.infiniteEndo) {
inventory.FusionPoints -= FusionPointCost; inventory.FusionPoints -= FusionPointCost;
} }
@ -58,7 +59,6 @@ export const artifactsController: RequestHandler = async (req, res) => {
} }
res.send(itemId); res.send(itemId);
broadcastInventoryUpdate(req);
}; };
interface IArtifactsRequest { interface IArtifactsRequest {

View File

@ -1,9 +1,9 @@
import { GuildAd } from "../../models/guildModel.ts"; import { GuildAd } from "@/src/models/guildModel";
import { getGuildForRequestEx, hasGuildPermission } from "../../services/guildService.ts"; import { getGuildForRequestEx, hasGuildPermission } from "@/src/services/guildService";
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission } from "../../types/guildTypes.ts"; import { GuildPermission } from "@/src/types/guildTypes";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
export const cancelGuildAdvertisementController: RequestHandler = async (req, res) => { export const cancelGuildAdvertisementController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);

View File

@ -1,16 +1,10 @@
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import { import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
getDojoClient, import { logger } from "@/src/utils/logger";
getGuildForRequestEx, import { GuildPermission, IDojoComponentDatabase } from "@/src/types/guildTypes";
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 { Types } from "mongoose"; import { Types } from "mongoose";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "@/src/services/inventoryService";
export const changeDojoRootController: RequestHandler = async (req, res) => { export const changeDojoRootController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);

View File

@ -1,8 +1,8 @@
import { GuildMember } from "../../models/guildModel.ts"; import { GuildMember } from "@/src/models/guildModel";
import { getGuildForRequest, hasGuildPermissionEx } from "../../services/guildService.ts"; import { getGuildForRequest, hasGuildPermissionEx } from "@/src/services/guildService";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission } from "../../types/guildTypes.ts"; import { GuildPermission } from "@/src/types/guildTypes";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
export const changeGuildRankController: RequestHandler = async (req, res) => { export const changeGuildRankController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);

View File

@ -1,12 +1,16 @@
import { getAccountForRequest } from "../../services/loginService.ts"; import { RequestHandler } from "express";
import type { RequestHandler } from "express";
export const checkDailyMissionBonusController: RequestHandler = async (req, res) => { const checkDailyMissionBonusController: RequestHandler = (_req, res) => {
const account = await getAccountForRequest(req); const data = Buffer.from([
const today = Math.trunc(Date.now() / 86400000) * 86400; 0x44, 0x61, 0x69, 0x6c, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x6f, 0x6e, 0x75, 0x73, 0x3a,
if (account.DailyFirstWinDate != today) { 0x31, 0x2d, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x50, 0x56, 0x50, 0x57, 0x69, 0x6e, 0x42, 0x6f, 0x6e, 0x75, 0x73,
res.send("DailyMissionBonus:1-DailyPVPWinBonus:1\n"); 0x3a, 0x31, 0x0a
} else { ]);
res.send("DailyMissionBonus:0-DailyPVPWinBonus:1\n"); res.writeHead(200, {
} "Content-Type": "text/html",
"Content-Length": data.length
});
res.end(data);
}; };
export { checkDailyMissionBonusController };

View File

@ -1,307 +1,142 @@
//this is a controller for the claimCompletedRecipe route //this is a controller for the claimCompletedRecipe route
//it will claim a recipe for the user //it will claim a recipe for the user
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import { logger } from "../../utils/logger.ts"; import { logger } from "@/src/utils/logger";
import { getRecipe } from "../../services/itemDataService.ts"; import { getRecipe } from "@/src/services/itemDataService";
import type { IOidWithLegacySupport } from "../../types/commonTypes.ts"; import { IOid } from "@/src/types/commonTypes";
import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import type { TAccountDocument } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { getAccountForRequest } from "../../services/loginService.ts";
import { import {
getInventory, getInventory,
updateCurrency, updateCurrency,
addItem, addItem,
addRecipes, addRecipes,
occupySlot, occupySlot,
combineInventoryChanges, combineInventoryChanges
addKubrowPetPrint, } from "@/src/services/inventoryService";
addPowerSuit, import { IInventoryChanges } from "@/src/types/purchaseTypes";
addEquipment import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
} from "../../services/inventoryService.ts"; import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
import type { IInventoryChanges } from "../../types/purchaseTypes.ts"; import { toOid } from "@/src/helpers/inventoryHelpers";
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";
interface IClaimCompletedRecipeRequest { interface IClaimCompletedRecipeRequest {
RecipeIds: IOidWithLegacySupport[]; RecipeIds: IOid[];
}
interface IClaimCompletedRecipeResponse {
InventoryChanges: IInventoryChanges;
BrandedSuits?: IOidWithLegacySupport[];
} }
export const claimCompletedRecipeController: RequestHandler = async (req, res) => { export const claimCompletedRecipeController: RequestHandler = async (req, res) => {
const claimCompletedRecipeRequest = getJSONfromString<IClaimCompletedRecipeRequest>(String(req.body)); const claimCompletedRecipeRequest = getJSONfromString<IClaimCompletedRecipeRequest>(String(req.body));
const account = await getAccountForRequest(req); const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(account._id.toString()); if (!accountId) throw new Error("no account id");
const 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)}`);
}
//check recipe is indeed ready to be completed const inventory = await getInventory(accountId);
// if (pendingRecipe.CompletionDate > new Date()) { const pendingRecipe = inventory.PendingRecipes.id(claimCompletedRecipeRequest.RecipeIds[0].$oid);
// throw new Error(`recipe ${pendingRecipe._id} is not ready to be completed`); if (!pendingRecipe) {
// } throw new Error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$oid}`);
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);
} }
await inventory.save();
res.json(resp);
};
const claimCompletedRecipe = async ( //check recipe is indeed ready to be completed
account: TAccountDocument, // if (pendingRecipe.CompletionDate > new Date()) {
inventory: TInventoryDatabaseDocument, // throw new Error(`recipe ${pendingRecipe._id} is not ready to be completed`);
recipe: IRecipe, // }
pendingRecipe: IPendingRecipeDatabase,
resp: IClaimCompletedRecipeResponse,
rush: any
): Promise<void> => {
logger.debug("Claiming Recipe", { recipe, pendingRecipe });
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") { inventory.PendingRecipes.pull(pendingRecipe._id);
inventory.PendingSpectreLoadouts ??= [];
inventory.SpectreLoadouts ??= [];
const pendingLoadoutIndex = inventory.PendingSpectreLoadouts.findIndex(x => x.ItemType == recipe.resultType); const recipe = getRecipe(pendingRecipe.ItemType);
if (pendingLoadoutIndex != -1) { if (!recipe) {
const loadoutIndex = inventory.SpectreLoadouts.findIndex(x => x.ItemType == recipe.resultType); throw new Error(`no completed item found for recipe ${pendingRecipe._id.toString()}`);
if (loadoutIndex != -1) { }
inventory.SpectreLoadouts.splice(loadoutIndex, 1);
if (req.query.cancel) {
const inventoryChanges: IInventoryChanges = {
...updateCurrency(inventory, recipe.buildPrice * -1, false)
};
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;
});
} }
logger.debug( }
"moving spectre loadout from pending to active", for (const ingredient of recipe.ingredients) {
inventory.toJSON().PendingSpectreLoadouts![pendingLoadoutIndex] if (!equipmentIngredients.has(ingredient.ItemType)) {
combineInventoryChanges(
inventoryChanges,
await addItem(inventory, ingredient.ItemType, ingredient.ItemCount)
);
}
}
await inventory.save();
res.json(inventoryChanges); // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
} else {
logger.debug("Claiming Recipe", { recipe, pendingRecipe });
let BrandedSuits: undefined | IOid[];
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
inventory.PendingSpectreLoadouts ??= [];
inventory.SpectreLoadouts ??= [];
const pendingLoadoutIndex = inventory.PendingSpectreLoadouts.findIndex(
x => x.ItemType == recipe.resultType
); );
inventory.SpectreLoadouts.push(inventory.PendingSpectreLoadouts[pendingLoadoutIndex]); if (pendingLoadoutIndex != -1) {
inventory.PendingSpectreLoadouts.splice(pendingLoadoutIndex, 1); const loadoutIndex = inventory.SpectreLoadouts.findIndex(x => x.ItemType == recipe.resultType);
} if (loadoutIndex != -1) {
} else if (recipe.secretIngredientAction == "SIA_UNBRAND") { inventory.SpectreLoadouts.splice(loadoutIndex, 1);
inventory.BrandedSuits!.splice( }
inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)), logger.debug(
1 "moving spectre loadout from pending to active",
); inventory.toJSON().PendingSpectreLoadouts![pendingLoadoutIndex]
resp.BrandedSuits = [toOid2(pendingRecipe.SuitToUnbrand!, account.BuildLabel)]; );
} inventory.SpectreLoadouts.push(inventory.PendingSpectreLoadouts[pendingLoadoutIndex]);
inventory.PendingSpectreLoadouts.splice(pendingLoadoutIndex, 1);
if (recipe.consumeOnUse) {
addRecipes(inventory, [
{
ItemType: pendingRecipe.ItemType,
ItemCount: -1
} }
]); } else if (recipe.secretIngredientAction == "SIA_UNBRAND") {
} inventory.BrandedSuits!.splice(
inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)),
if (rush) { 1
const end = Math.trunc(pendingRecipe.CompletionDate.getTime() / 1000); );
const start = end - recipe.buildTime; BrandedSuits = [toOid(pendingRecipe.SuitToUnbrand!)];
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;
}
}
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 = ( let InventoryChanges: IInventoryChanges = {};
await addItem( if (recipe.consumeOnUse) {
inventory, addRecipes(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",
{ {
Configs: [ ItemType: pendingRecipe.ItemType,
{ ItemCount: -1
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)
);
} }
if (req.query.rush) {
const end = Math.trunc(pendingRecipe.CompletionDate.getTime() / 1000);
const start = end - recipe.buildTime;
const secondsElapsed = Math.trunc(Date.now() / 1000) - start;
const progress = secondsElapsed / recipe.buildTime;
logger.debug(`rushing recipe at ${Math.trunc(progress * 100)}% completion`);
const cost = Math.round(recipe.skipBuildTimePrice * (1 - (progress - 0.5)));
InventoryChanges = {
...InventoryChanges,
...updateCurrency(inventory, cost, true)
};
}
if (recipe.secretIngredientAction != "SIA_UNBRAND") {
InventoryChanges = {
...InventoryChanges,
...(await addItem(inventory, recipe.resultType, recipe.num, false))
};
}
await inventory.save();
res.json({ InventoryChanges, BrandedSuits });
} }
}; };

View File

@ -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;
}

View File

@ -1,6 +1,6 @@
import { addFusionPoints, getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
export const claimLibraryDailyTaskRewardController: RequestHandler = async (req, res) => { export const claimLibraryDailyTaskRewardController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -17,7 +17,7 @@ export const claimLibraryDailyTaskRewardController: RequestHandler = async (req,
} }
syndicate.Standing += rewardStanding; syndicate.Standing += rewardStanding;
addFusionPoints(inventory, 80 * rewardQuantity); inventory.FusionPoints += 80 * rewardQuantity;
await inventory.save(); await inventory.save();
res.json({ res.json({

View File

@ -1,6 +1,6 @@
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
export const clearDialogueHistoryController: RequestHandler = async (req, res) => { export const clearDialogueHistoryController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);

View File

@ -1,4 +1,4 @@
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
// example req.body: {"NewEpisodeReward":true,"crossPlaySetting":"ENABLED"} // example req.body: {"NewEpisodeReward":true,"crossPlaySetting":"ENABLED"}
export const clearNewEpisodeRewardController: RequestHandler = (_req, res) => { export const clearNewEpisodeRewardController: RequestHandler = (_req, res) => {

View File

@ -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
});
};

View File

@ -1,10 +1,11 @@
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { addMiscItems, getInventory, updateCurrency } from "../../services/inventoryService.ts"; import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inventoryService";
import type { IInventoryChanges } from "../../types/purchaseTypes.ts"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import type { IMiscItem } from "../../types/inventoryTypes/inventoryTypes.ts"; import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import type { IVeiledRivenFingerprint } from "../../helpers/rivenHelper.ts"; import { createUnveiledRivenFingerprint } from "@/src/helpers/rivenHelper";
import { ExportUpgrades } from "warframe-public-export-plus";
export const completeRandomModChallengeController: RequestHandler = async (req, res) => { export const completeRandomModChallengeController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -26,11 +27,10 @@ export const completeRandomModChallengeController: RequestHandler = async (req,
inventoryChanges.MiscItems = miscItemChanges; inventoryChanges.MiscItems = miscItemChanges;
} }
// Complete the riven challenge // Update riven fingerprint to a randomised unveiled state
const upgrade = inventory.Upgrades.id(request.ItemId)!; const upgrade = inventory.Upgrades.id(request.ItemId)!;
const fp = JSON.parse(upgrade.UpgradeFingerprint!) as IVeiledRivenFingerprint; const meta = ExportUpgrades[upgrade.ItemType];
fp.challenge.Progress = fp.challenge.Required; upgrade.UpgradeFingerprint = JSON.stringify(createUnveiledRivenFingerprint(meta));
upgrade.UpgradeFingerprint = JSON.stringify(fp);
await inventory.save(); await inventory.save();

View File

@ -1,7 +1,7 @@
import { Alliance, AllianceMember, Guild, GuildMember } from "../../models/guildModel.ts"; import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
import { getAllianceClient } from "../../services/guildService.ts"; import { getAllianceClient } from "@/src/services/guildService";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
export const confirmAllianceInvitationController: RequestHandler = async (req, res) => { export const confirmAllianceInvitationController: RequestHandler = async (req, res) => {
// Check requester is a warlord in their guild // Check requester is a warlord in their guild

View File

@ -1,18 +1,12 @@
import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Guild, GuildMember } from "../../models/guildModel.ts"; import { Guild, GuildMember } from "@/src/models/guildModel";
import { Account } from "../../models/loginModel.ts"; import { Account } from "@/src/models/loginModel";
import { import { deleteGuild, getGuildClient, hasGuildPermission, removeDojoKeyItems } from "@/src/services/guildService";
deleteGuild, import { addRecipes, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
getGuildClient, import { getAccountForRequest, getAccountIdForRequest, getSuffixedName } from "@/src/services/loginService";
giveClanKey, import { GuildPermission } from "@/src/types/guildTypes";
hasGuildPermission, import { IInventoryChanges } from "@/src/types/purchaseTypes";
removeDojoKeyItems import { RequestHandler } from "express";
} 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 { Types } from "mongoose"; import { Types } from "mongoose";
// GET request: A player accepting an invite they got in their inbox. // GET request: A player accepting an invite they got in their inbox.
@ -47,7 +41,14 @@ export const confirmGuildInvitationGetController: RequestHandler = async (req, r
// Update inventory of new member // Update inventory of new member
const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes"); const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes");
inventory.GuildId = new Types.ObjectId(req.query.clanId as string); inventory.GuildId = new Types.ObjectId(req.query.clanId as string);
giveClanKey(inventory, inventoryChanges); const recipeChanges = [
{
ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
ItemCount: 1
}
];
addRecipes(inventory, recipeChanges);
combineInventoryChanges(inventoryChanges, { Recipes: recipeChanges });
await inventory.save(); await inventory.save();
const guild = (await Guild.findById(req.query.clanId as string))!; const guild = (await Guild.findById(req.query.clanId as string))!;
@ -62,7 +63,7 @@ export const confirmGuildInvitationGetController: RequestHandler = async (req, r
await guild.save(); await guild.save();
res.json({ res.json({
...(await getGuildClient(guild, account)), ...(await getGuildClient(guild, account._id.toString())),
InventoryChanges: inventoryChanges InventoryChanges: inventoryChanges
}); });
} else { } else {
@ -95,9 +96,14 @@ export const confirmGuildInvitationPostController: RequestHandler = async (req,
await GuildMember.deleteMany({ accountId: guildMember.accountId, status: 1 }); await GuildMember.deleteMany({ accountId: guildMember.accountId, status: 1 });
// Update inventory of new member // Update inventory of new member
const inventory = await getInventory(guildMember.accountId.toString(), "GuildId LevelKeys Recipes"); const inventory = await getInventory(guildMember.accountId.toString(), "GuildId Recipes");
inventory.GuildId = new Types.ObjectId(req.query.clanId as string); inventory.GuildId = new Types.ObjectId(req.query.clanId as string);
giveClanKey(inventory); addRecipes(inventory, [
{
ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
ItemCount: 1
}
]);
await inventory.save(); await inventory.save();
// Add join to clan log // Add join to clan log

View File

@ -1,10 +1,10 @@
import { toMongoDate } from "../../helpers/inventoryHelpers.ts"; import { toMongoDate } from "@/src/helpers/inventoryHelpers";
import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Guild } from "../../models/guildModel.ts"; import { Guild } from "@/src/models/guildModel";
import { checkClanAscensionHasRequiredContributors } from "../../services/guildService.ts"; import { checkClanAscensionHasRequiredContributors } from "@/src/services/guildService";
import { addFusionPoints, getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
export const contributeGuildClassController: RequestHandler = async (req, res) => { export const contributeGuildClassController: RequestHandler = async (req, res) => {
@ -36,7 +36,7 @@ export const contributeGuildClassController: RequestHandler = async (req, res) =
// Either way, endo is given to the contributor. // Either way, endo is given to the contributor.
const inventory = await getInventory(accountId, "FusionPoints"); const inventory = await getInventory(accountId, "FusionPoints");
addFusionPoints(inventory, guild.CeremonyEndo!); inventory.FusionPoints += guild.CeremonyEndo!;
await inventory.save(); await inventory.save();
res.json({ res.json({

View File

@ -1,6 +1,5 @@
import type { TGuildDatabaseDocument } from "../../models/guildModel.ts"; import { GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
import { GuildMember } from "../../models/guildModel.ts"; import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import type { TInventoryDatabaseDocument } from "../../models/inventoryModels/inventoryModel.ts";
import { import {
addGuildMemberMiscItemContribution, addGuildMemberMiscItemContribution,
getDojoClient, getDojoClient,
@ -9,15 +8,14 @@ import {
processDojoBuildMaterialsGathered, processDojoBuildMaterialsGathered,
scaleRequiredCount, scaleRequiredCount,
setDojoRoomLogFunded setDojoRoomLogFunded
} from "../../services/guildService.ts"; } from "@/src/services/guildService";
import { addMiscItems, getInventory, updateCurrency } from "../../services/inventoryService.ts"; import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import type { IDojoContributable, IGuildMemberDatabase } from "../../types/guildTypes.ts"; import { IDojoContributable, IGuildMemberDatabase } from "@/src/types/guildTypes";
import type { IMiscItem } from "../../types/inventoryTypes/inventoryTypes.ts"; import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import type { IInventoryChanges } from "../../types/purchaseTypes.ts"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import type { IDojoBuild } from "warframe-public-export-plus"; import { ExportDojoRecipes, IDojoBuild } from "warframe-public-export-plus";
import { ExportDojoRecipes } from "warframe-public-export-plus";
interface IContributeToDojoComponentRequest { interface IContributeToDojoComponentRequest {
ComponentId: string; ComponentId: string;

View File

@ -1,5 +1,10 @@
import type { TGuildDatabaseDocument, TGuildMemberDatabaseDocument } from "../../models/guildModel.ts"; import {
import { Alliance, Guild, GuildMember } from "../../models/guildModel.ts"; Alliance,
Guild,
GuildMember,
TGuildDatabaseDocument,
TGuildMemberDatabaseDocument
} from "@/src/models/guildModel";
import { import {
addGuildMemberMiscItemContribution, addGuildMemberMiscItemContribution,
addGuildMemberShipDecoContribution, addGuildMemberShipDecoContribution,
@ -7,18 +12,17 @@ import {
addVaultMiscItems, addVaultMiscItems,
addVaultShipDecos, addVaultShipDecos,
getGuildForRequestEx getGuildForRequestEx
} from "../../services/guildService.ts"; } from "@/src/services/guildService";
import { import {
addFusionTreasures, addFusionTreasures,
addMiscItems, addMiscItems,
addShipDecorations, addShipDecorations,
getInventory, getInventory,
updateCurrency updateCurrency
} from "../../services/inventoryService.ts"; } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import type { ITypeCount } from "../../types/commonTypes.ts"; import { IFusionTreasure, IMiscItem, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
import type { IFusionTreasure, IMiscItem } from "../../types/inventoryTypes/inventoryTypes.ts"; import { RequestHandler } from "express";
import type { RequestHandler } from "express";
export const contributeToVaultController: RequestHandler = async (req, res) => { export const contributeToVaultController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);

View File

@ -1,10 +1,10 @@
import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Alliance, AllianceMember, Guild, GuildMember } from "../../models/guildModel.ts"; import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
import { getAllianceClient } from "../../services/guildService.ts"; import { getAllianceClient } from "@/src/services/guildService";
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission } from "../../types/guildTypes.ts"; import { GuildPermission } from "@/src/types/guildTypes";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
export const createAllianceController: RequestHandler = async (req, res) => { export const createAllianceController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);

View File

@ -1,29 +1,16 @@
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Guild, GuildMember } from "../../models/guildModel.ts"; import { Guild, GuildMember } from "@/src/models/guildModel";
import { createUniqueClanName, getGuildClient, giveClanKey } from "../../services/guildService.ts"; import { createUniqueClanName, getGuildClient } from "@/src/services/guildService";
import { getInventory } from "../../services/inventoryService.ts"; import { addRecipes, getInventory } from "@/src/services/inventoryService";
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
import { sendWsBroadcastTo } from "../../services/wsService.ts";
export const createGuildController: RequestHandler = async (req, res) => { export const createGuildController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req); const accountId = await getAccountIdForRequest(req);
const payload = getJSONfromString<ICreateGuildRequest>(String(req.body)); const payload = getJSONfromString<ICreateGuildRequest>(String(req.body));
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 // Remove pending applications for this account
await GuildMember.deleteMany({ accountId: account._id, status: 1 }); await GuildMember.deleteMany({ accountId, status: 1 });
// Create guild on database // Create guild on database
const guild = new Guild({ const guild = new Guild({
@ -33,22 +20,33 @@ export const createGuildController: RequestHandler = async (req, res) => {
// Create guild member on database // Create guild member on database
await GuildMember.insertOne({ await GuildMember.insertOne({
accountId: account._id, accountId: accountId,
guildId: guild._id, guildId: guild._id,
status: 0, status: 0,
rank: 0 rank: 0
}); });
const inventory = await getInventory(accountId, "GuildId Recipes");
inventory.GuildId = guild._id; inventory.GuildId = guild._id;
const inventoryChanges: IInventoryChanges = {}; addRecipes(inventory, [
giveClanKey(inventory, inventoryChanges); {
ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
ItemCount: 1
}
]);
await inventory.save(); await inventory.save();
res.json({ res.json({
...(await getGuildClient(guild, account)), ...(await getGuildClient(guild, accountId)),
InventoryChanges: inventoryChanges InventoryChanges: {
Recipes: [
{
ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
ItemCount: 1
}
]
}
}); });
sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
}; };
interface ICreateGuildRequest { interface ICreateGuildRequest {

View File

@ -1,17 +1,12 @@
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { config } from "@/src/services/configService";
import { getInventory } from "../../services/inventoryService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService";
export const creditsController: RequestHandler = async (req, res) => { export const creditsController: RequestHandler = async (req, res) => {
const inventory = ( const accountId = await getAccountIdForRequest(req);
await Promise.all([
getAccountIdForRequest(req), const inventory = await getInventory(accountId, "RegularCredits TradesRemaining PremiumCreditsFree PremiumCredits");
getInventory(
req.query.accountId as string,
"RegularCredits TradesRemaining PremiumCreditsFree PremiumCredits infiniteCredits infinitePlatinum"
)
])
)[1];
const response = { const response = {
RegularCredits: inventory.RegularCredits, RegularCredits: inventory.RegularCredits,
@ -20,10 +15,10 @@ export const creditsController: RequestHandler = async (req, res) => {
PremiumCredits: inventory.PremiumCredits PremiumCredits: inventory.PremiumCredits
}; };
if (inventory.infiniteCredits) { if (config.infiniteCredits) {
response.RegularCredits = 999999999; response.RegularCredits = 999999999;
} }
if (inventory.infinitePlatinum) { if (config.infinitePlatinum) {
response.PremiumCreditsFree = 0; response.PremiumCreditsFree = 0;
response.PremiumCredits = 999999999; response.PremiumCredits = 999999999;
} }

View File

@ -1,30 +1,21 @@
import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import type { TInventoryDatabaseDocument } from "../../models/inventoryModels/inventoryModel.ts"; import { getInventory } from "@/src/services/inventoryService";
import { getInventory } from "../../services/inventoryService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { ICrewMemberClient } from "@/src/types/inventoryTypes/inventoryTypes";
import type { ICrewMemberClient } from "../../types/inventoryTypes/inventoryTypes.ts"; import { RequestHandler } from "express";
import type { RequestHandler } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
export const crewMembersController: RequestHandler = async (req, res) => { export const crewMembersController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "CrewMembers NemesisHistory"); const inventory = await getInventory(accountId, "CrewMembers");
const data = getJSONfromString<ICrewMembersRequest>(String(req.body)); const data = getJSONfromString<ICrewMembersRequest>(String(req.body));
if (data.crewMember.SecondInCommand) { const dbCrewMember = inventory.CrewMembers.id(data.crewMember.ItemId.$oid)!;
clearOnCall(inventory); dbCrewMember.AssignedRole = data.crewMember.AssignedRole;
} dbCrewMember.SkillEfficiency = data.crewMember.SkillEfficiency;
if (data.crewMember.ItemId.$oid == "000000000000000000000000") { dbCrewMember.WeaponConfigIdx = data.crewMember.WeaponConfigIdx;
const convertedNemesis = inventory.NemesisHistory!.find(x => x.fp == data.crewMember.NemesisFingerprint)!; dbCrewMember.WeaponId = new Types.ObjectId(data.crewMember.WeaponId.$oid);
convertedNemesis.SecondInCommand = data.crewMember.SecondInCommand; dbCrewMember.Configs = data.crewMember.Configs;
} else { dbCrewMember.SecondInCommand = data.crewMember.SecondInCommand;
const dbCrewMember = inventory.CrewMembers.id(data.crewMember.ItemId.$oid)!;
dbCrewMember.AssignedRole = data.crewMember.AssignedRole;
dbCrewMember.SkillEfficiency = data.crewMember.SkillEfficiency;
dbCrewMember.WeaponConfigIdx = data.crewMember.WeaponConfigIdx;
dbCrewMember.WeaponId = new Types.ObjectId(data.crewMember.WeaponId.$oid);
dbCrewMember.Configs = data.crewMember.Configs;
dbCrewMember.SecondInCommand = data.crewMember.SecondInCommand;
}
await inventory.save(); await inventory.save();
res.json({ res.json({
crewMemberId: data.crewMember.ItemId.$oid, crewMemberId: data.crewMember.ItemId.$oid,
@ -35,20 +26,3 @@ export const crewMembersController: RequestHandler = async (req, res) => {
interface ICrewMembersRequest { interface ICrewMembersRequest {
crewMember: ICrewMemberClient; crewMember: ICrewMemberClient;
} }
const clearOnCall = (inventory: TInventoryDatabaseDocument): void => {
for (const cm of inventory.CrewMembers) {
if (cm.SecondInCommand) {
cm.SecondInCommand = false;
return;
}
}
if (inventory.NemesisHistory) {
for (const cm of inventory.NemesisHistory) {
if (cm.SecondInCommand) {
cm.SecondInCommand = false;
return;
}
}
}
};

View File

@ -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];

View File

@ -3,19 +3,16 @@ import {
addCrewShipRawSalvage, addCrewShipRawSalvage,
getInventory, getInventory,
addEquipment addEquipment
} from "../../services/inventoryService.ts"; } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import type { import { ICrewShipComponentFingerprint, IInnateDamageFingerprint } from "@/src/types/inventoryTypes/inventoryTypes";
ICrewShipComponentFingerprint,
IInnateDamageFingerprint
} from "../../types/inventoryTypes/inventoryTypes.ts";
import { ExportCustoms, ExportRailjackWeapons, ExportUpgrades } from "warframe-public-export-plus"; import { ExportCustoms, ExportRailjackWeapons, ExportUpgrades } from "warframe-public-export-plus";
import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import type { IInventoryChanges } from "../../types/purchaseTypes.ts"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { getRandomInt } from "../../services/rngService.ts"; import { getRandomInt } from "@/src/services/rngService";
import type { IFingerprintStat } from "../../helpers/rivenHelper.ts"; import { IFingerprintStat } from "@/src/helpers/rivenHelper";
import type { IEquipmentDatabase } from "../../types/equipmentTypes.ts"; import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
export const crewShipIdentifySalvageController: RequestHandler = async (req, res) => { export const crewShipIdentifySalvageController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -65,7 +62,14 @@ export const crewShipIdentifySalvageController: RequestHandler = async (req, res
} satisfies IInnateDamageFingerprint) } satisfies IInnateDamageFingerprint)
}; };
} }
addEquipment(inventory, "CrewShipSalvagedWeapons", payload.ItemType, defaultOverwrites, inventoryChanges); addEquipment(
inventory,
"CrewShipSalvagedWeapons",
payload.ItemType,
undefined,
inventoryChanges,
defaultOverwrites
);
} }
inventoryChanges.CrewShipRawSalvage = [ inventoryChanges.CrewShipRawSalvage = [

View File

@ -1,15 +1,12 @@
import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Guild } from "../../models/guildModel.ts"; import { Guild } from "@/src/models/guildModel";
import { hasAccessToDojo, hasGuildPermission } from "../../services/guildService.ts"; import { getAccountForRequest } from "@/src/services/loginService";
import { getInventory } from "../../services/inventoryService.ts"; import { logger } from "@/src/utils/logger";
import { getAccountForRequest, getAccountIdForRequest } from "../../services/loginService.ts"; import { RequestHandler } from "express";
import { GuildPermission } from "../../types/guildTypes.ts";
import { logger } from "../../utils/logger.ts";
import type { RequestHandler } from "express";
export const customObstacleCourseLeaderboardController: RequestHandler = async (req, res) => { export const customObstacleCourseLeaderboardController: RequestHandler = async (req, res) => {
const data = getJSONfromString<ICustomObstacleCourseLeaderboardRequest>(String(req.body)); const data = getJSONfromString<ICustomObstacleCourseLeaderboardRequest>(String(req.body));
const guild = (await Guild.findById(data.g, "DojoComponents Ranks"))!; const guild = (await Guild.findById(data.g, "DojoComponents"))!;
const component = guild.DojoComponents.id(data.c)!; const component = guild.DojoComponents.id(data.c)!;
if (req.query.act == "f") { if (req.query.act == "f") {
res.json({ res.json({
@ -37,19 +34,6 @@ export const customObstacleCourseLeaderboardController: RequestHandler = async (
entry.r = ++r; entry.r = ++r;
} }
await guild.save(); 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(); res.status(200).end();
} else { } else {
logger.debug(`data provided to ${req.path}: ${String(req.body)}`); logger.debug(`data provided to ${req.path}: ${String(req.body)}`);

View File

@ -1,8 +1,7 @@
import { getGuildForRequest, hasGuildPermission } from "../../services/guildService.ts"; import { getGuildForRequest, hasGuildPermission } from "@/src/services/guildService";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import type { IGuildRank } from "../../types/guildTypes.ts"; import { GuildPermission, IGuildRank } from "@/src/types/guildTypes";
import { GuildPermission } from "../../types/guildTypes.ts"; import { RequestHandler } from "express";
import type { RequestHandler } from "express";
export const customizeGuildRanksController: RequestHandler = async (req, res) => { export const customizeGuildRanksController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);

View File

@ -1,6 +1,6 @@
import { AllianceMember, GuildMember } from "../../models/guildModel.ts"; import { AllianceMember, GuildMember } from "@/src/models/guildModel";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
export const declineAllianceInviteController: RequestHandler = async (req, res) => { export const declineAllianceInviteController: RequestHandler = async (req, res) => {
// Check requester is a warlord in their guild // Check requester is a warlord in their guild

View File

@ -1,6 +1,6 @@
import { GuildMember } from "../../models/guildModel.ts"; import { GuildMember } from "@/src/models/guildModel";
import { getAccountForRequest } from "../../services/loginService.ts"; import { getAccountForRequest } from "@/src/services/loginService";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
export const declineGuildInviteController: RequestHandler = async (req, res) => { export const declineGuildInviteController: RequestHandler = async (req, res) => {
const accountId = await getAccountForRequest(req); const accountId = await getAccountForRequest(req);

View File

@ -1,5 +1,5 @@
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import { deleteSession } from "../../managers/sessionManager.ts"; import { deleteSession } from "@/src/managers/sessionManager";
const deleteSessionController: RequestHandler = (_req, res) => { const deleteSessionController: RequestHandler = (_req, res) => {
deleteSession(_req.query.sessionId as string); deleteSession(_req.query.sessionId as string);

View File

@ -3,14 +3,12 @@ import {
getGuildForRequestEx, getGuildForRequestEx,
hasAccessToDojo, hasAccessToDojo,
hasGuildPermission, hasGuildPermission,
refundDojoDeco,
removeDojoDeco removeDojoDeco
} from "../../services/guildService.ts"; } from "@/src/services/guildService";
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission } from "../../types/guildTypes.ts"; import { GuildPermission } from "@/src/types/guildTypes";
import { logger } from "../../utils/logger.ts"; import { RequestHandler } from "express";
import type { RequestHandler } from "express";
export const destroyDojoDecoController: RequestHandler = async (req, res) => { export const destroyDojoDecoController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -20,20 +18,9 @@ export const destroyDojoDecoController: RequestHandler = async (req, res) => {
res.json({ DojoRequestStatus: -1 }); res.json({ DojoRequestStatus: -1 });
return; return;
} }
const request = JSON.parse(String(req.body)) as IDestroyDojoDecoRequest | IClearObstacleCourseRequest; const request = JSON.parse(String(req.body)) as IDestroyDojoDecoRequest;
if ("DecoType" in request) {
removeDojoDeco(guild, request.ComponentId, request.DecoId); removeDojoDeco(guild, request.ComponentId, request.DecoId);
} else if (request.Act == "cObst") {
const component = guild.DojoComponents.id(request.ComponentId)!;
if (component.Decos) {
for (const deco of component.Decos) {
refundDojoDeco(guild, component, deco);
}
component.Decos.splice(0, component.Decos.length);
}
} else {
logger.error(`unhandled destroyDojoDeco request`, request);
}
await guild.save(); await guild.save();
res.json(await getDojoClient(guild, 0, request.ComponentId)); res.json(await getDojoClient(guild, 0, request.ComponentId));
@ -44,8 +31,3 @@ interface IDestroyDojoDecoRequest {
ComponentId: string; ComponentId: string;
DecoId: string; DecoId: string;
} }
interface IClearObstacleCourseRequest {
ComponentId: string;
Act: "cObst" | "maybesomethingelsewedontknowabout";
}

View File

@ -1,9 +1,9 @@
import { Alliance, AllianceMember, Guild, GuildMember } from "../../models/guildModel.ts"; import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
import { getAccountForRequest } from "../../services/loginService.ts"; import { getAccountForRequest } from "@/src/services/loginService";
import { GuildPermission } from "../../types/guildTypes.ts"; import { GuildPermission } from "@/src/types/guildTypes";
import { parallelForeach } from "../../utils/async-utils.ts"; import { parallelForeach } from "@/src/utils/async-utils";
import { logger } from "../../utils/logger.ts"; import { logger } from "@/src/utils/logger";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
export const divvyAllianceVaultController: RequestHandler = async (req, res) => { 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. // 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.

View File

@ -1,17 +1,10 @@
import type { TGuildDatabaseDocument } from "../../models/guildModel.ts"; import { GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
import { GuildMember } from "../../models/guildModel.ts"; import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, scaleRequiredCount } from "@/src/services/guildService";
import { import { getInventory, updateCurrency } from "@/src/services/inventoryService";
getDojoClient, import { getAccountIdForRequest } from "@/src/services/loginService";
getGuildForRequestEx, import { IDojoContributable } from "@/src/types/guildTypes";
hasAccessToDojo, import { RequestHandler } from "express";
scaleRequiredCount import { ExportDojoRecipes, IDojoBuild } from "warframe-public-export-plus";
} 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";
interface IDojoComponentRushRequest { interface IDojoComponentRushRequest {
DecoType?: string; DecoType?: string;

View File

@ -1,4 +1,4 @@
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
// Arbiter Dojo endpoints, not really used by us as we don't provide a ContentURL. // Arbiter Dojo endpoints, not really used by us as we don't provide a ContentURL.

View File

@ -1,12 +1,13 @@
import { toMongoDate, toOid } from "../../helpers/inventoryHelpers.ts"; import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
import { addMiscItems, getInventory } from "../../services/inventoryService.ts"; import { config } from "@/src/services/configService";
import { fromStoreItem } from "../../services/itemDataService.ts"; import { addMiscItems, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { fromStoreItem } from "@/src/services/itemDataService";
import { getRandomInt, getRandomWeightedRewardUc } from "../../services/rngService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import type { IMongoDate, IOid } from "../../types/commonTypes.ts"; import { getRandomInt, getRandomWeightedRewardUc } from "@/src/services/rngService";
import type { IDroneClient } from "../../types/inventoryTypes/inventoryTypes.ts"; import { IMongoDate, IOid } from "@/src/types/commonTypes";
import type { IInventoryChanges } from "../../types/purchaseTypes.ts"; import { IDroneClient } from "@/src/types/inventoryTypes/inventoryTypes";
import type { RequestHandler } from "express"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { RequestHandler } from "express";
import { ExportDrones, ExportResources, ExportSystems } from "warframe-public-export-plus"; import { ExportDrones, ExportResources, ExportSystems } from "warframe-public-export-plus";
export const dronesController: RequestHandler = async (req, res) => { export const dronesController: RequestHandler = async (req, res) => {
@ -38,13 +39,10 @@ export const dronesController: RequestHandler = async (req, res) => {
ActiveDrones: activeDrones ActiveDrones: activeDrones
}); });
} else if ("droneId" in req.query && "systemIndex" in req.query) { } else if ("droneId" in req.query && "systemIndex" in req.query) {
const inventory = await getInventory( const inventory = await getInventory(accountId, "Drones");
accountId,
"Drones instantResourceExtractorDrones noResourceExtractorDronesDamage"
);
const drone = inventory.Drones.id(req.query.droneId as string)!; const drone = inventory.Drones.id(req.query.droneId as string)!;
const droneMeta = ExportDrones[drone.ItemType]; 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) { if (drone.RepairStart) {
const repairMinutes = (Date.now() - drone.RepairStart.getTime()) / 60_000; const repairMinutes = (Date.now() - drone.RepairStart.getTime()) / 60_000;
const hpPerMinute = droneMeta.repairRate / 60; const hpPerMinute = droneMeta.repairRate / 60;
@ -53,11 +51,11 @@ export const dronesController: RequestHandler = async (req, res) => {
} }
drone.System = parseInt(req.query.systemIndex as string); drone.System = parseInt(req.query.systemIndex as string);
const system = ExportSystems[drone.System - 1]; const system = ExportSystems[drone.System - 1];
drone.DamageTime = inventory.instantResourceExtractorDrones drone.DamageTime = config.instantResourceExtractorDrones
? new Date() ? new Date()
: new Date(Date.now() + getRandomInt(3 * 3600 * 1000, 4 * 3600 * 1000)); : new Date(Date.now() + getRandomInt(3 * 3600 * 1000, 4 * 3600 * 1000));
drone.PendingDamage = drone.PendingDamage =
!inventory.noResourceExtractorDronesDamage && Math.random() < system.damageChance !config.noResourceExtractorDronesDamage && Math.random() < system.damageChance
? getRandomInt(system.droneDamage.minValue, system.droneDamage.maxValue) ? getRandomInt(system.droneDamage.minValue, system.droneDamage.maxValue)
: 0; : 0;
const resource = getRandomWeightedRewardUc(system.resources, droneMeta.probabilities)!; const resource = getRandomWeightedRewardUc(system.resources, droneMeta.probabilities)!;
@ -74,7 +72,7 @@ export const dronesController: RequestHandler = async (req, res) => {
); );
} }
} else { } else {
drone.ResourceCount = droneMeta.binCapacity * droneMeta.capacityMultipliers[resource.Rarity]; drone.ResourceCount = 1;
} }
await inventory.save(); await inventory.save();
res.json({}); res.json({});

View File

@ -1,534 +1,60 @@
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { combineInventoryChanges, getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "@/src/services/inventoryService";
import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import type { import { TEndlessXpCategory } from "@/src/types/inventoryTypes/inventoryTypes";
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";
export const endlessXpController: RequestHandler = async (req, res) => { export const endlessXpController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const payload = getJSONfromString<IEndlessXpRequest>(String(req.body)); const payload = getJSONfromString<IEndlessXpRequest>(String(req.body));
if (payload.Mode == "r") {
const inventory = await getInventory(accountId, "EndlessXP");
inventory.EndlessXP ??= [];
let entry = inventory.EndlessXP.find(x => x.Category == payload.Category);
if (!entry) {
entry = {
Category: payload.Category,
Earn: 0,
Claim: 0,
Choices: payload.Choices,
PendingRewards: []
};
inventory.EndlessXP.push(entry);
}
const weekStart = 1734307200_000 + Math.trunc((Date.now() - 1734307200_000) / 604800000) * 604800000; inventory.EndlessXP ??= [];
const weekEnd = weekStart + 604800000; const entry = inventory.EndlessXP.find(x => x.Category == payload.Category);
if (entry) {
entry.Earn = 0;
entry.Claim = 0;
entry.BonusAvailable = new Date(weekStart);
entry.Expiry = new Date(weekEnd);
entry.Choices = payload.Choices; entry.Choices = payload.Choices;
entry.PendingRewards =
payload.Category == "EXC_HARD"
? generateHardModeRewards(payload.Choices)
: generateNormalModeRewards(payload.Choices);
await inventory.save();
res.json({
NewProgress: inventory.toJSON<IInventoryClient>().EndlessXP!.find(x => x.Category == payload.Category)!
});
} else if (payload.Mode == "c") {
const inventory = await getInventory(accountId);
const entry = inventory.EndlessXP!.find(x => x.Category == payload.Category)!;
const inventoryChanges: IInventoryChanges = {};
for (const reward of entry.PendingRewards) {
if (entry.Claim < reward.RequiredTotalXp && reward.RequiredTotalXp <= entry.Earn) {
combineInventoryChanges(
inventoryChanges,
(
await handleStoreItemAcquisition(
reward.Rewards[0].StoreItem,
inventory,
reward.Rewards[0].ItemCount
)
).InventoryChanges
);
}
}
entry.Claim = entry.Earn;
await inventory.save();
res.json({
InventoryChanges: inventoryChanges,
ClaimedXp: entry.Claim
});
} else { } else {
logger.debug(`data provided to ${req.path}: ${String(req.body)}`); inventory.EndlessXP.push({
throw new Error(`unexpected endlessXp mode: ${payload.Mode}`); Category: payload.Category,
Choices: payload.Choices
});
} }
}; await inventory.save();
type IEndlessXpRequest = res.json({
| { NewProgress: {
Mode: "r"; Category: payload.Category,
Category: TEndlessXpCategory; Earn: 0,
Choices: string[]; Claim: 0,
} BonusAvailable: {
| { $date: {
Mode: "c" | "something else"; $numberLong: "9999999999999"
Category: TEndlessXpCategory;
};
const generateRandomRewards = (deckName: string): ICountedStoreItem[] => {
const reward = getRandomElement(ExportRewards[deckName][0])!;
return [
{
StoreItem: reward.type,
ItemCount: reward.itemCount
}
];
};
const normalModeChosenRewards: Record<string, string[]> = {
Excalibur: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Excalibur/RadialJavelinAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburBlueprint"
],
Trinity: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinityHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinityChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Trinity/EnergyVampireAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinitySystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinityBlueprint"
],
Ember: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Ember/WorldOnFireAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberBlueprint"
],
Loki: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKIHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKIChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Loki/InvisibilityAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKISystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKIBlueprint"
],
Mag: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Mag/CrushAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagBlueprint"
],
Rhino: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Rhino/RhinoChargeAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoBlueprint"
],
Ash: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Ninja/GlaiveAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshBlueprint"
],
Frost: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Frost/IceShieldAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostBlueprint"
],
Nyx: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Jade/SelfBulletAttractorAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxBlueprint"
],
Saryn: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Saryn/PoisonAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynBlueprint"
],
Vauban: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Trapper/LevTrapAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperBlueprint"
],
Nova: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaChassisBlueprint",
"/Lotus/StoreItems/Powersuits/AntiMatter/MolecularPrimeAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaBlueprint"
],
Nekros: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Necro/CloneTheDeadAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroBlueprint"
],
Valkyr: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Berserker/IntimidateAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerBlueprint"
],
Oberon: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Paladin/RegenerationAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinBlueprint"
],
Hydroid: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Pirate/CannonBarrageAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidBlueprint"
],
Mirage: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Harlequin/LightAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinBlueprint"
],
Limbo: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Magician/TearInSpaceAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianBlueprint"
],
Mesa: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Cowgirl/GunFuPvPAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerBlueprint"
],
Chroma: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Dragon/DragonLuckAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaBlueprint"
],
Atlas: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Brawler/BrawlerPassiveAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerBlueprint"
],
Ivara: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Ranger/RangerStealAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerBlueprint"
],
Inaros: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummyHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummyChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Sandman/SandmanSwarmAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummySystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummyBlueprint"
],
Titania: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairyHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairyChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Fairy/FairyFlightAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairySystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairyBlueprint"
],
Nidus: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Infestation/InfestPodsAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusBlueprint"
],
Octavia: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Bard/BardCharmAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaBlueprint"
],
Harrow: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Priest/PriestPactAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestBlueprint"
],
Gara: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Glass/GlassFragmentAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassBlueprint"
],
Khora: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Khora/KhoraCrackAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraBlueprint"
],
Revenant: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Revenant/RevenantMarkAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantBlueprint"
],
Garuda: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Garuda/GarudaUnstoppableAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaBlueprint"
],
Baruuk: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistChassisBlueprint",
"/Lotus/StoreItems/Powersuits/Pacifist/PacifistFistAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistBlueprint"
],
Hildryn: [
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeHelmetBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeChassisBlueprint",
"/Lotus/StoreItems/Powersuits/IronFrame/IronFrameStripAugmentCard",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeSystemsBlueprint",
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeBlueprint"
]
};
const generateNormalModeRewards = (choices: string[]): IEndlessXpReward[] => {
const choiceRewards = normalModeChosenRewards[choices[0]];
return [
{
RequiredTotalXp: 190,
Rewards: generateRandomRewards(
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalSilverRewards"
)
},
{
RequiredTotalXp: 400,
Rewards: [
{
StoreItem: choiceRewards[0],
ItemCount: 1
} }
] },
}, Expiry: {
{ $date: {
RequiredTotalXp: 630, $numberLong: "9999999999999"
Rewards: generateRandomRewards(
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalSilverRewards"
)
},
{
RequiredTotalXp: 890,
Rewards: generateRandomRewards(
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalMODRewards"
)
},
{
RequiredTotalXp: 1190,
Rewards: [
{
StoreItem: choiceRewards[1],
ItemCount: 1
} }
] },
}, Choices: payload.Choices,
{ PendingRewards: [
RequiredTotalXp: 1540,
Rewards: generateRandomRewards(
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalGoldRewards"
)
},
{
RequiredTotalXp: 1950,
Rewards: [
{ {
StoreItem: choiceRewards[2], RequiredTotalXp: 190,
ItemCount: 1 Rewards: [
} {
] StoreItem: "/Lotus/StoreItems/Upgrades/Mods/Aura/PlayerHealthAuraMod",
}, ItemCount: 1
{ }
RequiredTotalXp: 2430, ]
Rewards: [
{
StoreItem: choiceRewards[3],
ItemCount: 1
}
]
},
{
RequiredTotalXp: 2990,
Rewards: generateRandomRewards(
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalArcaneRewards"
)
},
{
RequiredTotalXp: 3640,
Rewards: [
{
StoreItem: choiceRewards[4],
ItemCount: 1
} }
// ...
] ]
} }
]; });
}; };
const hardModeChosenRewards: Record<string, string> = { interface IEndlessXpRequest {
Braton: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BratonIncarnonUnlocker", Mode: string; // "r"
Lato: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/LatoIncarnonUnlocker", Category: TEndlessXpCategory;
Skana: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/SkanaIncarnonUnlocker", Choices: string[];
Paris: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/ParisIncarnonUnlocker", }
Kunai: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/KunaiIncarnonUnlocker",
Boar: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BoarIncarnonUnlocker",
Gammacor: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/GammacorIncarnonUnlocker",
Anku: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/AnkuIncarnonUnlocker",
Gorgon: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/GorgonIncarnonUnlocker",
Angstrum: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/AngstrumIncarnonUnlocker",
Bo: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/BoIncarnonUnlocker",
Latron: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/LatronIncarnonUnlocker",
Furis: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/FurisIncarnonUnlocker",
Furax: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/FuraxIncarnonUnlocker",
Strun: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/StrunIncarnonUnlocker",
Lex: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/LexIncarnonUnlocker",
Magistar: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/MagistarIncarnonUnlocker",
Boltor: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BoltorIncarnonUnlocker",
Bronco: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/BroncoIncarnonUnlocker",
CeramicDagger: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/CeramicDaggerIncarnonUnlocker",
Torid: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/ToridIncarnonUnlocker",
DualToxocyst: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/DualToxocystIncarnonUnlocker",
DualIchor: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/DualIchorIncarnonUnlocker",
Miter: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/MiterIncarnonUnlocker",
Atomos: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/AtomosIncarnonUnlocker",
AckAndBrunt: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/AckAndBruntIncarnonUnlocker",
Soma: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/SomaIncarnonUnlocker",
Vasto: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/VastoIncarnonUnlocker",
NamiSolo: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/NamiSoloIncarnonUnlocker",
Burston: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BurstonIncarnonUnlocker",
Zylok: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/ZylokIncarnonUnlocker",
Sibear: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/SibearIncarnonUnlocker",
Dread: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/DreadIncarnonUnlocker",
Despair: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/DespairIncarnonUnlocker",
Hate: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/HateIncarnonUnlocker",
Dera: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/DeraIncarnonUnlocker",
Cestra: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/CestraIncarnonUnlocker",
Okina: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/OkinaIncarnonUnlocker",
Sybaris: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/SybarisIncarnonUnlocker",
Sicarus: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/SicarusIncarnonUnlocker",
RivenPrimary: "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawRifleRandomMod",
RivenSecondary: "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawPistolRandomMod",
RivenMelee: "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawMeleeRandomMod",
Kuva: "/Lotus/Types/Game/DuviriEndless/CircuitSteelPathBIGKuvaReward"
};
const generateHardModeRewards = (choices: string[]): IEndlessXpReward[] => {
return [
{
RequiredTotalXp: 285,
Rewards: generateRandomRewards(
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSilverRewards"
)
},
{
RequiredTotalXp: 600,
Rewards: generateRandomRewards(
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathArcaneRewards"
)
},
{
RequiredTotalXp: 945,
Rewards: generateRandomRewards(
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSilverRewards"
)
},
{
RequiredTotalXp: 1335,
Rewards: generateRandomRewards(
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSilverRewards"
)
},
{
RequiredTotalXp: 1785,
Rewards: [
{
StoreItem: hardModeChosenRewards[choices[0]],
ItemCount: 1
}
]
},
{
RequiredTotalXp: 2310,
Rewards: generateRandomRewards(
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathGoldRewards"
)
},
{
RequiredTotalXp: 2925,
Rewards: generateRandomRewards(
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathGoldRewards"
)
},
{
RequiredTotalXp: 3645,
Rewards: generateRandomRewards(
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathArcaneRewards"
)
},
{
RequiredTotalXp: 4485,
Rewards: generateRandomRewards(
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSteelEssenceRewards"
)
},
{
RequiredTotalXp: 5460,
Rewards: [
{
StoreItem: hardModeChosenRewards[choices[1]],
ItemCount: 1
}
]
}
];
};

View File

@ -1,8 +1,8 @@
import { toMongoDate } from "../../helpers/inventoryHelpers.ts"; import { toMongoDate } from "@/src/helpers/inventoryHelpers";
import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getInventory, updateEntratiVault } from "../../services/inventoryService.ts"; import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
export const entratiLabConquestModeController: RequestHandler = async (req, res) => { export const entratiLabConquestModeController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -11,7 +11,24 @@ export const entratiLabConquestModeController: RequestHandler = async (req, res)
"EntratiVaultCountResetDate EntratiVaultCountLastPeriod EntratiLabConquestUnlocked EchoesHexConquestUnlocked EchoesHexConquestActiveFrameVariants EchoesHexConquestActiveStickers EntratiLabConquestActiveFrameVariants EntratiLabConquestCacheScoreMission EchoesHexConquestCacheScoreMission" "EntratiVaultCountResetDate EntratiVaultCountLastPeriod EntratiLabConquestUnlocked EchoesHexConquestUnlocked EchoesHexConquestActiveFrameVariants EchoesHexConquestActiveStickers EntratiLabConquestActiveFrameVariants EntratiLabConquestCacheScoreMission EchoesHexConquestCacheScoreMission"
); );
const body = getJSONfromString<IEntratiLabConquestModeRequest>(String(req.body)); const body = getJSONfromString<IEntratiLabConquestModeRequest>(String(req.body));
updateEntratiVault(inventory); if (!inventory.EntratiVaultCountResetDate || Date.now() >= inventory.EntratiVaultCountResetDate.getTime()) {
const EPOCH = 1734307200 * 1000; // Mondays, amirite?
const day = Math.trunc((Date.now() - EPOCH) / 86400000);
const week = Math.trunc(day / 7);
const weekStart = EPOCH + week * 604800000;
const weekEnd = weekStart + 604800000;
inventory.EntratiVaultCountLastPeriod = 0;
inventory.EntratiVaultCountResetDate = new Date(weekEnd);
if (inventory.EntratiLabConquestUnlocked) {
inventory.EntratiLabConquestUnlocked = 0;
inventory.EntratiLabConquestActiveFrameVariants = [];
}
if (inventory.EchoesHexConquestUnlocked) {
inventory.EchoesHexConquestUnlocked = 0;
inventory.EchoesHexConquestActiveFrameVariants = [];
inventory.EchoesHexConquestActiveStickers = [];
}
}
if (body.BuyMode) { if (body.BuyMode) {
inventory.EntratiVaultCountLastPeriod! += 2; inventory.EntratiVaultCountLastPeriod! += 2;
if (body.IsEchoesDeepArchemedea) { if (body.IsEchoesDeepArchemedea) {
@ -32,7 +49,7 @@ export const entratiLabConquestModeController: RequestHandler = async (req, res)
} }
await inventory.save(); await inventory.save();
res.json({ res.json({
EntratiVaultCountResetDate: toMongoDate(inventory.EntratiVaultCountResetDate!), EntratiVaultCountResetDate: toMongoDate(inventory.EntratiVaultCountResetDate),
EntratiVaultCountLastPeriod: inventory.EntratiVaultCountLastPeriod, EntratiVaultCountLastPeriod: inventory.EntratiVaultCountLastPeriod,
EntratiLabConquestUnlocked: inventory.EntratiLabConquestUnlocked, EntratiLabConquestUnlocked: inventory.EntratiLabConquestUnlocked,
EntratiLabConquestCacheScoreMission: inventory.EntratiLabConquestCacheScoreMission, EntratiLabConquestCacheScoreMission: inventory.EntratiLabConquestCacheScoreMission,

View File

@ -1,10 +1,9 @@
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { addMiscItems, getInventory } from "../../services/inventoryService.ts"; import { addMiscItems, getInventory } from "@/src/services/inventoryService";
import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import type { WeaponTypeInternal } from "../../services/itemDataService.ts"; import { getRecipe, WeaponTypeInternal } from "@/src/services/itemDataService";
import { getRecipe } from "../../services/itemDataService.ts"; import { EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { EquipmentFeatures } from "../../types/equipmentTypes.ts";
export const evolveWeaponController: RequestHandler = async (req, res) => { export const evolveWeaponController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);

View File

@ -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;
}

View File

@ -1,7 +1,7 @@
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getSession } from "../../managers/sessionManager.ts"; import { getSession } from "@/src/managers/sessionManager";
import { logger } from "../../utils/logger.ts"; import { logger } from "@/src/utils/logger";
import type { IFindSessionRequest } from "../../types/session.ts"; import { IFindSessionRequest } from "@/src/types/session";
export const findSessionsController: RequestHandler = (_req, res) => { export const findSessionsController: RequestHandler = (_req, res) => {
const req = JSON.parse(String(_req.body)) as IFindSessionRequest; const req = JSON.parse(String(_req.body)) as IFindSessionRequest;

View File

@ -1,8 +1,8 @@
import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { addMiscItems, addStanding, getInventory } from "../../services/inventoryService.ts"; import { addMiscItems, addStanding, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import type { IMiscItem } from "../../types/inventoryTypes/inventoryTypes.ts"; import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import { ExportResources } from "warframe-public-export-plus"; import { ExportResources } from "warframe-public-export-plus";
export const fishmongerController: RequestHandler = async (req, res) => { export const fishmongerController: RequestHandler = async (req, res) => {
@ -30,14 +30,15 @@ export const fishmongerController: RequestHandler = async (req, res) => {
miscItemChanges.push({ ItemType: fish.ItemType, ItemCount: fish.ItemCount * -1 }); miscItemChanges.push({ ItemType: fish.ItemType, ItemCount: fish.ItemCount * -1 });
} }
addMiscItems(inventory, miscItemChanges); addMiscItems(inventory, miscItemChanges);
if (gainedStanding && syndicateTag) addStanding(inventory, syndicateTag, gainedStanding); let affiliationMod;
if (gainedStanding && syndicateTag) affiliationMod = addStanding(inventory, syndicateTag, gainedStanding);
await inventory.save(); await inventory.save();
res.json({ res.json({
InventoryChanges: { InventoryChanges: {
MiscItems: miscItemChanges MiscItems: miscItemChanges
}, },
SyndicateTag: syndicateTag, SyndicateTag: syndicateTag,
StandingChange: gainedStanding StandingChange: affiliationMod?.Standing || 0
}); });
}; };

View File

@ -1,91 +1,23 @@
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory, addMiscItems, addEquipment, occupySlot } from "../../services/inventoryService.ts"; import { getInventory, addMiscItems, addEquipment, occupySlot } from "@/src/services/inventoryService";
import type { IMiscItem, TFocusPolarity, TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts"; import { IMiscItem, TFocusPolarity, TEquipmentKey, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts"; import { logger } from "@/src/utils/logger";
import { logger } from "../../utils/logger.ts";
import { ExportFocusUpgrades } from "warframe-public-export-plus"; import { ExportFocusUpgrades } from "warframe-public-export-plus";
import { Inventory } from "../../models/inventoryModels/inventoryModel.ts"; import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { version_compare } from "../../helpers/inventoryHelpers.ts"; import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
export const focusController: RequestHandler = async (req, res) => { export const focusController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req); const accountId = await getAccountIdForRequest(req);
switch (req.query.op) {
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) {
default: default:
logger.error("Unhandled focus op type: " + String(req.query.op)); logger.error("Unhandled focus op type: " + String(req.query.op));
logger.debug(String(req.body)); logger.debug(String(req.body));
res.end(); res.end();
break; break;
case "InstallLens": { case FocusOperation.InstallLens: {
const request = JSON.parse(String(req.body)) as ILensInstallRequest; const request = JSON.parse(String(req.body)) as ILensInstallRequest;
const inventory = await getInventory(account._id.toString()); const inventory = await getInventory(accountId);
const item = inventory[request.Category].id(request.WeaponId); const item = inventory[request.Category].id(request.WeaponId);
if (item) { if (item) {
item.FocusLens = request.LensType; item.FocusLens = request.LensType;
@ -103,15 +35,15 @@ export const focusController: RequestHandler = async (req, res) => {
}); });
break; break;
} }
case "UnlockWay": { case FocusOperation.UnlockWay: {
const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType; const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType;
const focusPolarity = focusTypeToPolarity(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; const cost = inventory.FocusAbility ? 50_000 : 0;
inventory.FocusAbility ??= focusType; inventory.FocusAbility ??= focusType;
inventory.FocusUpgrades.push({ ItemType: focusType }); inventory.FocusUpgrades.push({ ItemType: focusType });
if (cost) { if (inventory.FocusXP) {
inventory.FocusXP![focusPolarity]! -= cost; inventory.FocusXP[focusPolarity] -= cost;
} }
await inventory.save(); await inventory.save();
res.json({ res.json({
@ -120,57 +52,31 @@ export const focusController: RequestHandler = async (req, res) => {
}); });
break; break;
} }
case "IncreasePool": { case FocusOperation.ActivateWay: {
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": {
const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType; const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType;
await Inventory.updateOne( await Inventory.updateOne(
{ {
accountOwnerId: account._id.toString() accountOwnerId: accountId
}, },
{ {
FocusAbility: focusType FocusAbility: focusType
} }
); );
res.json({ res.end();
FocusUpgrade: { ItemType: focusType }
});
break; break;
} }
case "UnlockUpgrade": { case FocusOperation.UnlockUpgrade: {
const request = JSON.parse(String(req.body)) as IUnlockUpgradeRequest; const request = JSON.parse(String(req.body)) as IUnlockUpgradeRequest;
const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]); const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]);
const inventory = await getInventory(account._id.toString()); const inventory = await getInventory(accountId);
let cost = 0; let cost = 0;
for (const focusType of request.FocusTypes) { for (const focusType of request.FocusTypes) {
if (focusType in ExportFocusUpgrades) { cost += ExportFocusUpgrades[focusType].baseFocusPointCost;
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`);
}
inventory.FocusUpgrades.push({ ItemType: focusType, Level: 0 }); inventory.FocusUpgrades.push({ ItemType: focusType, Level: 0 });
} }
inventory.FocusXP![focusPolarity]! -= cost; inventory.FocusXP![focusPolarity] -= cost;
await inventory.save(); await inventory.save();
res.json({ res.json({
FocusTypes: request.FocusTypes, FocusTypes: request.FocusTypes,
@ -178,22 +84,17 @@ export const focusController: RequestHandler = async (req, res) => {
}); });
break; break;
} }
case "LevelUpUpgrade": case FocusOperation.LevelUpUpgrade: {
case "UpdateUpgrade": {
const request = JSON.parse(String(req.body)) as ILevelUpUpgradeRequest; const request = JSON.parse(String(req.body)) as ILevelUpUpgradeRequest;
const focusPolarity = focusTypeToPolarity(request.FocusInfos[0].ItemType); const focusPolarity = focusTypeToPolarity(request.FocusInfos[0].ItemType);
const inventory = await getInventory(account._id.toString()); const inventory = await getInventory(accountId);
let cost = 0; let cost = 0;
for (const focusUpgrade of request.FocusInfos) { for (const focusUpgrade of request.FocusInfos) {
cost += focusUpgrade.FocusXpCost; cost += focusUpgrade.FocusXpCost;
const focusUpgradeDb = inventory.FocusUpgrades.find(entry => entry.ItemType == focusUpgrade.ItemType)!; const focusUpgradeDb = inventory.FocusUpgrades.find(entry => entry.ItemType == focusUpgrade.ItemType)!;
if (op == "UpdateUpgrade") { focusUpgradeDb.Level = focusUpgrade.Level;
focusUpgradeDb.IsActive = focusUpgrade.IsActive;
} else {
focusUpgradeDb.Level = focusUpgrade.Level;
}
} }
inventory.FocusXP![focusPolarity]! -= cost; inventory.FocusXP![focusPolarity] -= cost;
await inventory.save(); await inventory.save();
res.json({ res.json({
FocusInfos: request.FocusInfos, FocusInfos: request.FocusInfos,
@ -201,26 +102,25 @@ export const focusController: RequestHandler = async (req, res) => {
}); });
break; break;
} }
case "SentTrainingAmplifier": { case FocusOperation.SentTrainingAmplifier: {
const request = JSON.parse(String(req.body)) as ISentTrainingAmplifierRequest; const request = JSON.parse(String(req.body)) as ISentTrainingAmplifierRequest;
const inventory = await getInventory(account._id.toString()); const parts: string[] = [
const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, { "/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingGrip",
ModularParts: [ "/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingChassis",
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingGrip", "/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingBarrel"
"/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); occupySlot(inventory, InventorySlot.AMPS, false);
await inventory.save(); await inventory.save();
res.json(inventoryChanges.OperatorAmps![0]); res.json((inventoryChanges.OperatorAmps as IEquipmentClient[])[0]);
break; break;
} }
case "UnbindUpgrade": { case FocusOperation.UnbindUpgrade: {
const request = JSON.parse(String(req.body)) as IUnbindUpgradeRequest; const request = JSON.parse(String(req.body)) as IUnbindUpgradeRequest;
const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]); const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]);
const inventory = await getInventory(account._id.toString()); const inventory = await getInventory(accountId);
inventory.FocusXP![focusPolarity]! -= 750_000 * request.FocusTypes.length; inventory.FocusXP![focusPolarity] -= 750_000 * request.FocusTypes.length;
addMiscItems(inventory, [ addMiscItems(inventory, [
{ {
ItemType: "/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantItem", ItemType: "/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantItem",
@ -246,7 +146,7 @@ export const focusController: RequestHandler = async (req, res) => {
}); });
break; break;
} }
case "ConvertShard": { case FocusOperation.ConvertShard: {
const request = JSON.parse(String(req.body)) as IConvertShardRequest; const request = JSON.parse(String(req.body)) as IConvertShardRequest;
// Tally XP // Tally XP
let xp = 0; let xp = 0;
@ -264,11 +164,9 @@ export const focusController: RequestHandler = async (req, res) => {
for (const shard of request.Shards) { for (const shard of request.Shards) {
shard.ItemCount *= -1; shard.ItemCount *= -1;
} }
const inventory = await getInventory(account._id.toString()); const inventory = await getInventory(accountId);
const polarity = request.Polarity; inventory.FocusXP ??= { AP_POWER: 0, AP_TACTIC: 0, AP_DEFENSE: 0, AP_ATTACK: 0, AP_WARD: 0 };
inventory.FocusXP ??= {}; inventory.FocusXP[request.Polarity] += xp;
inventory.FocusXP[polarity] ??= 0;
inventory.FocusXP[polarity] += xp;
addMiscItems(inventory, request.Shards); addMiscItems(inventory, request.Shards);
await inventory.save(); await inventory.save();
break; break;
@ -276,8 +174,7 @@ export const focusController: RequestHandler = async (req, res) => {
} }
}; };
// Focus 3.0 enum FocusOperation {
enum Focus3Operation {
InstallLens = "1", InstallLens = "1",
UnlockWay = "2", UnlockWay = "2",
UnlockUpgrade = "3", UnlockUpgrade = "3",
@ -288,20 +185,6 @@ enum Focus3Operation {
ConvertShard = "9" 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 // For UnlockWay & ActivateWay
interface IWayRequest { interface IWayRequest {
FocusType: string; FocusType: string;
@ -311,13 +194,6 @@ interface IUnlockUpgradeRequest {
FocusTypes: string[]; FocusTypes: string[];
} }
// Focus 2.0
interface IIncreasePoolRequest {
FocusType: string;
CurrentTotalCapacity: number;
NewTotalCapacity: number;
}
interface ILevelUpUpgradeRequest { interface ILevelUpUpgradeRequest {
FocusInfos: { FocusInfos: {
ItemType: string; ItemType: string;
@ -325,7 +201,6 @@ interface ILevelUpUpgradeRequest {
IsUniversal: boolean; IsUniversal: boolean;
Level: number; Level: number;
IsActiveAbility: boolean; IsActiveAbility: boolean;
IsActive?: number; // Focus 2.0
}[]; }[];
} }
@ -351,7 +226,7 @@ interface ILensInstallRequest {
// Works for ways & upgrades // Works for ways & upgrades
const focusTypeToPolarity = (type: string): TFocusPolarity => { 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 = { const shardValues = {
@ -360,19 +235,3 @@ const shardValues = {
"/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantItem": 25_000, "/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantItem": 25_000,
"/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantTierTwoItem": 40_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
];

View File

@ -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[];
}

View File

@ -1,15 +1,23 @@
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import { ExportResources } from "warframe-public-export-plus"; import { ExportResources } from "warframe-public-export-plus";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { addFusionTreasures, addMiscItems, getInventory } from "../../services/inventoryService.ts"; import { addFusionTreasures, addMiscItems, getInventory } from "@/src/services/inventoryService";
import type { IMiscItem } from "../../types/inventoryTypes/inventoryTypes.ts"; import { IFusionTreasure, IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import { parseFusionTreasure } from "../../helpers/inventoryHelpers.ts";
interface IFusionTreasureRequest { interface IFusionTreasureRequest {
oldTreasureName: string; oldTreasureName: string;
newTreasureName: 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) => { export const fusionTreasuresController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);

View File

@ -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[][]>;
}

View File

@ -1,8 +1,8 @@
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { updateGeneric } from "../../services/inventoryService.ts"; import { updateGeneric } from "@/src/services/inventoryService";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import type { IGenericUpdate } from "../../types/genericUpdate.ts"; 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. // 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. // SpaceNinjaServer supports both endpoints right now.

View File

@ -1,8 +1,8 @@
import { Alliance, Guild } from "../../models/guildModel.ts"; import { Alliance, Guild } from "@/src/models/guildModel";
import { getAllianceClient } from "../../services/guildService.ts"; import { getAllianceClient } from "@/src/services/guildService";
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
export const getAllianceController: RequestHandler = async (req, res) => { export const getAllianceController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -18,7 +18,6 @@ export const getAllianceController: RequestHandler = async (req, res) => {
res.end(); res.end();
}; };
// POST request since U27
/*interface IGetAllianceRequest { /*interface IGetAllianceRequest {
memberCount: number; memberCount: number;
clanLeaderName: string; clanLeaderName: string;

View File

@ -1,10 +1,8 @@
import { DailyDeal } from "../../models/worldStateModel.ts"; import { RequestHandler } from "express";
import type { RequestHandler } from "express";
export const getDailyDealStockLevelsController: RequestHandler = async (req, res) => { export const getDailyDealStockLevelsController: RequestHandler = (req, res) => {
const dailyDeal = (await DailyDeal.findOne({ StoreItem: req.query.productName }, "AmountSold"))!;
res.json({ res.json({
StoreItem: req.query.productName, StoreItem: req.query.productName,
AmountSold: dailyDeal.AmountSold AmountSold: 0
}); });
}; };

View File

@ -1,54 +1,15 @@
import { toOid } from "../../helpers/inventoryHelpers.ts"; import { Request, Response } from "express";
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";
// POST with {} instead of GET as of 38.5.0 // POST with {} instead of GET as of 38.5.0
export const getFriendsController: RequestHandler = async (req: Request, res: Response) => { const getFriendsController = (_request: Request, response: Response): void => {
const accountId = await getAccountIdForRequest(req); response.writeHead(200, {
const response: IGetFriendsResponse = { //Connection: "keep-alive",
Current: [], //"Content-Encoding": "gzip",
IncomingFriendRequests: [], "Content-Type": "text/html",
OutgoingFriendRequests: [] // charset: "UTF - 8",
}; "Content-Length": "3"
const [internalFriendships, externalFriendships] = await Promise.all([ });
Friendship.find({ owner: accountId }), response.end(Buffer.from([0x7b, 0x7d, 0x0a]));
Friendship.find({ friend: accountId }, "owner Note")
]);
for (const externalFriendship of externalFriendships) {
if (!internalFriendships.find(x => x.friend.equals(externalFriendship.owner))) {
response.IncomingFriendRequests.push({
_id: toOid(externalFriendship.owner),
Note: externalFriendship.Note
});
}
}
for (const internalFriendship of internalFriendships) {
const friendInfo: IFriendInfo = {
_id: toOid(internalFriendship.friend)
};
if (externalFriendships.find(x => x.owner.equals(internalFriendship.friend))) {
response.Current.push(friendInfo);
} else {
response.OutgoingFriendRequests.push(friendInfo);
}
}
const promises: Promise<void>[] = [];
for (const arr of Object.values(response)) {
for (const friendInfo of arr) {
promises.push(addAccountDataToFriendInfo(friendInfo));
promises.push(addInventoryDataToFriendInfo(friendInfo));
}
}
await Promise.all(promises);
res.json(response);
}; };
// interface IGetFriendsResponse { export { getFriendsController };
// Current: IFriendInfo[];
// IncomingFriendRequests: IFriendInfo[];
// OutgoingFriendRequests: IFriendInfo[];
// }
type IGetFriendsResponse = Record<"Current" | "IncomingFriendRequests" | "OutgoingFriendRequests", IFriendInfo[]>;

View File

@ -1,8 +1,8 @@
import { GuildMember } from "../../models/guildModel.ts"; import { GuildMember } from "@/src/models/guildModel";
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import type { IGuildMemberClient } from "../../types/guildTypes.ts"; import { IGuildMemberClient } from "@/src/types/guildTypes";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
export const getGuildContributionsController: RequestHandler = async (req, res) => { export const getGuildContributionsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);

View File

@ -1,13 +1,13 @@
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import { Guild } from "../../models/guildModel.ts"; import { Guild } from "@/src/models/guildModel";
import { getAccountForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { logger } from "../../utils/logger.ts"; import { logger } from "@/src/utils/logger";
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "@/src/services/inventoryService";
import { createUniqueClanName, getGuildClient } from "../../services/guildService.ts"; import { createUniqueClanName, getGuildClient } from "@/src/services/guildService";
export const getGuildController: RequestHandler = async (req, res) => { export const getGuildController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req); const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(account._id.toString(), "GuildId"); const inventory = await getInventory(accountId, "GuildId");
if (inventory.GuildId) { if (inventory.GuildId) {
const guild = await Guild.findById(inventory.GuildId); const guild = await Guild.findById(inventory.GuildId);
if (guild) { if (guild) {
@ -24,7 +24,7 @@ export const getGuildController: RequestHandler = async (req, res) => {
guild.CeremonyResetDate = undefined; guild.CeremonyResetDate = undefined;
await guild.save(); await guild.save();
} }
res.json(await getGuildClient(guild, account)); res.json(await getGuildClient(guild, accountId));
return; return;
} }
} }

View File

@ -1,8 +1,7 @@
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { Guild } from "../../models/guildModel.ts"; import { Guild } from "@/src/models/guildModel";
import { getDojoClient } from "../../services/guildService.ts"; import { getDojoClient } from "@/src/services/guildService";
import { Account } from "../../models/loginModel.ts";
export const getGuildDojoController: RequestHandler = async (req, res) => { export const getGuildDojoController: RequestHandler = async (req, res) => {
const guildId = req.query.guildId as string; const guildId = req.query.guildId as string;
@ -19,15 +18,14 @@ export const getGuildDojoController: RequestHandler = async (req, res) => {
_id: new Types.ObjectId(), _id: new Types.ObjectId(),
pf: "/Lotus/Levels/ClanDojo/DojoHall.level", pf: "/Lotus/Levels/ClanDojo/DojoHall.level",
ppf: "", ppf: "",
CompletionTime: new Date(Date.now() - 1000), CompletionTime: new Date(Date.now()),
DecoCapacity: 600 DecoCapacity: 600
}); });
await guild.save(); await guild.save();
} }
const payload: IGetGuildDojoRequest = req.body ? (JSON.parse(String(req.body)) as IGetGuildDojoRequest) : {}; const payload: IGetGuildDojoRequest = req.body ? (JSON.parse(String(req.body)) as IGetGuildDojoRequest) : {};
const account = await Account.findById(req.query.accountId as string); res.json(await getDojoClient(guild, 0, payload.ComponentId));
res.json(await getDojoClient(guild, 0, payload.ComponentId, account?.BuildLabel));
}; };
interface IGetGuildDojoRequest { interface IGetGuildDojoRequest {

View File

@ -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({});
};

View File

@ -1,9 +1,9 @@
import { toMongoDate } from "../../helpers/inventoryHelpers.ts"; import { toMongoDate } from "@/src/helpers/inventoryHelpers";
import { Guild } from "../../models/guildModel.ts"; import { Guild } from "@/src/models/guildModel";
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import type { IMongoDate } from "../../types/commonTypes.ts"; import { IMongoDate } from "@/src/types/commonTypes";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
export const getGuildLogController: RequestHandler = async (req, res) => { export const getGuildLogController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);

View File

@ -1,9 +1,9 @@
import { toOid } from "../../helpers/inventoryHelpers.ts"; import { toOid } from "@/src/helpers/inventoryHelpers";
import { Account, Ignore } from "../../models/loginModel.ts"; import { Account, Ignore } from "@/src/models/loginModel";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import type { IFriendInfo } from "../../types/friendTypes.ts"; import { IFriendInfo } from "@/src/types/guildTypes";
import { parallelForeach } from "../../utils/async-utils.ts"; import { parallelForeach } from "@/src/utils/async-utils";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
export const getIgnoredUsersController: RequestHandler = async (req, res) => { export const getIgnoredUsersController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);

View File

@ -1,7 +1,7 @@
import { Inventory } from "../../models/inventoryModels/inventoryModel.ts"; import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { generateRewardSeed } from "@/src/services/inventoryService";
import { generateRewardSeed } from "../../services/rngService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
export const getNewRewardSeedController: RequestHandler = async (req, res) => { export const getNewRewardSeedController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);

View File

@ -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 });
}
};

View File

@ -1,33 +1,43 @@
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { config } from "@/src/services/configService";
import { createGarden, getPersonalRooms } from "../../services/personalRoomsService.ts"; import allShipFeatures from "@/static/fixed_responses/allShipFeatures.json";
import type { IGetShipResponse, IPersonalRoomsClient } from "../../types/personalRoomsTypes.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { getLoadout } from "../../services/loadoutService.ts"; import { getPersonalRooms } from "@/src/services/personalRoomsService";
import { toOid } from "../../helpers/inventoryHelpers.ts"; 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) => { export const getShipController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const personalRoomsDb = await getPersonalRooms(accountId); const personalRoomsDb = await getPersonalRooms(accountId);
const personalRooms = personalRoomsDb.toJSON<IPersonalRooms>();
// Setup gardening if it's missing. Maybe should be done as part of some quest completion in the future.
if (personalRoomsDb.Apartment.Gardening.Planters.length == 0) {
personalRoomsDb.Apartment.Gardening = createGarden();
await personalRoomsDb.save();
}
const personalRooms = personalRoomsDb.toJSON<IPersonalRoomsClient>();
const loadout = await getLoadout(accountId); const loadout = await getLoadout(accountId);
const ship = await getShip(personalRoomsDb.activeShipId, "ShipAttachments SkinFlavourItem");
const getShipResponse: IGetShipResponse = { const getShipResponse: IGetShipResponse = {
ShipOwnerId: accountId, ShipOwnerId: accountId,
LoadOutInventory: { LoadOutPresets: loadout.toJSON() }, LoadOutInventory: { LoadOutPresets: loadout.toJSON() },
Ship: { Ship: {
...personalRooms.Ship, ...personalRooms.Ship,
ShipId: toOid(personalRoomsDb.activeShipId) ShipId: toOid(personalRoomsDb.activeShipId),
ShipInterior: {
Colors: personalRooms.ShipInteriorColors,
ShipAttachments: ship.ShipAttachments,
SkinFlavourItem: ship.SkinFlavourItem
},
FavouriteLoadoutId: personalRooms.Ship.FavouriteLoadoutId
? toOid(personalRooms.Ship.FavouriteLoadoutId)
: undefined
}, },
Apartment: personalRooms.Apartment, Apartment: personalRooms.Apartment,
TailorShop: personalRooms.TailorShop TailorShop: personalRooms.TailorShop
}; };
if (config.unlockAllShipFeatures) {
getShipResponse.Ship.Features = allShipFeatures;
}
res.json(getShipResponse); res.json(getShipResponse);
}; };

View File

@ -1,29 +1,14 @@
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import { applyStandingToVendorManifest, getVendorManifestByTypeName } from "../../services/serversideVendorsService.ts"; import { getVendorManifestByTypeName } from "@/src/services/serversideVendorsService";
import { getInventory } from "../../services/inventoryService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { config } from "../../services/configService.ts";
export const getVendorInfoController: RequestHandler = async (req, res) => { export const getVendorInfoController: RequestHandler = (req, res) => {
let manifest = getVendorManifestByTypeName(req.query.vendor as string); if (typeof req.query.vendor == "string") {
if (!manifest) { const manifest = getVendorManifestByTypeName(req.query.vendor);
throw new Error(`Unknown vendor: ${req.query.vendor as string}`); if (!manifest) {
} throw new Error(`Unknown vendor: ${req.query.vendor}`);
// For testing purposes, authenticating with this endpoint is optional here, but would be required on live.
if (req.query.accountId) {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
manifest = applyStandingToVendorManifest(inventory, manifest);
if (config.dev?.keepVendorsExpired) {
manifest = {
VendorInfo: {
...manifest.VendorInfo,
Expiry: { $date: { $numberLong: "0" } }
}
};
} }
res.json(manifest);
} else {
res.status(400).end();
} }
res.json(manifest);
}; };

View File

@ -1,9 +1,9 @@
import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { crackRelic } from "../../helpers/relicHelper.ts"; import { crackRelic } from "@/src/helpers/relicHelper";
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import type { IVoidTearParticipantInfo } from "../../types/requestTypes.ts"; import { IVoidTearParticipantInfo } from "@/src/types/requestTypes";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
export const getVoidProjectionRewardsController: RequestHandler = async (req, res) => { export const getVoidProjectionRewardsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -11,11 +11,7 @@ export const getVoidProjectionRewardsController: RequestHandler = async (req, re
if (data.ParticipantInfo.QualifiesForReward && !data.ParticipantInfo.HaveRewardResponse) { if (data.ParticipantInfo.QualifiesForReward && !data.ParticipantInfo.HaveRewardResponse) {
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
const reward = await crackRelic(inventory, data.ParticipantInfo); await crackRelic(inventory, data.ParticipantInfo);
if (!inventory.MissionRelicRewards || inventory.MissionRelicRewards.length >= data.CurrentWave) {
inventory.MissionRelicRewards = [];
}
inventory.MissionRelicRewards.push({ ItemType: reward.type, ItemCount: reward.itemCount });
await inventory.save(); await inventory.save();
} }

View File

@ -1,35 +1,16 @@
import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Account } from "../../models/loginModel.ts"; import { Account } from "@/src/models/loginModel";
import { areFriends } from "../../services/friendService.ts"; import { createMessage } from "@/src/services/inboxService";
import { createMessage } from "../../services/inboxService.ts"; import { getInventory, updateCurrency } from "@/src/services/inventoryService";
import { import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
combineInventoryChanges, import { IOid } from "@/src/types/commonTypes";
getEffectiveAvatarImageType, import { IPurchaseParams } from "@/src/types/purchaseTypes";
getInventory, import { RequestHandler } from "express";
updateCurrency import { ExportFlavour } from "warframe-public-export-plus";
} from "../../services/inventoryService.ts";
import { getAccountForRequest, getSuffixedName } from "../../services/loginService.ts";
import { handleDailyDealPurchase, handleStoreItemAcquisition } from "../../services/purchaseService.ts";
import type { IOid } from "../../types/commonTypes.ts";
import type { IPurchaseParams, IPurchaseResponse } from "../../types/purchaseTypes.ts";
import { PurchaseSource } from "../../types/purchaseTypes.ts";
import type { RequestHandler } from "express";
import { ExportBundles, ExportFlavour } from "warframe-public-export-plus";
const checkPurchaseParams = (params: IPurchaseParams): boolean => {
switch (params.Source) {
case PurchaseSource.Market:
return params.UsePremium;
case PurchaseSource.DailyDeal:
return true;
}
return false;
};
export const giftingController: RequestHandler = async (req, res) => { export const giftingController: RequestHandler = async (req, res) => {
const data = getJSONfromString<IGiftingRequest>(String(req.body)); const data = getJSONfromString<IGiftingRequest>(String(req.body));
if (!checkPurchaseParams(data.PurchaseParams)) { if (data.PurchaseParams.Source != 0 || !data.PurchaseParams.UsePremium) {
throw new Error(`unexpected purchase params in gifting request: ${String(req.body)}`); throw new Error(`unexpected purchase params in gifting request: ${String(req.body)}`);
} }
@ -49,11 +30,8 @@ export const giftingController: RequestHandler = async (req, res) => {
} }
// Cannot gift to players who have gifting disabled. // Cannot gift to players who have gifting disabled.
const senderAccount = await getAccountForRequest(req); // TODO: Also consider GIFT_MODE_FRIENDS once friends are implemented
if ( if (inventory.Settings?.GiftMode == "GIFT_MODE_NONE") {
inventory.Settings?.GiftMode == "GIFT_MODE_NONE" ||
(inventory.Settings?.GiftMode == "GIFT_MODE_FRIENDS" && !(await areFriends(account._id, senderAccount._id)))
) {
res.status(400).send("17").end(); res.status(400).send("17").end();
return; return;
} }
@ -62,7 +40,11 @@ export const giftingController: RequestHandler = async (req, res) => {
// TODO: Cannot gift archwing items to players that have not completed the archwing quest. (Code 7) // TODO: Cannot gift archwing items to players that have not completed the archwing quest. (Code 7)
// TODO: Cannot gift necramechs to players that have not completed heart of deimos. (Code 20) // TODO: Cannot gift necramechs to players that have not completed heart of deimos. (Code 20)
const senderInventory = await getInventory(senderAccount._id.toString()); const senderAccount = await getAccountForRequest(req);
const senderInventory = await getInventory(
senderAccount._id.toString(),
"PremiumCredits PremiumCreditsFree ActiveAvatarImageType GiftsRemaining"
);
if (senderInventory.GiftsRemaining == 0) { if (senderInventory.GiftsRemaining == 0) {
res.status(400).send("10").end(); res.status(400).send("10").end();
@ -70,23 +52,7 @@ export const giftingController: RequestHandler = async (req, res) => {
} }
senderInventory.GiftsRemaining -= 1; senderInventory.GiftsRemaining -= 1;
const response: IPurchaseResponse = { updateCurrency(senderInventory, data.PurchaseParams.ExpectedPrice, true);
InventoryChanges: {}
};
if (data.PurchaseParams.Source == PurchaseSource.DailyDeal) {
await handleDailyDealPurchase(senderInventory, data.PurchaseParams, response);
} else {
updateCurrency(senderInventory, data.PurchaseParams.ExpectedPrice, true, response.InventoryChanges);
}
if (data.PurchaseParams.StoreItem in ExportBundles) {
const bundle = ExportBundles[data.PurchaseParams.StoreItem];
if (bundle.giftingBonus) {
combineInventoryChanges(
response.InventoryChanges,
(await handleStoreItemAcquisition(bundle.giftingBonus, senderInventory)).InventoryChanges
);
}
}
await senderInventory.save(); await senderInventory.save();
const senderName = getSuffixedName(senderAccount); const senderName = getSuffixedName(senderAccount);
@ -105,7 +71,7 @@ export const giftingController: RequestHandler = async (req, res) => {
} }
], ],
sub: "/Lotus/Language/Menu/GiftReceivedSubject", sub: "/Lotus/Language/Menu/GiftReceivedSubject",
icon: ExportFlavour[getEffectiveAvatarImageType(senderInventory)].icon, icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
gifts: [ gifts: [
{ {
GiftType: data.PurchaseParams.StoreItem GiftType: data.PurchaseParams.StoreItem
@ -114,7 +80,7 @@ export const giftingController: RequestHandler = async (req, res) => {
} }
]); ]);
res.json(response); res.end();
}; };
interface IGiftingRequest { interface IGiftingRequest {

View File

@ -1,14 +1,11 @@
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { broadcastInventoryUpdate } from "../../services/wsService.ts"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { addMiscItems, getInventory } from "@/src/services/inventoryService";
import { addMiscItems, getInventory } from "../../services/inventoryService.ts"; import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts"; import { ArtifactPolarity, EquipmentFeatures, IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import type { ArtifactPolarity } from "../../types/inventoryTypes/commonInventoryTypes.ts";
import { ExportRecipes } from "warframe-public-export-plus"; import { ExportRecipes } from "warframe-public-export-plus";
import type { IInventoryChanges } from "../../types/purchaseTypes.ts"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import type { IEquipmentClient } from "../../types/equipmentTypes.ts";
import { EquipmentFeatures } from "../../types/equipmentTypes.ts";
interface IGildWeaponRequest { interface IGildWeaponRequest {
ItemName: string; ItemName: string;
@ -75,5 +72,4 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
InventoryChanges: inventoryChanges, InventoryChanges: inventoryChanges,
AffiliationMods: affiliationMods AffiliationMods: affiliationMods
}); });
broadcastInventoryUpdate(req);
}; };

View File

@ -1,17 +1,16 @@
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { parseString } from "@/src/helpers/general";
import { getInventory } from "../../services/inventoryService.ts"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { giveKeyChainItem } from "../../services/questService.ts"; import { getInventory } from "@/src/services/inventoryService";
import type { IKeyChainRequest } from "../../types/requestTypes.ts"; import { giveKeyChainItem } from "@/src/services/questService";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { IKeyChainRequest } from "@/src/types/requestTypes";
export const giveKeyChainTriggeredItemsController: RequestHandler = async (req, res) => { export const giveKeyChainTriggeredItemsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = parseString(req.query.accountId);
const keyChainInfo = getJSONfromString<IKeyChainRequest>((req.body as string).toString()); const keyChainInfo = getJSONfromString<IKeyChainRequest>((req.body as string).toString());
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
const questKey = inventory.QuestKeys.find(qk => qk.ItemType === keyChainInfo.KeyChain)!; const inventoryChanges = await giveKeyChainItem(inventory, keyChainInfo);
const inventoryChanges = await giveKeyChainItem(inventory, keyChainInfo, questKey);
await inventory.save(); await inventory.save();
res.send(inventoryChanges); res.send(inventoryChanges);

View File

@ -1,16 +1,15 @@
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { giveKeyChainMessage } from "../../services/questService.ts"; import { giveKeyChainMessage } from "@/src/services/questService";
import type { IKeyChainRequest } from "../../types/requestTypes.ts"; import { IKeyChainRequest } from "@/src/types/requestTypes";
import type { RequestHandler } from "express"; import { RequestHandler } from "express";
export const giveKeyChainTriggeredMessageController: RequestHandler = async (req, res) => { export const giveKeyChainTriggeredMessageController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const keyChainInfo = JSON.parse((req.body as Buffer).toString()) as IKeyChainRequest; const keyChainInfo = JSON.parse((req.body as Buffer).toString()) as IKeyChainRequest;
const inventory = await getInventory(accountId, "QuestKeys accountOwnerId"); const inventory = await getInventory(accountId, "QuestKeys");
const questKey = inventory.QuestKeys.find(qk => qk.ItemType === keyChainInfo.KeyChain)!; await giveKeyChainMessage(inventory, accountId, keyChainInfo);
await giveKeyChainMessage(inventory, keyChainInfo, questKey);
await inventory.save(); await inventory.save();
res.send(1); res.send(1);

Some files were not shown because too many files have changed in this diff Show More