Compare commits

..

6 Commits

Author SHA1 Message Date
Jānis
225cd83cea
change database name to not conflict with latest version 2024-12-09 20:06:11 +02:00
Jānis
e824087034 w.i.p mods 2024-09-07 23:32:59 +03:00
Jānis
fd2027b071 fix lint 2024-09-06 02:16:27 +03:00
Jānis
0af98bc6c2 feat: mission rewards & more
fixes mission rewards, item xp, tutorial
2024-09-06 02:02:26 +03:00
Jānis
3403d496b4 fix lint 2024-09-03 20:21:09 +03:00
Jānis
58b1cfc30f init 2024-09-03 17:23:42 +03:00
229 changed files with 21431 additions and 22962 deletions

View File

@ -1,5 +0,0 @@
**/.dockerignore
**/.git
Dockerfile*
.*
docker-data/

4
.env.example Normal file
View File

@ -0,0 +1,4 @@
# Docker may need a .env file for the following settings:
DATABASE_PORT=27017
DATABASE_USERNAME=root
DATABASE_PASSWORD=database

View File

@ -12,6 +12,7 @@
},
"rules": {
"@typescript-eslint/explicit-function-return-type": "warn",
"@typescript-eslint/explicit-module-boundary-types": "warn",
"@typescript-eslint/restrict-template-expressions": "warn",
"@typescript-eslint/restrict-plus-operands": "warn",
"@typescript-eslint/no-unsafe-member-access": "warn",
@ -22,13 +23,10 @@
"@typescript-eslint/no-unsafe-assignment": "warn",
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-loss-of-precision": "warn",
"@typescript-eslint/no-unnecessary-condition": "warn",
"no-case-declarations": "warn",
"prettier/prettier": "error",
"@typescript-eslint/semi": "error",
"no-mixed-spaces-and-tabs": "error",
"require-await": "off",
"@typescript-eslint/require-await": "error"
"no-mixed-spaces-and-tabs": "error"
},
"parser": "@typescript-eslint/parser",
"parserOptions": {

View File

@ -17,5 +17,6 @@ jobs:
node-version: ${{ matrix.version }}
- run: npm ci
- run: cp config.json.example config.json
- run: echo '{"version":"","buildLabel":"","matchmakingBuildId":""}' > static/data/buildConfig.json
- run: npm run build
- run: npm run lint

View File

@ -1,24 +0,0 @@
name: Build Docker image
on:
push:
branches:
- main
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Set up Docker buildx
uses: docker/setup-buildx-action@v3
- name: Log in to container registry
uses: docker/login-action@v3
with:
username: openwf
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
platforms: linux/amd64,linux/arm64
push: true
tags: |
openwf/spaceninjaserver:latest
openwf/spaceninjaserver:${{ github.sha }}

5
.gitignore vendored
View File

@ -15,7 +15,4 @@ yarn.lock
/logs
# MongoDB VSCode extension playground scripts
/database_scripts
# Default Docker directory
/docker-data
/database_scripts

19
.vscode/launch.json vendored
View File

@ -1,19 +0,0 @@
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug and Watch",
"runtimeArgs": ["-r", "tsconfig-paths/register", "-r", "ts-node/register", "--watch-path", "src"],
"args": ["${workspaceFolder}/src/index.ts"],
"console": "integratedTerminal"
}
]
}
//can use "console": "internalConsole" for VS Code's Debug Console. For that, forceConsole in logger.ts is needed to be true
//"internalConsoleOptions": "openOnSessionStart" can be useful then

View File

@ -1,28 +1,5 @@
FROM node:18-alpine3.19
FROM mongo as base
ENV APP_MONGODB_URL=mongodb://mongodb:27017/openWF
ENV APP_MY_ADDRESS=localhost
ENV APP_HTTP_PORT=80
ENV APP_HTTPS_PORT=443
ENV APP_AUTO_CREATE_ACCOUNT=true
ENV APP_SKIP_STORY_MODE_CHOICE=true
ENV APP_SKIP_TUTORIAL=true
ENV APP_SKIP_ALL_DIALOGUE=true
ENV APP_UNLOCK_ALL_SCANS=true
ENV APP_UNLOCK_ALL_MISSIONS=true
ENV APP_UNLOCK_ALL_QUESTS=true
ENV APP_COMPLETE_ALL_QUESTS=true
ENV APP_INFINITE_RESOURCES=true
ENV APP_UNLOCK_ALL_SHIP_FEATURES=true
ENV APP_UNLOCK_ALL_SHIP_DECORATIONS=true
ENV APP_UNLOCK_ALL_FLAVOUR_ITEMS=true
ENV APP_UNLOCK_ALL_SKINS=true
ENV APP_UNIVERSAL_POLARITY_EVERYWHERE=true
ENV APP_SPOOF_MASTERY_RANK=-1
EXPOSE 27017
RUN apk add --no-cache bash sed wget jq
COPY . /app
WORKDIR /app
ENTRYPOINT ["/app/docker-entrypoint.sh"]
CMD ["mongod"]

View File

@ -1,8 +1,3 @@
# Space Ninja Server
More information for the moment here: [https://discord.gg/PNNZ3asUuY](https://discord.gg/PNNZ3asUuY)
## config.json
- `logger.level` can be `fatal`, `error`, `warn`, `info`, `http`, `debug`, or `trace`.
- `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`.

View File

@ -1,24 +0,0 @@
@echo off
echo Updating SpaceNinjaServer...
git config remote.origin.url https://openwf.io/SpaceNinjaServer.git
git fetch --prune
git reset --hard origin/main
if exist static\data\0\ (
echo Updating stripped assets...
cd static\data\0\
git pull
cd ..\..\..\
)
echo Updating dependencies...
call npm i
call npm run build
call npm run start
echo SpaceNinjaServer seems to have crashed.
:a
pause > nul
goto a

View File

@ -1,31 +1,26 @@
{
"mongodbUrl": "mongodb://127.0.0.1:27017/openWF",
"mongodbUrl": "mongodb://127.0.0.1:27017/openWF_2013",
"logger": {
"files": true,
"level": "trace"
"level": "trace",
"__valid_levels": "fatal, error, warn, info, http, debug, trace"
},
"myAddress": "localhost",
"httpPort": 80,
"httpsPort": 443,
"administratorNames": [],
"autoCreateAccount": true,
"skipStoryModeChoice": true,
"skipTutorial": true,
"skipAllDialogue": true,
"unlockAllScans": true,
"unlockAllMissions": true,
"infiniteCredits": true,
"infinitePlatinum": true,
"infiniteEndo": true,
"infiniteRegalAya": true,
"unlockAllQuests": true,
"completeAllQuests": false,
"infiniteResources": true,
"unlockAllShipFeatures": true,
"unlockAllShipDecorations": true,
"unlockAllFlavourItems": true,
"unlockAllSkins": true,
"unlockAllCapturaScenes": true,
"universalPolarityEverywhere": true,
"unlockDoubleCapacityPotatoesEverywhere": true,
"unlockExilusEverywhere": true,
"unlockArcanesEverywhere": true,
"noDailyStandingLimits": true,
"spoofMasteryRank": -1
}

View File

@ -1,43 +1,24 @@
version: "3.9"
services:
spaceninjaserver:
# build: .
image: openwf/spaceninjaserver:latest
environment:
APP_MONGODB_URL: mongodb://openwfagent:spaceninjaserver@mongodb:27017/
# Following environment variables are set to default image values.
# Uncomment to edit.
# APP_MY_ADDRESS: localhost
# APP_HTTP_PORT: 80
# APP_HTTPS_PORT: 443
# APP_AUTO_CREATE_ACCOUNT: true
# APP_SKIP_STORY_MODE_CHOICE: true
# APP_SKIP_TUTORIAL: true
# APP_SKIP_ALL_DIALOGUE: true
# APP_UNLOCK_ALL_SCANS: true
# APP_UNLOCK_ALL_MISSIONS: true
# APP_UNLOCK_ALL_QUESTS: true
# APP_COMPLETE_ALL_QUESTS: true
# APP_INFINITE_RESOURCES: true
# APP_UNLOCK_ALL_SHIP_FEATURES: true
# APP_UNLOCK_ALL_SHIP_DECORATIONS: true
# APP_UNLOCK_ALL_FLAVOUR_ITEMS: true
# APP_UNLOCK_ALL_SKINS: true
# APP_UNIVERSAL_POLARITY_EVERYWHERE: true
# APP_SPOOF_MASTERY_RANK: -1
volumes:
- ./docker-data/static:/app/static/data
- ./docker-data/logs:/app/logs
ports:
- 80:80
- 443:443
depends_on:
- mongodb
mongodb:
image: docker.io/library/mongo:8.0.0-noble
container_name: mongodb
image: mongodb
restart: always
build:
context: .
dockerfile: Dockerfile
target: base
environment:
MONGO_INITDB_ROOT_USERNAME: openwfagent
MONGO_INITDB_ROOT_PASSWORD: spaceninjaserver
volumes:
- ./docker-data/database:/data/db
MONGO_INITDB_ROOT_USERNAME: ${DATABASE_USERNAME}
MONGO_INITDB_ROOT_PASSWORD: ${DATABASE_PASSWORD}
ports:
- ${DATABASE_PORT}:${DATABASE_PORT}
expose:
- "${DATABASE_PORT}"
networks:
- docker
networks:
docker:
external: true

View File

@ -1,23 +0,0 @@
#!/bin/bash
set -e
# Set up the configuration file using environment variables.
echo '{
"logger": {
"files": true,
"level": "trace",
"__valid_levels": "fatal, error, warn, info, http, debug, trace"
}
}
' > config.json
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

2184
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,32 +9,30 @@
"build": "tsc && copyfiles static/webui/** build",
"lint": "eslint --ext .ts .",
"lint:fix": "eslint --fix --ext .ts .",
"prettier": "prettier --write .",
"update-translations": "cd scripts && node update-translations.js"
"prettier": "prettier --write ."
},
"license": "GNU",
"dependencies": {
"copyfiles": "^2.4.1",
"express": "^5",
"mongoose": "^8.9.4",
"warframe-public-export-plus": "^0.5.30",
"warframe-riven-info": "^0.1.2",
"winston": "^3.17.0",
"express": "^5.0.0-beta.3",
"mongoose": "^8.4.5",
"warframe-public-export-plus": "^0.4.4",
"warframe-riven-info": "^0.1.1",
"winston": "^3.13.0",
"winston-daily-rotate-file": "^5.0.0"
},
"devDependencies": {
"@types/express": "^5",
"@types/express": "^4.17.20",
"@types/morgan": "^1.9.9",
"@typescript-eslint/eslint-plugin": "^7.18",
"@typescript-eslint/parser": "^7.18",
"@typescript-eslint/eslint-plugin": "^7.14",
"@typescript-eslint/parser": "^7.14",
"eslint": "^8.56.0",
"eslint-plugin-prettier": "^5.2.3",
"eslint-plugin-prettier": "^5.1.3",
"morgan": "^1.10.0",
"prettier": "^3.4.2",
"ts-node": "^10.9.2",
"prettier": "^3.3.2",
"ts-node-dev": "^2.0.0",
"tsconfig-paths": "^4.2.0",
"typescript": ">=4.7.4 <5.6.0"
"typescript": "^5.5"
},
"engines": {
"node": ">=18.15.0",

View File

@ -1,46 +0,0 @@
// Based on http://209.141.38.3/OpenWF/Translations/src/branch/main/update.php
// Converted via ChatGPT-4o
const fs = require("fs");
function extractStrings(content) {
const regex = /([a-zA-Z_]+): `([^`]*)`,/g;
let matches;
const strings = {};
while ((matches = regex.exec(content)) !== null) {
strings[matches[1]] = matches[2];
}
return strings;
}
const source = fs.readFileSync("../static/webui/translations/en.js", "utf8");
const sourceStrings = extractStrings(source);
const sourceLines = source.split("\n");
fs.readdirSync("../static/webui/translations").forEach(file => {
if (fs.lstatSync(`../static/webui/translations/${file}`).isFile() && file !== "en.js") {
const content = fs.readFileSync(`../static/webui/translations/${file}`, "utf8");
const targetStrings = extractStrings(content);
const contentLines = content.split("\n");
const fileHandle = fs.openSync(`../static/webui/translations/${file}`, "w");
fs.writeSync(fileHandle, contentLines[0] + "\n");
sourceLines.forEach(line => {
const strings = extractStrings(line);
if (Object.keys(strings).length > 0) {
Object.entries(strings).forEach(([key, value]) => {
if (targetStrings.hasOwnProperty(key)) {
fs.writeSync(fileHandle, ` ${key}: \`${targetStrings[key]}\`,\n`);
} else {
fs.writeSync(fileHandle, ` ${key}: \`[UNTRANSLATED] ${value}\`,\n`);
}
});
} else if (line.length) {
fs.writeSync(fileHandle, line + "\n");
}
});
fs.closeSync(fileHandle);
}
});

View File

@ -1,34 +1,65 @@
import express from "express";
import bodyParser from "body-parser";
import { unknownEndpointHandler } from "@/src/middleware/middleware";
import { requestLogger } from "@/src/middleware/morgenMiddleware";
import { errorHandler } from "@/src/middleware/errorHandler";
import { apiRouter } from "@/src/routes/api";
//import { testRouter } from "@/src/routes/test";
import { cacheRouter } from "@/src/routes/cache";
import bodyParser from "body-parser";
import { steamPacksController } from "@/src/controllers/misc/steamPacksController";
import { customRouter } from "@/src/routes/custom";
import { dynamicController } from "@/src/routes/dynamic";
import { payRouter } from "@/src/routes/pay";
import { statsRouter } from "@/src/routes/stats";
import { webuiRouter } from "@/src/routes/webui";
import { connectDatabase } from "@/src/services/mongoService";
import { registerLogFileCreationListener } from "@/src/utils/logger";
import * as zlib from "zlib";
void registerLogFileCreationListener();
void connectDatabase();
const app = express();
app.use(function (req, _res, next) {
const buffer: Buffer[] = [];
req.on("data", function (chunk: Buffer) {
if (chunk !== undefined && chunk.length > 2 && chunk[0] == 0x1f && chunk[1] == 0x8b) {
buffer.push(Buffer.from(chunk));
}
});
req.on("end", function () {
zlib.gunzip(Buffer.concat(buffer), function (_err, dezipped) {
if (typeof dezipped != "undefined") {
req.body = dezipped.toString("utf-8");
}
next();
});
});
});
app.use(bodyParser.raw());
app.use(express.json({ limit: "4mb" }));
app.use(express.json());
app.use(bodyParser.text());
app.use(requestLogger);
//app.use(requestLogger);
app.use("/api", apiRouter);
//app.use("/test", testRouter);
app.use("/", cacheRouter);
app.use("/custom", customRouter);
app.use("/:id/dynamic", dynamicController);
app.use("/pay", payRouter);
app.post("/pay/steamPacks.php", steamPacksController);
app.use("/stats", statsRouter);
app.use("/", webuiRouter);
app.use(unknownEndpointHandler);
app.use(errorHandler);
//app.use(errorHandler)
export { app };

View File

@ -1,98 +0,0 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { addMods, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getRandomElement, getRandomInt, getRandomReward, IRngResult } from "@/src/services/rngService";
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express";
import { ExportUpgrades } from "warframe-public-export-plus";
export const activateRandomModController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const request = getJSONfromString<IActiveRandomModRequest>(String(req.body));
addMods(inventory, [
{
ItemType: request.ItemType,
ItemCount: -1
}
]);
const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType]);
const challenge = getRandomElement(ExportUpgrades[rivenType].availableChallenges!);
const fingerprintChallenge: IRandomModChallenge = {
Type: challenge.fullName,
Progress: 0,
Required: getRandomInt(challenge.countRange[0], challenge.countRange[1])
};
if (Math.random() < challenge.complicationChance) {
const complicationsAsRngResults: IRngResult[] = [];
for (const complication of challenge.complications) {
complicationsAsRngResults.push({
type: complication.fullName,
itemCount: 1,
probability: complication.weight
});
}
fingerprintChallenge.Complication = getRandomReward(complicationsAsRngResults)!.type;
logger.debug(
`riven rolled challenge ${fingerprintChallenge.Type} with complication ${fingerprintChallenge.Complication}`
);
const complication = challenge.complications.find(x => x.fullName == fingerprintChallenge.Complication)!;
fingerprintChallenge.Required *= complication.countMultiplier;
} else {
logger.debug(`riven rolled challenge ${fingerprintChallenge.Type}`);
}
const upgradeIndex =
inventory.Upgrades.push({
ItemType: rivenType,
UpgradeFingerprint: JSON.stringify({ challenge: fingerprintChallenge })
}) - 1;
await inventory.save();
res.json({
NewMod: inventory.Upgrades[upgradeIndex].toJSON()
});
};
interface IActiveRandomModRequest {
ItemType: string;
}
interface IRandomModChallenge {
Type: string;
Progress: number;
Required: number;
Complication?: string;
}
const rivenRawToRealWeighted: Record<string, string[]> = {
"/Lotus/Upgrades/Mods/Randomized/RawArchgunRandomMod": [
"/Lotus/Upgrades/Mods/Randomized/LotusArchgunRandomModRare"
],
"/Lotus/Upgrades/Mods/Randomized/RawMeleeRandomMod": [
"/Lotus/Upgrades/Mods/Randomized/PlayerMeleeWeaponRandomModRare"
],
"/Lotus/Upgrades/Mods/Randomized/RawModularMeleeRandomMod": [
"/Lotus/Upgrades/Mods/Randomized/LotusModularMeleeRandomModRare"
],
"/Lotus/Upgrades/Mods/Randomized/RawModularPistolRandomMod": [
"/Lotus/Upgrades/Mods/Randomized/LotusModularPistolRandomModRare"
],
"/Lotus/Upgrades/Mods/Randomized/RawPistolRandomMod": ["/Lotus/Upgrades/Mods/Randomized/LotusPistolRandomModRare"],
"/Lotus/Upgrades/Mods/Randomized/RawRifleRandomMod": ["/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare"],
"/Lotus/Upgrades/Mods/Randomized/RawShotgunRandomMod": [
"/Lotus/Upgrades/Mods/Randomized/LotusShotgunRandomModRare"
],
"/Lotus/Upgrades/Mods/Randomized/RawSentinelWeaponRandomMod": [
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
"/Lotus/Upgrades/Mods/Randomized/LotusShotgunRandomModRare",
"/Lotus/Upgrades/Mods/Randomized/LotusPistolRandomModRare",
"/Lotus/Upgrades/Mods/Randomized/PlayerMeleeWeaponRandomModRare"
]
};

View File

@ -4,9 +4,10 @@ import { IUpdateGlyphRequest } from "@/src/types/requestTypes";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
const addFriendImageController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const json = getJSONfromString<IUpdateGlyphRequest>(String(req.body));
const json = getJSONfromString(String(req.body)) as IUpdateGlyphRequest;
const inventory = await getInventory(accountId);
inventory.ActiveAvatarImageType = json.AvatarImageType;
await inventory.save();

View File

@ -1,76 +0,0 @@
import { RequestHandler } from "express";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory, addMods } from "@/src/services/inventoryService";
import { IOid } from "@/src/types/commonTypes";
export const arcaneCommonController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const json = getJSONfromString<IArcaneCommonRequest>(String(req.body));
const inventory = await getInventory(accountId);
const upgrade = inventory.Upgrades.id(json.arcane.ItemId.$oid);
if (json.newRank == -1) {
// Break down request?
if (!upgrade || !upgrade.UpgradeFingerprint) {
throw new Error(`Failed to find upgrade with OID ${json.arcane.ItemId.$oid}`);
}
// Remove Upgrade
inventory.Upgrades.pull({ _id: json.arcane.ItemId.$oid });
// Add RawUpgrades
const numRawUpgradesToGive = arcaneLevelCounts[(JSON.parse(upgrade.UpgradeFingerprint) as { lvl: number }).lvl];
addMods(inventory, [
{
ItemType: json.arcane.ItemType,
ItemCount: numRawUpgradesToGive
}
]);
res.json({ upgradeId: json.arcane.ItemId.$oid, numConsumed: numRawUpgradesToGive });
} else {
// Upgrade request?
let numConsumed = arcaneLevelCounts[json.newRank];
let upgradeId = json.arcane.ItemId.$oid;
if (upgrade) {
// Have an existing Upgrade item?
if (upgrade.UpgradeFingerprint) {
const existingLevel = (JSON.parse(upgrade.UpgradeFingerprint) as { lvl: number }).lvl;
numConsumed -= arcaneLevelCounts[existingLevel];
}
upgrade.UpgradeFingerprint = JSON.stringify({ lvl: json.newRank });
} else {
const newLength = inventory.Upgrades.push({
ItemType: json.arcane.ItemType,
UpgradeFingerprint: JSON.stringify({ lvl: json.newRank })
});
upgradeId = inventory.Upgrades[newLength - 1]._id.toString();
}
// Remove RawUpgrades
addMods(inventory, [
{
ItemType: json.arcane.ItemType,
ItemCount: numConsumed * -1
}
]);
res.json({ newLevel: json.newRank, numConsumed, upgradeId });
}
await inventory.save();
};
const arcaneLevelCounts = [0, 3, 6, 10, 15, 21];
interface IArcaneCommonRequest {
arcane: {
ItemType: string;
ItemId: IOid;
FromSKU: boolean;
UpgradeFingerprint: string;
PendingRerollFingerprint: string;
ItemCount: number;
LastAdded: IOid;
};
newRank: number;
}

View File

@ -1,51 +0,0 @@
import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import { colorToShard, combineColors, shardToColor } from "@/src/helpers/shardHelper";
export const archonFusionController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const request = JSON.parse(String(req.body)) as IArchonFusionRequest;
const inventory = await getInventory(accountId);
request.Consumed.forEach(x => {
x.ItemCount *= -1;
});
addMiscItems(inventory, request.Consumed);
const newArchons: IMiscItem[] = [];
switch (request.FusionType) {
case "AFT_ASCENT":
newArchons.push({
ItemType: request.Consumed[0].ItemType + "Mythic",
ItemCount: 1
});
break;
case "AFT_COALESCENT":
newArchons.push({
ItemType:
colorToShard[
combineColors(
shardToColor[request.Consumed[0].ItemType],
shardToColor[request.Consumed[1].ItemType]
)
],
ItemCount: 1
});
break;
default:
throw new Error(`unknown archon fusion type: ${request.FusionType}`);
}
addMiscItems(inventory, newArchons);
await inventory.save();
res.json({
NewArchons: newArchons
});
};
interface IArchonFusionRequest {
Consumed: IMiscItem[];
FusionType: string;
StatResultType: "SRT_NEW_STAT"; // ???
}

View File

@ -1,70 +1,22 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { upgradeMod } from "@/src/services/inventoryService";
import { IArtifactsRequest } from "@/src/types/requestTypes";
import { RequestHandler } from "express";
import { IInventoryClient, IUpgradeClient } from "@/src/types/inventoryTypes/inventoryTypes";
import { addMods, getInventory } from "@/src/services/inventoryService";
import { config } from "@/src/services/configService";
export const artifactsController: RequestHandler = async (req, res) => {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
const artifactsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const artifactsData = getJSONfromString<IArtifactsRequest>(String(req.body));
const { Upgrade, LevelDiff, Cost, FusionPointCost } = artifactsData;
const inventory = await getInventory(accountId);
const { Upgrades } = inventory;
const { ItemType, UpgradeFingerprint, ItemId } = Upgrade;
const safeUpgradeFingerprint = UpgradeFingerprint || '{"lvl":0}';
const parsedUpgradeFingerprint = JSON.parse(safeUpgradeFingerprint) as { lvl: number };
parsedUpgradeFingerprint.lvl += LevelDiff;
const stringifiedUpgradeFingerprint = JSON.stringify(parsedUpgradeFingerprint);
let itemIndex = Upgrades.findIndex(upgrade => upgrade._id.equals(ItemId.$oid));
if (itemIndex !== -1) {
Upgrades[itemIndex].UpgradeFingerprint = stringifiedUpgradeFingerprint;
inventory.markModified(`Upgrades.${itemIndex}.UpgradeFingerprint`);
} else {
itemIndex =
Upgrades.push({
UpgradeFingerprint: stringifiedUpgradeFingerprint,
ItemType
}) - 1;
addMods(inventory, [{ ItemType, ItemCount: -1 }]);
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call
const artifactsData = getJSONfromString(req.body.toString()) as IArtifactsRequest;
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const upgradeModId = await upgradeMod(artifactsData, accountId);
res.send(upgradeModId);
} catch (err) {
console.error("Error parsing JSON data:", err);
}
if (!config.infiniteCredits) {
inventory.RegularCredits -= Cost;
}
if (!config.infiniteEndo) {
inventory.FusionPoints -= FusionPointCost;
}
if (artifactsData.LegendaryFusion) {
addMods(inventory, [
{
ItemType: "/Lotus/Upgrades/Mods/Fusers/LegendaryModFuser",
ItemCount: -1
}
]);
}
const changedInventory = await inventory.save();
const itemId = changedInventory.toJSON<IInventoryClient>().Upgrades[itemIndex].ItemId.$oid;
if (!itemId) {
throw new Error("Item Id not found in upgradeMod");
}
res.send(itemId);
};
interface IArtifactsRequest {
Upgrade: IUpgradeClient;
LevelDiff: number;
Cost: number;
FusionPointCost: number;
LegendaryFusion?: boolean;
}
export { artifactsController };

View File

@ -1,90 +0,0 @@
import { RequestHandler } from "express";
import { getDojoClient, getGuildForRequest } from "@/src/services/guildService";
import { logger } from "@/src/utils/logger";
import { IDojoComponentDatabase } from "@/src/types/guildTypes";
import { Types } from "mongoose";
export const changeDojoRootController: RequestHandler = async (req, res) => {
const guild = await getGuildForRequest(req);
// At this point, we know that a member of the guild is making this request. Assuming they are allowed to change the root.
const idToNode: Record<string, INode> = {};
guild.DojoComponents!.forEach(x => {
idToNode[x._id.toString()] = {
component: x,
parent: undefined,
children: []
};
});
let oldRoot: INode | undefined;
guild.DojoComponents!.forEach(x => {
const node = idToNode[x._id.toString()];
if (x.pi) {
idToNode[x.pi.toString()].children.push(node);
node.parent = idToNode[x.pi.toString()];
} else {
oldRoot = node;
}
});
logger.debug("Old tree:\n" + treeToString(oldRoot!));
const newRoot = idToNode[req.query.newRoot as string];
recursivelyTurnParentsIntoChildren(newRoot);
newRoot.component.pi = undefined;
newRoot.component.op = undefined;
newRoot.component.pp = undefined;
newRoot.parent = undefined;
// Don't even ask me why this is needed because I don't know either
const stack: INode[] = [newRoot];
let i = 0;
const idMap: Record<string, Types.ObjectId> = {};
while (stack.length != 0) {
const top = stack.shift()!;
idMap[top.component._id.toString()] = new Types.ObjectId(
(++i).toString(16).padStart(8, "0") + top.component._id.toString().substr(8)
);
top.children.forEach(x => stack.push(x));
}
guild.DojoComponents!.forEach(x => {
x._id = idMap[x._id.toString()];
if (x.pi) {
x.pi = idMap[x.pi.toString()];
}
});
logger.debug("New tree:\n" + treeToString(newRoot));
await guild.save();
res.json(getDojoClient(guild, 0));
};
interface INode {
component: IDojoComponentDatabase;
parent: INode | undefined;
children: INode[];
}
const treeToString = (root: INode, depth: number = 0): string => {
let str = " ".repeat(depth * 4) + root.component.pf + " (" + root.component._id.toString() + ")\n";
root.children.forEach(x => {
str += treeToString(x, depth + 1);
});
return str;
};
const recursivelyTurnParentsIntoChildren = (node: INode): void => {
if (node.parent!.parent) {
recursivelyTurnParentsIntoChildren(node.parent!);
}
node.parent!.component.pi = node.component._id;
node.parent!.component.op = node.component.pp;
node.parent!.component.pp = node.component.op;
node.parent!.parent = node;
node.parent!.children.splice(node.parent!.children.indexOf(node), 1);
node.children.push(node.parent!);
};

View File

@ -7,32 +7,30 @@ import { getRecipe } from "@/src/services/itemDataService";
import { IOid } from "@/src/types/commonTypes";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getAccountIdForRequest } from "@/src/services/loginService";
import {
getInventory,
updateCurrency,
addItem,
addMiscItems,
addRecipes,
updateCurrencyByAccountId
} from "@/src/services/inventoryService";
import { getInventory, updateCurrency, addItem, addMiscItems, addRecipes } from "@/src/services/inventoryService";
export interface IClaimCompletedRecipeRequest {
RecipeIds: IOid[];
}
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const claimCompletedRecipeController: RequestHandler = async (req, res) => {
const claimCompletedRecipeRequest = getJSONfromString<IClaimCompletedRecipeRequest>(String(req.body));
const claimCompletedRecipeRequest = getJSONfromString(String(req.body)) as IClaimCompletedRecipeRequest;
const accountId = await getAccountIdForRequest(req);
if (!accountId) throw new Error("no account id");
const inventory = await getInventory(accountId);
const pendingRecipe = inventory.PendingRecipes.id(claimCompletedRecipeRequest.RecipeIds[0].$oid);
const pendingRecipe = inventory.PendingRecipes.find(
recipe => recipe._id?.toString() === claimCompletedRecipeRequest.RecipeIds[0].$id
);
if (!pendingRecipe) {
throw new Error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$oid}`);
logger.error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$id}`);
throw new Error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$id}`);
}
//check recipe is indeed ready to be completed
// if (pendingRecipe.CompletionDate > new Date()) {
// logger.error(`recipe ${pendingRecipe._id} is not ready to be completed`);
// throw new Error(`recipe ${pendingRecipe._id} is not ready to be completed`);
// }
@ -41,12 +39,14 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
const recipe = getRecipe(pendingRecipe.ItemType);
if (!recipe) {
throw new Error(`no completed item found for recipe ${pendingRecipe._id.toString()}`);
logger.error(`no completed item found for recipe ${pendingRecipe._id}`);
throw new Error(`no completed item found for recipe ${pendingRecipe._id}`);
}
if (req.query.cancel) {
const currencyChanges = await updateCurrency(recipe.buildPrice * -1, false, accountId);
const inventory = await getInventory(accountId);
const currencyChanges = updateCurrency(inventory, recipe.buildPrice * -1, false);
addMiscItems(inventory, recipe.ingredients);
await inventory.save();
@ -57,30 +57,6 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
});
} else {
logger.debug("Claiming Recipe", { recipe, pendingRecipe });
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
const inventory = await getInventory(accountId);
inventory.PendingSpectreLoadouts ??= [];
inventory.SpectreLoadouts ??= [];
const pendingLoadoutIndex = inventory.PendingSpectreLoadouts.findIndex(
x => x.ItemType == recipe.resultType
);
if (pendingLoadoutIndex != -1) {
const loadoutIndex = inventory.SpectreLoadouts.findIndex(x => x.ItemType == recipe.resultType);
if (loadoutIndex != -1) {
inventory.SpectreLoadouts.splice(loadoutIndex, 1);
}
logger.debug(
"moving spectre loadout from pending to active",
inventory.toJSON().PendingSpectreLoadouts![pendingLoadoutIndex]
);
inventory.SpectreLoadouts.push(inventory.PendingSpectreLoadouts[pendingLoadoutIndex]);
inventory.PendingSpectreLoadouts.splice(pendingLoadoutIndex, 1);
await inventory.save();
}
}
let InventoryChanges = {};
if (recipe.consumeOnUse) {
const recipeChanges = [
@ -99,15 +75,14 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
if (req.query.rush) {
InventoryChanges = {
...InventoryChanges,
...(await updateCurrencyByAccountId(recipe.skipBuildTimePrice, true, accountId))
...(await updateCurrency(recipe.skipBuildTimePrice, true, accountId))
};
}
const inventory = await getInventory(accountId);
InventoryChanges = {
...InventoryChanges,
...(await addItem(inventory, recipe.resultType, recipe.num)).InventoryChanges
};
await inventory.save();
res.json({ InventoryChanges });
res.json({
InventoryChanges: {
...InventoryChanges,
...(await addItem(accountId, recipe.resultType, recipe.num)).InventoryChanges
}
});
}
};

View File

@ -1,23 +0,0 @@
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
export const clearDialogueHistoryController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const request = JSON.parse(String(req.body)) as IClearDialogueRequest;
if (inventory.DialogueHistory && inventory.DialogueHistory.Dialogues) {
for (const dialogueName of request.Dialogues) {
const index = inventory.DialogueHistory.Dialogues.findIndex(x => x.DialogueName == dialogueName);
if (index != -1) {
inventory.DialogueHistory.Dialogues.splice(index, 1);
}
}
}
await inventory.save();
res.end();
};
interface IClearDialogueRequest {
Dialogues: string[];
}

View File

@ -3,10 +3,12 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
import { Guild } from "@/src/models/guildModel";
import { ICreateGuildRequest } from "@/src/types/guildTypes";
export const createGuildController: RequestHandler = async (req, res) => {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
const createGuildController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const payload = getJSONfromString<ICreateGuildRequest>(String(req.body));
const payload = getJSONfromString(String(req.body)) as ICreateGuildRequest;
// Create guild on database
const guild = new Guild({
@ -21,6 +23,7 @@ export const createGuildController: RequestHandler = async (req, res) => {
inventory.GuildId = guild._id;
// Give clan key (TODO: This should only be a blueprint)
inventory.LevelKeys ??= [];
inventory.LevelKeys.push({
ItemType: "/Lotus/Types/Keys/DojoKey",
ItemCount: 1
@ -32,6 +35,4 @@ export const createGuildController: RequestHandler = async (req, res) => {
res.json(guild);
};
interface ICreateGuildRequest {
guildName: string;
}
export { createGuildController };

View File

@ -1,27 +0,0 @@
import { RequestHandler } from "express";
import { config } from "@/src/services/configService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService";
export const creditsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "RegularCredits TradesRemaining PremiumCreditsFree PremiumCredits");
const response = {
RegularCredits: inventory.RegularCredits,
TradesRemaining: inventory.TradesRemaining,
PremiumCreditsFree: inventory.PremiumCreditsFree,
PremiumCredits: inventory.PremiumCredits
};
if (config.infiniteCredits) {
response.RegularCredits = 999999999;
}
if (config.infinitePlatinum) {
response.PremiumCreditsFree = 999999999;
response.PremiumCredits = 999999999;
}
res.json(response);
};

View File

@ -1,60 +0,0 @@
import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { TEndlessXpCategory } from "@/src/types/inventoryTypes/inventoryTypes";
export const endlessXpController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const payload = getJSONfromString<IEndlessXpRequest>(String(req.body));
inventory.EndlessXP ??= [];
const entry = inventory.EndlessXP.find(x => x.Category == payload.Category);
if (entry) {
entry.Choices = payload.Choices;
} else {
inventory.EndlessXP.push({
Category: payload.Category,
Choices: payload.Choices
});
}
await inventory.save();
res.json({
NewProgress: {
Category: payload.Category,
Earn: 0,
Claim: 0,
BonusAvailable: {
$date: {
$numberLong: "9999999999999"
}
},
Expiry: {
$date: {
$numberLong: "9999999999999"
}
},
Choices: payload.Choices,
PendingRewards: [
{
RequiredTotalXp: 190,
Rewards: [
{
StoreItem: "/Lotus/StoreItems/Upgrades/Mods/Aura/PlayerHealthAuraMod",
ItemCount: 1
}
]
}
// ...
]
}
});
};
interface IEndlessXpRequest {
Mode: string; // "r"
Category: TEndlessXpCategory;
Choices: string[];
}

View File

@ -1,48 +1,32 @@
import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
import { getInventory } from "@/src/services/inventoryService";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getRecipe, WeaponTypeInternal } from "@/src/services/itemDataService";
import { WeaponTypeInternal } from "@/src/services/itemDataService";
import { EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const evolveWeaponController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const payload = getJSONfromString<IEvolveWeaponRequest>(String(req.body));
const payload = getJSONfromString(String(req.body)) as IEvolveWeaponRequest;
console.assert(payload.Action == "EWA_INSTALL");
const recipe = getRecipe(payload.Recipe)!;
if (payload.Action == "EWA_INSTALL") {
addMiscItems(
inventory,
recipe.ingredients.map(x => ({ ItemType: x.ItemType, ItemCount: x.ItemCount * -1 }))
);
// TODO: We should remove the Genesis item & its resources, but currently we don't know these "recipes".
const item = inventory[payload.Category].find(item => item._id.toString() == (req.query.ItemId as string))!;
item.Features ??= 0;
item.Features |= EquipmentFeatures.INCARNON_GENESIS;
const item = inventory[payload.Category].find(item => item._id.toString() == (req.query.ItemId as string))!;
item.Features ??= 0;
item.Features |= EquipmentFeatures.INCARNON_GENESIS;
item.SkillTree = "0";
item.SkillTree = "0";
inventory.EvolutionProgress ??= [];
if (!inventory.EvolutionProgress.find(entry => entry.ItemType == payload.EvoType)) {
inventory.EvolutionProgress.push({
Progress: 0,
Rank: 1,
ItemType: payload.EvoType
});
}
} else if (payload.Action == "EWA_UNINSTALL") {
addMiscItems(inventory, [
{
ItemType: recipe.resultType,
ItemCount: 1
}
]);
const item = inventory[payload.Category].find(item => item._id.toString() == (req.query.ItemId as string))!;
item.Features! &= ~EquipmentFeatures.INCARNON_GENESIS;
} else {
throw new Error(`unexpected evolve weapon action: ${payload.Action}`);
inventory.EvolutionProgress ??= [];
if (!inventory.EvolutionProgress.find(entry => entry.ItemType == payload.EvoType)) {
inventory.EvolutionProgress.push({
Progress: 0,
Rank: 1,
ItemType: payload.EvoType
});
}
await inventory.save();
@ -50,7 +34,7 @@ export const evolveWeaponController: RequestHandler = async (req, res) => {
};
interface IEvolveWeaponRequest {
Action: string;
Action: "EWA_INSTALL";
Category: WeaponTypeInternal;
Recipe: string; // e.g. "/Lotus/Types/Items/MiscItems/IncarnonAdapters/UnlockerBlueprints/DespairIncarnonBlueprint"
UninstallRecipe: "";

View File

@ -1,28 +1,31 @@
import { RequestHandler } from "express";
import { getSession } from "@/src/managers/sessionManager";
import { logger } from "@/src/utils/logger";
import { IFindSessionRequest } from "@/src/types/session";
export const findSessionsController: RequestHandler = (_req, res) => {
const req = JSON.parse(String(_req.body)) as IFindSessionRequest;
logger.debug("FindSession Request ", req);
//TODO: cleanup
const findSessionsController: RequestHandler = (_req, res) => {
const reqBody = JSON.parse(String(_req.body));
logger.debug("FindSession Request ", { reqBody });
const req = JSON.parse(String(_req.body));
if (req.id != undefined) {
logger.debug("Found ID");
const session = getSession(req.id);
const session = getSession(req.id as string);
if (session.length) res.json({ queryId: req.queryId, Sessions: session });
if (session) res.json({ queryId: req.queryId, Sessions: session });
else res.json({});
} else if (req.originalSessionId != undefined) {
logger.debug("Found OriginalSessionID");
const session = getSession(req.originalSessionId);
if (session.length) res.json({ queryId: req.queryId, Sessions: session });
const session = getSession(req.originalSessionId as string);
if (session) res.json({ queryId: req.queryId, Sessions: session });
else res.json({});
} else {
logger.debug("Found SessionRequest");
const session = getSession(req);
if (session.length) res.json({ queryId: req.queryId, Sessions: session });
const session = getSession(String(_req.body));
if (session) res.json({ queryId: req.queryId, Sessions: session });
else res.json({});
}
};
export { findSessionsController };

View File

@ -1,65 +0,0 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper";
import { addMiscItems, getInventory, getStandingLimit, updateStandingLimit } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import { RequestHandler } from "express";
import { ExportResources, ExportSyndicates } from "warframe-public-export-plus";
export const fishmongerController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const body = getJSONfromString<IFishmongerRequest>(String(req.body));
const miscItemChanges: IMiscItem[] = [];
let syndicateTag: string | undefined;
let gainedStanding = 0;
for (const fish of body.Fish) {
const fishData = ExportResources[fish.ItemType];
if (req.query.dissect == "1") {
for (const part of fishData.dissectionParts!) {
const partItem = miscItemChanges.find(x => x.ItemType == part.ItemType);
if (partItem) {
partItem.ItemCount += part.ItemCount * fish.ItemCount;
} else {
miscItemChanges.push({ ItemType: part.ItemType, ItemCount: part.ItemCount * fish.ItemCount });
}
}
} else {
syndicateTag = fishData.syndicateTag!;
gainedStanding += fishData.standingBonus! * fish.ItemCount;
}
miscItemChanges.push({ ItemType: fish.ItemType, ItemCount: fish.ItemCount * -1 });
}
addMiscItems(inventory, miscItemChanges);
if (gainedStanding && syndicateTag) {
let syndicate = inventory.Affiliations.find(x => x.Tag == syndicateTag);
if (!syndicate) {
syndicate = inventory.Affiliations[inventory.Affiliations.push({ Tag: syndicateTag, Standing: 0 }) - 1];
}
const syndicateMeta = ExportSyndicates[syndicateTag];
const max = getMaxStanding(syndicateMeta, syndicate.Title ?? 0);
if (syndicate.Standing + gainedStanding > max) {
gainedStanding = max - syndicate.Standing;
}
if (gainedStanding > getStandingLimit(inventory, syndicateMeta.dailyLimitBin)) {
gainedStanding = getStandingLimit(inventory, syndicateMeta.dailyLimitBin);
}
syndicate.Standing += gainedStanding;
updateStandingLimit(inventory, syndicateMeta.dailyLimitBin, gainedStanding);
}
await inventory.save();
res.json({
InventoryChanges: {
MiscItems: miscItemChanges
},
SyndicateTag: syndicateTag,
StandingChange: gainedStanding
});
};
interface IFishmongerRequest {
Fish: IMiscItem[];
}

View File

@ -1,41 +1,19 @@
import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory, addMiscItems, addEquipment } from "@/src/services/inventoryService";
import { IMiscItem, TFocusPolarity, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import { IMiscItem, TFocusPolarity } from "@/src/types/inventoryTypes/inventoryTypes";
import { logger } from "@/src/utils/logger";
import { ExportFocusUpgrades } from "warframe-public-export-plus";
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const focusController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
switch (req.query.op) {
default:
logger.error("Unhandled focus op type: " + String(req.query.op));
logger.debug(String(req.body));
logger.error("Unhandled focus op type: " + req.query.op);
logger.debug(req.body.toString());
res.end();
break;
case FocusOperation.InstallLens: {
const request = JSON.parse(String(req.body)) as ILensInstallRequest;
const inventory = await getInventory(accountId);
for (const item of inventory[request.Category]) {
if (item._id.toString() == request.WeaponId) {
item.FocusLens = request.LensType;
addMiscItems(inventory, [
{
ItemType: request.LensType,
ItemCount: -1
} satisfies IMiscItem
]);
break;
}
}
await inventory.save();
res.json({
weaponId: request.WeaponId,
lensType: request.LensType
});
break;
}
case FocusOperation.UnlockWay: {
const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType;
const focusPolarity = focusTypeToPolarity(focusType);
@ -70,7 +48,7 @@ export const focusController: RequestHandler = async (req, res) => {
cost += ExportFocusUpgrades[focusType].baseFocusPointCost;
inventory.FocusUpgrades.push({ ItemType: focusType, Level: 0 });
}
inventory.FocusXP![focusPolarity] -= cost;
inventory.FocusXP[focusPolarity] -= cost;
await inventory.save();
res.json({
FocusTypes: request.FocusTypes,
@ -88,7 +66,7 @@ export const focusController: RequestHandler = async (req, res) => {
const focusUpgradeDb = inventory.FocusUpgrades.find(entry => entry.ItemType == focusUpgrade.ItemType)!;
focusUpgradeDb.Level = focusUpgrade.Level;
}
inventory.FocusXP![focusPolarity] -= cost;
inventory.FocusXP[focusPolarity] -= cost;
await inventory.save();
res.json({
FocusInfos: request.FocusInfos,
@ -103,17 +81,15 @@ export const focusController: RequestHandler = async (req, res) => {
"/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);
await inventory.save();
res.json((inventoryChanges.OperatorAmps as IEquipmentClient[])[0]);
const result = await addEquipment("OperatorAmps", request.StartingWeaponType, accountId, parts);
res.json(result);
break;
}
case FocusOperation.UnbindUpgrade: {
const request = JSON.parse(String(req.body)) as IUnbindUpgradeRequest;
const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]);
const inventory = await getInventory(accountId);
inventory.FocusXP![focusPolarity] -= 750_000 * request.FocusTypes.length;
inventory.FocusXP[focusPolarity] -= 750_000 * request.FocusTypes.length;
addMiscItems(inventory, [
{
ItemType: "/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantItem",
@ -168,7 +144,6 @@ export const focusController: RequestHandler = async (req, res) => {
};
enum FocusOperation {
InstallLens = "1",
UnlockWay = "2",
UnlockUpgrade = "3",
LevelUpUpgrade = "4",
@ -211,12 +186,6 @@ interface ISentTrainingAmplifierRequest {
StartingWeaponType: string;
}
interface ILensInstallRequest {
LensType: string;
Category: TEquipmentKey;
WeaponId: string;
}
// Works for ways & upgrades
const focusTypeToPolarity = (type: string): TFocusPolarity => {
return ("AP_" + type.substr(1).split("/")[3].toUpperCase()) as TFocusPolarity;

View File

@ -1,49 +0,0 @@
import { RequestHandler } from "express";
import { ExportResources } from "warframe-public-export-plus";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { addFusionTreasures, addMiscItems, getInventory } from "@/src/services/inventoryService";
import { IFusionTreasure, IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
interface IFusionTreasureRequest {
oldTreasureName: string;
newTreasureName: string;
}
const parseFusionTreasure = (name: string, count: number): IFusionTreasure => {
const arr = name.split("_");
return {
ItemType: arr[0],
Sockets: parseInt(arr[1], 16),
ItemCount: count
};
};
export const fusionTreasuresController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const request = JSON.parse(String(req.body)) as IFusionTreasureRequest;
const oldTreasure = parseFusionTreasure(request.oldTreasureName, -1);
const newTreasure = parseFusionTreasure(request.newTreasureName, 1);
// Swap treasures
addFusionTreasures(inventory, [oldTreasure]);
addFusionTreasures(inventory, [newTreasure]);
// Remove consumed stars
const miscItemChanges: IMiscItem[] = [];
const filledSockets = newTreasure.Sockets & ~oldTreasure.Sockets;
for (let i = 0; filledSockets >> i; ++i) {
if ((filledSockets >> i) & 1) {
//console.log("Socket", i, "has been filled with", ExportResources[oldTreasure.ItemType].sockets![i]);
miscItemChanges.push({
ItemType: ExportResources[oldTreasure.ItemType].sockets![i],
ItemCount: -1
});
}
}
addMiscItems(inventory, miscItemChanges);
await inventory.save();
res.end();
};

View File

@ -7,11 +7,11 @@ import { IGenericUpdate } from "@/src/types/genericUpdate";
// This endpoint used to be /api/genericUpdate.php, but sometime around the Jade Shadows update, it was changed to /api/updateNodeIntros.php.
// SpaceNinjaServer supports both endpoints right now.
// eslint-disable-next-line @typescript-eslint/no-misused-promises
const genericUpdateController: RequestHandler = async (request, response) => {
const accountId = await getAccountIdForRequest(request);
const update = getJSONfromString<IGenericUpdate>(String(request.body));
await updateGeneric(update, accountId);
response.json(update);
const update = getJSONfromString(String(request.body)) as IGenericUpdate;
response.json(await updateGeneric(update, accountId));
};
export { genericUpdateController };

View File

@ -0,0 +1,29 @@
import { RequestHandler } from "express";
import { config } from "@/src/services/configService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const getCreditsController: RequestHandler = async (req, res) => {
let accountId;
try {
accountId = await getAccountIdForRequest(req);
} catch (e) {
res.status(400).send("Log-in expired");
return;
}
if (config.infiniteResources) {
res.json({
RegularCredits: 999999999,
PremiumCredits: 999999999
});
return;
}
const inventory = await getInventory(accountId);
res.json({
RegularCredits: inventory.RegularCredits,
PremiumCredits: inventory.PremiumCredits
});
};

View File

@ -1,6 +1,6 @@
import { Request, Response } from "express";
const getFriendsController = (_request: Request, response: Response): void => {
const getFriendsController = (_request: Request, response: Response) => {
response.writeHead(200, {
//Connection: "keep-alive",
//"Content-Encoding": "gzip",

View File

@ -1,72 +1,6 @@
import { RequestHandler } from "express";
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
import { Guild } from "@/src/models/guildModel";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { toOid } from "@/src/helpers/inventoryHelpers";
const getGuildController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await Inventory.findOne({ accountOwnerId: accountId });
if (!inventory) {
res.status(400).json({ error: "inventory was undefined" });
return;
}
if (inventory.GuildId) {
const guild = await Guild.findOne({ _id: inventory.GuildId });
if (guild) {
res.json({
_id: toOid(guild._id),
Name: guild.Name,
Members: [
{
_id: { $oid: req.query.accountId },
Rank: 0,
Status: 0
}
],
Ranks: [
{
Name: "/Lotus/Language/Game/Rank_Creator",
Permissions: 16351
},
{
Name: "/Lotus/Language/Game/Rank_Warlord",
Permissions: 14303
},
{
Name: "/Lotus/Language/Game/Rank_General",
Permissions: 4318
},
{
Name: "/Lotus/Language/Game/Rank_Officer",
Permissions: 4314
},
{
Name: "/Lotus/Language/Game/Rank_Leader",
Permissions: 4106
},
{
Name: "/Lotus/Language/Game/Rank_Sage",
Permissions: 4304
},
{
Name: "/Lotus/Language/Game/Rank_Soldier",
Permissions: 4098
},
{
Name: "/Lotus/Language/Game/Rank_Initiate",
Permissions: 4096
},
{
Name: "/Lotus/Language/Game/Rank_Utility",
Permissions: 4096
}
],
Tier: 1
});
return;
}
}
const getGuildController: RequestHandler = (_, res) => {
res.json({});
};

View File

@ -1,8 +1,10 @@
import { RequestHandler } from "express";
import { Types } from "mongoose";
import { Guild } from "@/src/models/guildModel";
import { getDojoClient } from "@/src/services/guildService";
import { IDojoClient, IDojoComponentClient } from "@/src/types/guildTypes";
import { toOid, toMongoDate } from "@/src/helpers/inventoryHelpers";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const getGuildDojoController: RequestHandler = async (req, res) => {
const guildId = req.query.guildId as string;
@ -25,5 +27,34 @@ export const getGuildDojoController: RequestHandler = async (req, res) => {
await guild.save();
}
res.json(getDojoClient(guild, 0));
const dojo: IDojoClient = {
_id: { $id: guildId },
Name: guild.Name,
Tier: 1,
FixedContributions: true,
DojoRevision: 1,
RevisionTime: Math.round(Date.now() / 1000),
Energy: 5,
Capacity: 100,
DojoRequestStatus: 0,
DojoComponents: []
};
guild.DojoComponents.forEach(dojoComponent => {
const clientComponent: IDojoComponentClient = {
id: toOid(dojoComponent._id),
pf: dojoComponent.pf,
ppf: dojoComponent.ppf,
DecoCapacity: 600
};
if (dojoComponent.pi) {
clientComponent.pi = toOid(dojoComponent.pi);
clientComponent.op = dojoComponent.op!;
clientComponent.pp = dojoComponent.pp!;
}
if (dojoComponent.CompletionTime) {
clientComponent.CompletionTime = toMongoDate(dojoComponent.CompletionTime);
}
dojo.DojoComponents.push(clientComponent);
});
res.json(dojo);
};

View File

@ -4,32 +4,31 @@ import allShipFeatures from "@/static/fixed_responses/allShipFeatures.json";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getPersonalRooms } from "@/src/services/personalRoomsService";
import { getShip } from "@/src/services/shipService";
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
import { logger } from "@/src/utils/logger";
import { toOid } from "@/src/helpers/inventoryHelpers";
import { IGetShipResponse } from "@/src/types/shipTypes";
import { IPersonalRooms } from "@/src/types/personalRoomsTypes";
import { getLoadout } from "@/src/services/loadoutService";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const getShipController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const personalRoomsDb = await getPersonalRooms(accountId);
const personalRooms = personalRoomsDb.toJSON<IPersonalRooms>();
const personalRooms = await getPersonalRooms(accountId);
const loadout = await getLoadout(accountId);
const ship = await getShip(personalRoomsDb.activeShipId, "ShipAttachments SkinFlavourItem");
const ship = await getShip(personalRooms.activeShipId, "ShipInteriorColors ShipAttachments SkinFlavourItem");
const getShipResponse: IGetShipResponse = {
ShipOwnerId: accountId,
LoadOutInventory: { LoadOutPresets: loadout.toJSON() },
Ship: {
...personalRooms.Ship,
ShipId: toOid(personalRoomsDb.activeShipId),
...personalRooms.toJSON().Ship,
ShipId: toOid(personalRooms.activeShipId),
ShipInterior: {
Colors: personalRooms.ShipInteriorColors,
Colors: ship.ShipInteriorColors,
ShipAttachments: ship.ShipAttachments,
SkinFlavourItem: ship.SkinFlavourItem
}
},
Apartment: personalRooms.Apartment,
TailorShop: personalRooms.TailorShop
Apartment: personalRooms.Apartment
};
if (config.unlockAllShipFeatures) {
@ -38,3 +37,14 @@ export const getShipController: RequestHandler = async (req, res) => {
res.json(getShipResponse);
};
export const getLoadout = async (accountId: string) => {
const loadout = await Loadout.findOne({ loadoutOwnerId: accountId });
if (!loadout) {
logger.error(`loadout not found for account ${accountId}`);
throw new Error("loadout not found");
}
return loadout;
};

View File

@ -1,14 +1,23 @@
import { RequestHandler } from "express";
import { getVendorManifestByTypeName } from "@/src/services/serversideVendorsService";
import ArchimedeanVendorManifest from "@/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json";
import MaskSalesmanManifest from "@/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json";
import ZarimanCommisionsManifestArchimedean from "@/static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json";
export const getVendorInfoController: RequestHandler = (req, res) => {
if (typeof req.query.vendor == "string") {
const manifest = getVendorManifestByTypeName(req.query.vendor);
if (!manifest) {
switch (req.query.vendor as string) {
case "/Lotus/Types/Game/VendorManifests/Zariman/ArchimedeanVendorManifest":
res.json(ArchimedeanVendorManifest);
break;
case "/Lotus/Types/Game/VendorManifests/Ostron/MaskSalesmanManifest":
res.json(MaskSalesmanManifest);
break;
case "/Lotus/Types/Game/VendorManifests/Zariman/ZarimanCommisionsManifestArchimedean":
res.json(ZarimanCommisionsManifestArchimedean);
break;
default:
throw new Error(`Unknown vendor: ${req.query.vendor}`);
}
res.json(manifest);
} else {
res.status(400).end();
}
};

View File

@ -1,98 +0,0 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { getRandomWeightedReward2 } from "@/src/services/rngService";
import { ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express";
import { ExportRelics, ExportRewards, TRarity } from "warframe-public-export-plus";
export const getVoidProjectionRewardsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const data = getJSONfromString<IVoidProjectionRewardRequest>(String(req.body));
const response: IVoidProjectionRewardResponse = {
CurrentWave: data.CurrentWave,
ParticipantInfo: data.ParticipantInfo,
DifficultyTier: data.DifficultyTier
};
if (data.ParticipantInfo.QualifiesForReward) {
const relic = ExportRelics[data.ParticipantInfo.VoidProjection];
const weights = refinementToWeights[relic.quality];
logger.debug(`opening a relic of quality ${relic.quality}; rarity weights are`, weights);
const reward = getRandomWeightedReward2(
ExportRewards[relic.rewardManifest][0] as { type: string; itemCount: number; rarity: TRarity }[], // rarity is nullable in PE+ typings, but always present for relics
weights
)!;
logger.debug(`relic rolled`, reward);
response.ParticipantInfo.Reward = reward.type;
const inventory = await getInventory(accountId);
// Remove relic
addMiscItems(inventory, [
{
ItemType: data.ParticipantInfo.VoidProjection,
ItemCount: -1
}
]);
// Give reward
await handleStoreItemAcquisition(reward.type, inventory, reward.itemCount);
await inventory.save();
}
res.json(response);
};
const refinementToWeights = {
VPQ_BRONZE: {
COMMON: 0.76,
UNCOMMON: 0.22,
RARE: 0.02,
LEGENDARY: 0
},
VPQ_SILVER: {
COMMON: 0.7,
UNCOMMON: 0.26,
RARE: 0.04,
LEGENDARY: 0
},
VPQ_GOLD: {
COMMON: 0.6,
UNCOMMON: 0.34,
RARE: 0.06,
LEGENDARY: 0
},
VPQ_PLATINUM: {
COMMON: 0.5,
UNCOMMON: 0.4,
RARE: 0.1,
LEGENDARY: 0
}
};
interface IVoidProjectionRewardRequest {
CurrentWave: number;
ParticipantInfo: IParticipantInfo;
VoidTier: string;
DifficultyTier: number;
VoidProjectionRemovalHash: string;
}
interface IVoidProjectionRewardResponse {
CurrentWave: number;
ParticipantInfo: IParticipantInfo;
DifficultyTier: number;
}
interface IParticipantInfo {
AccountId: string;
Name: string;
ChosenRewardOwner: string;
MissionHash: string;
VoidProjection: string;
Reward: string;
QualifiesForReward: boolean;
HaveRewardResponse: boolean;
RewardsMultiplier: number;
RewardProjection: string;
HardModeReward: ITypeCount;
}

View File

@ -24,19 +24,23 @@ interface IGildWeaponRequest {
// In export there no recipes for gild action, so reputation and ressources only consumed visually
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const gildWeaponController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const data = getJSONfromString<IGildWeaponRequest>(String(req.body));
const data: IGildWeaponRequest = getJSONfromString(String(req.body));
data.ItemId = String(req.query.ItemId);
if (!modularWeaponCategory.includes(req.query.Category as WeaponTypeInternal | "Hoverboards")) {
throw new Error(`Unknown modular weapon Category: ${String(req.query.Category)}`);
throw new Error(`Unknown modular weapon Category: ${req.query.Category}`);
}
data.Category = req.query.Category as WeaponTypeInternal | "Hoverboards";
const inventory = await getInventory(accountId);
if (!inventory[data.Category]) {
throw new Error(`Category ${req.query.Category} not found in inventory`);
}
const weaponIndex = inventory[data.Category].findIndex(x => String(x._id) === data.ItemId);
if (weaponIndex === -1) {
throw new Error(`Weapon with ${data.ItemId} not found in category ${String(req.query.Category)}`);
throw new Error(`Weapon with ${data.ItemId} not found in category ${req.query.Category}`);
}
const weapon = inventory[data.Category][weaponIndex];

View File

@ -1,38 +0,0 @@
import { RequestHandler } from "express";
import { isEmptyObject, parseString } from "@/src/helpers/general";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { addKeyChainItems, getInventory } from "@/src/services/inventoryService";
import { IGroup } from "@/src/types/loginTypes";
import { updateQuestStage } from "@/src/services/questService";
export const giveKeyChainTriggeredItemsController: RequestHandler = async (req, res) => {
const accountId = parseString(req.query.accountId);
const keyChainInfo = getJSONfromString<IKeyChainRequest>((req.body as string).toString());
const inventory = await getInventory(accountId);
const inventoryChanges = await addKeyChainItems(inventory, keyChainInfo);
if (isEmptyObject(inventoryChanges)) {
throw new Error("inventory changes was empty after getting keychain items: should not happen");
}
// items were added: update quest stage's i (item was given)
updateQuestStage(inventory, keyChainInfo, { i: true });
await inventory.save();
res.send(inventoryChanges);
//TODO: Check whether Wishlist is used to track items which should exist uniquely in the inventory
/*
some items are added or removed (not sure) to the wishlist, in that case a
WishlistChanges: ["/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem"],
is added to the response, need to determine for which items this is the case and what purpose this has.
*/
//{"KeyChain":"/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain","ChainStage":0}
//{"WishlistChanges":["/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem"],"MiscItems":[{"ItemType":"/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem","ItemCount":1}]}
};
export interface IKeyChainRequest {
KeyChain: string;
ChainStage: number;
Groups?: IGroup[];
}

View File

@ -1,35 +0,0 @@
import { IKeyChainRequest } from "@/src/controllers/api/giveKeyChainTriggeredItemsController";
import { IMessage } from "@/src/models/inboxModel";
import { createMessage } from "@/src/services/inboxService";
import { getInventory } from "@/src/services/inventoryService";
import { getKeyChainMessage } from "@/src/services/itemDataService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { updateQuestStage } from "@/src/services/questService";
import { RequestHandler } from "express";
export const giveKeyChainTriggeredMessageController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const keyChainInfo = JSON.parse((req.body as Buffer).toString()) as IKeyChainRequest;
const keyChainMessage = getKeyChainMessage(keyChainInfo);
const message = {
sndr: keyChainMessage.sender,
msg: keyChainMessage.body,
sub: keyChainMessage.title,
att: keyChainMessage.attachments.length > 0 ? keyChainMessage.attachments : undefined,
countedAtt: keyChainMessage.countedAttachments.length > 0 ? keyChainMessage.countedAttachments : undefined,
icon: keyChainMessage.icon ?? "",
transmission: keyChainMessage.transmission ?? "",
highPriority: keyChainMessage.highPriority ?? false,
r: false
} satisfies IMessage;
await createMessage(accountId, [message]);
const inventory = await getInventory(accountId, "QuestKeys");
updateQuestStage(inventory, keyChainInfo, { m: true });
await inventory.save();
res.send(1);
};

View File

@ -1,45 +0,0 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { addItem, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IOid } from "@/src/types/commonTypes";
import { RequestHandler } from "express";
export const giveQuestKeyRewardController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const rewardRequest = getJSONfromString<IQuestKeyRewardRequest>((req.body as Buffer).toString());
if (Array.isArray(rewardRequest.reward)) {
throw new Error("Multiple rewards not expected");
}
const reward = rewardRequest.reward;
const inventory = await getInventory(accountId);
const inventoryChanges = await addItem(inventory, reward.ItemType, reward.Amount);
await inventory.save();
res.json(inventoryChanges.InventoryChanges);
//TODO: consider whishlist changes
};
export interface IQuestKeyRewardRequest {
reward: IQuestKeyReward;
}
export interface IQuestKeyReward {
RewardType: string;
CouponType: string;
Icon: string;
ItemType: string;
StoreItemType: string;
ProductCategory: string;
Amount: number;
ScalingMultiplier: number;
Durability: string;
DisplayName: string;
Duration: number;
CouponSku: number;
Syndicate: string;
Milestones: any[];
ChooseSetIndex: number;
NewSystemReward: boolean;
_id: IOid;
}

View File

@ -1,14 +1,14 @@
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
import { getInventory } from "@/src/services/inventoryService";
export const startLibraryPersonalTargetController: RequestHandler = async (req, res) => {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const giveStartingGearController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
inventory.LibraryPersonalTarget = req.query.target as string;
inventory.ReceivedStartingGear = true;
console.log(req.query);
await inventory.save();
res.json({
IsQuest: false,
Target: req.query.target
});
res.status(200);
};

View File

@ -1,127 +1,5 @@
import { RequestHandler } from "express";
import { getGuildForRequestEx } from "@/src/services/guildService";
import { ExportDojoRecipes } from "warframe-public-export-plus";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { addMiscItems, addRecipes, getInventory, updateCurrency } from "@/src/services/inventoryService";
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
export const guildTechController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const guild = await getGuildForRequestEx(req, inventory);
const data = JSON.parse(String(req.body)) as TGuildTechRequest;
const action = data.Action.split(",")[0];
if (action == "Sync") {
res.json({
TechProjects: guild.toJSON().TechProjects
});
} else if (action == "Start") {
const recipe = ExportDojoRecipes.research[data.RecipeType!];
guild.TechProjects ??= [];
if (!guild.TechProjects.find(x => x.ItemType == data.RecipeType)) {
guild.TechProjects.push({
ItemType: data.RecipeType!,
ReqCredits: scaleRequiredCount(recipe.price),
ReqItems: recipe.ingredients.map(x => ({
ItemType: x.ItemType,
ItemCount: scaleRequiredCount(x.ItemCount)
})),
State: 0
});
}
await guild.save();
res.end();
} else if (action == "Contribute") {
const contributions = data as IGuildTechContributeFields;
const techProject = guild.TechProjects!.find(x => x.ItemType == contributions.RecipeType)!;
if (contributions.RegularCredits > techProject.ReqCredits) {
contributions.RegularCredits = techProject.ReqCredits;
}
techProject.ReqCredits -= contributions.RegularCredits;
const miscItemChanges = [];
for (const miscItem of contributions.MiscItems) {
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
if (reqItem) {
if (miscItem.ItemCount > reqItem.ItemCount) {
miscItem.ItemCount = reqItem.ItemCount;
}
reqItem.ItemCount -= miscItem.ItemCount;
miscItemChanges.push({
ItemType: miscItem.ItemType,
ItemCount: miscItem.ItemCount * -1
});
}
}
addMiscItems(inventory, miscItemChanges);
const inventoryChanges: IInventoryChanges = {
...updateCurrency(inventory, contributions.RegularCredits, false),
MiscItems: miscItemChanges
};
if (techProject.ReqCredits == 0 && !techProject.ReqItems.find(x => x.ItemCount > 0)) {
// This research is now fully funded.
techProject.State = 1;
const recipe = ExportDojoRecipes.research[data.RecipeType!];
techProject.CompletionDate = new Date(new Date().getTime() + recipe.time * 1000);
}
await guild.save();
await inventory.save();
res.json({
InventoryChanges: inventoryChanges
});
} else if (action == "Buy") {
const purchase = data as IGuildTechBuyFields;
const quantity = parseInt(data.Action.split(",")[1]);
const inventory = await getInventory(accountId);
const recipeChanges = [
{
ItemType: purchase.RecipeType,
ItemCount: quantity
}
];
addRecipes(inventory, recipeChanges);
const currencyChanges = updateCurrency(
inventory,
ExportDojoRecipes.research[purchase.RecipeType].replicatePrice,
false
);
await inventory.save();
// Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`.
res.json({
inventoryChanges: {
...currencyChanges,
Recipes: recipeChanges
}
});
} else {
throw new Error(`unknown guildTech action: ${data.Action}`);
}
};
type TGuildTechRequest = {
Action: string;
} & Partial<IGuildTechStartFields> &
Partial<IGuildTechContributeFields>;
interface IGuildTechStartFields {
Mode: "Guild";
RecipeType: string;
}
type IGuildTechBuyFields = IGuildTechStartFields;
interface IGuildTechContributeFields {
ResearchId: "";
RecipeType: string;
RegularCredits: number;
MiscItems: IMiscItem[];
VaultCredits: number;
VaultMiscItems: IMiscItem[];
}
const scaleRequiredCount = (count: number): number => {
// The recipes in the export are for Moon clans. For now we'll just assume we only have Ghost clans.
return Math.max(1, Math.trunc(count / 100));
export const guildTechController: RequestHandler = (_req, res) => {
res.status(500).end(); // This is what I got for a fresh clan.
};

View File

@ -4,6 +4,7 @@ import { createNewSession } from "@/src/managers/sessionManager";
import { logger } from "@/src/utils/logger";
import { ISession } from "@/src/types/session";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
const hostSessionController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const hostSessionRequest = JSON.parse(req.body as string) as ISession;
@ -11,7 +12,7 @@ const hostSessionController: RequestHandler = async (req, res) => {
const session = createNewSession(hostSessionRequest, accountId);
logger.debug(`New Session Created`, { session });
res.json({ sessionId: { $oid: session.sessionId }, rewardSeed: 99999999 });
res.json({ sessionId: { $id: session.sessionId }, rewardSeed: 99999999 });
};
export { hostSessionController };

View File

@ -1,87 +1,8 @@
import { RequestHandler } from "express";
import { Inbox } from "@/src/models/inboxModel";
import {
createNewEventMessages,
deleteAllMessagesRead,
deleteMessageRead,
getAllMessagesSorted,
getMessage
} from "@/src/services/inboxService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { addItems, getInventory } from "@/src/services/inventoryService";
import { logger } from "@/src/utils/logger";
import { ExportGear } from "warframe-public-export-plus";
import inbox from "@/static/fixed_responses/inbox.json";
export const inboxController: RequestHandler = async (req, res) => {
const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query;
const accountId = await getAccountIdForRequest(req);
if (deleteId) {
if (deleteId === "DeleteAllRead") {
await deleteAllMessagesRead(accountId);
res.status(200).end();
return;
}
await deleteMessageRead(deleteId as string);
res.status(200).end();
} else if (messageId) {
const message = await getMessage(messageId as string);
message.r = true;
const attachmentItems = message.att;
const attachmentCountedItems = message.countedAtt;
if (!attachmentItems && !attachmentCountedItems) {
await message.save();
res.status(200).end();
return;
}
const inventory = await getInventory(accountId);
const inventoryChanges = {};
if (attachmentItems) {
await addItems(
inventory,
attachmentItems.map(attItem => ({
ItemType: attItem,
ItemCount: attItem in ExportGear ? (ExportGear[attItem].purchaseQuantity ?? 1) : 1
})),
inventoryChanges
);
}
if (attachmentCountedItems) {
await addItems(inventory, attachmentCountedItems, inventoryChanges);
}
await inventory.save();
await message.save();
res.json({ InventoryChanges: inventoryChanges });
} else if (latestClientMessageId) {
await createNewEventMessages(req);
const messages = await Inbox.find({ ownerId: accountId }).sort({ date: 1 });
const latestClientMessage = messages.find(m => m._id.toString() === latestClientMessageId);
if (!latestClientMessage) {
logger.debug(`this should only happen after DeleteAllRead `);
res.json({ Inbox: messages });
return;
}
const newMessages = messages.filter(m => m.date > latestClientMessage.date);
if (newMessages.length === 0) {
res.send("no-new");
return;
}
res.json({ Inbox: newMessages });
} else {
//newly created event messages must be newer than account.LatestEventMessageDate
await createNewEventMessages(req);
const messages = await getAllMessagesSorted(accountId);
const inbox = messages.map(m => m.toJSON());
res.json({ Inbox: inbox });
}
const inboxController: RequestHandler = (_req, res) => {
res.json(inbox);
};
export { inboxController };

View File

@ -1,30 +1,18 @@
import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getInventory, addMiscItems, updateCurrency, addRecipes } from "@/src/services/inventoryService";
import { getInventory, addMiscItems } from "@/src/services/inventoryService";
import { IOid } from "@/src/types/commonTypes";
import {
IConsumedSuit,
IHelminthFoodRecord,
IInfestedFoundryDatabase,
IMiscItem,
ITypeCount
} from "@/src/types/inventoryTypes/inventoryTypes";
import { ExportMisc, ExportRecipes } from "warframe-public-export-plus";
import { getRecipe } from "@/src/services/itemDataService";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
import { logger } from "@/src/utils/logger";
import { colorToShard } from "@/src/helpers/shardHelper";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const infestedFoundryController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
switch (req.query.mode) {
case "s": {
// shard installation
const request = getJSONfromString<IShardInstallRequest>(String(req.body));
const request = getJSONfromString(String(req.body)) as IShardInstallRequest;
const inventory = await getInventory(accountId);
const suit = inventory.Suits.find(suit => suit._id.toString() == request.SuitId.$oid)!;
const suit = inventory.Suits.find(suit => suit._id.toString() == request.SuitId.$id)!;
if (!suit.ArchonCrystalUpgrades || suit.ArchonCrystalUpgrades.length != 5) {
suit.ArchonCrystalUpgrades = [{}, {}, {}, {}, {}];
}
@ -48,47 +36,9 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
break;
}
case "x": {
// shard removal
const request = getJSONfromString<IShardUninstallRequest>(String(req.body));
const inventory = await getInventory(accountId);
const suit = inventory.Suits.find(suit => suit._id.toString() == request.SuitId.$oid)!;
// refund shard
const shard = Object.entries(colorToShard).find(
([color]) => color == suit.ArchonCrystalUpgrades![request.Slot].Color
)![1];
const miscItemChanges = [
{
ItemType: shard,
ItemCount: 1
}
];
addMiscItems(inventory, miscItemChanges);
// remove from suit
suit.ArchonCrystalUpgrades![request.Slot] = {};
// remove bile
const bile = inventory.InfestedFoundry!.Resources!.find(
x => x.ItemType == "/Lotus/Types/Items/InfestedFoundry/HelminthBile"
)!;
bile.Count -= 300;
await inventory.save();
res.json({
InventoryChanges: {
MiscItems: miscItemChanges,
InfestedFoundry: inventory.toJSON().InfestedFoundry
}
});
break;
}
case "n": {
// name the beast
const request = getJSONfromString<IHelminthNameRequest>(String(req.body));
const request = getJSONfromString(String(req.body)) as IHelminthNameRequest;
const inventory = await getInventory(accountId);
inventory.InfestedFoundry ??= {};
inventory.InfestedFoundry.Name = request.newName;
@ -103,233 +53,13 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
break;
}
case "c": {
// consume items
const request = getJSONfromString<IHelminthFeedRequest>(String(req.body));
const inventory = await getInventory(accountId);
inventory.InfestedFoundry ??= {};
inventory.InfestedFoundry.Resources ??= [];
const miscItemChanges: IMiscItem[] = [];
let totalPercentagePointsGained = 0;
const currentUnixSeconds = Math.trunc(new Date().getTime() / 1000);
for (const contribution of request.ResourceContributions) {
const snack = ExportMisc.helminthSnacks[contribution.ItemType];
// tally items for removal
const change = miscItemChanges.find(x => x.ItemType == contribution.ItemType);
if (change) {
change.ItemCount -= snack.count;
} else {
miscItemChanges.push({ ItemType: contribution.ItemType, ItemCount: snack.count * -1 });
}
if (snack.type == "/Lotus/Types/Items/InfestedFoundry/HelminthAppetiteCooldownReducer") {
// sentinent apetite
let mostDislikedSnackRecord: IHelminthFoodRecord = { ItemType: "", Date: 0 };
for (const resource of inventory.InfestedFoundry.Resources) {
if (resource.RecentlyConvertedResources) {
for (const record of resource.RecentlyConvertedResources) {
if (record.Date > mostDislikedSnackRecord.Date) {
mostDislikedSnackRecord = record;
}
}
}
}
logger.debug("helminth eats sentient resource; most disliked snack:", {
type: mostDislikedSnackRecord.ItemType,
date: mostDislikedSnackRecord.Date
});
mostDislikedSnackRecord.Date = currentUnixSeconds + 24 * 60 * 60; // Possibly unfaithful
continue;
}
let resource = inventory.InfestedFoundry.Resources.find(x => x.ItemType == snack.type);
if (!resource) {
resource =
inventory.InfestedFoundry.Resources[
inventory.InfestedFoundry.Resources.push({ ItemType: snack.type, Count: 0 }) - 1
];
}
resource.RecentlyConvertedResources ??= [];
let record = resource.RecentlyConvertedResources.find(x => x.ItemType == contribution.ItemType);
if (!record) {
record =
resource.RecentlyConvertedResources[
resource.RecentlyConvertedResources.push({ ItemType: contribution.ItemType, Date: 0 }) - 1
];
}
const hoursRemaining = (record.Date - currentUnixSeconds) / 3600;
const apetiteFactor = apetiteModel(hoursRemaining) / 30;
logger.debug(`helminth eating ${contribution.ItemType} (+${(snack.gain * 100).toFixed(0)}%)`, {
hoursRemaining,
apetiteFactor
});
if (hoursRemaining >= 18) {
record.Date = currentUnixSeconds + 72 * 60 * 60; // Possibly unfaithful
} else {
record.Date = currentUnixSeconds + 24 * 60 * 60;
}
totalPercentagePointsGained += snack.gain * 100 * apetiteFactor; // 30% would be gain=0.3, so percentage points is equal to gain * 100.
resource.Count += Math.trunc(snack.gain * 1000 * apetiteFactor); // 30% would be gain=0.3 or Count=300, so Count=gain*1000.
if (resource.Count > 1000) resource.Count = 1000;
}
const recipeChanges = addInfestedFoundryXP(inventory.InfestedFoundry, 666 * totalPercentagePointsGained);
addRecipes(inventory, recipeChanges);
addMiscItems(inventory, miscItemChanges);
await inventory.save();
res.json({
InventoryChanges: {
Recipes: recipeChanges,
InfestedFoundry: {
XP: inventory.InfestedFoundry.XP,
Resources: inventory.InfestedFoundry.Resources,
Slots: inventory.InfestedFoundry.Slots
},
MiscItems: miscItemChanges
}
});
case "o": // offerings update
// {"OfferingsIndex":540,"SuitTypes":["/Lotus/Powersuits/PaxDuviricus/PaxDuviricusBaseSuit","/Lotus/Powersuits/Nezha/NezhaBaseSuit","/Lotus/Powersuits/Devourer/DevourerBaseSuit"],"Extra":false}
res.status(404).end();
break;
}
case "o": {
// offerings update
const request = getJSONfromString<IHelminthOfferingsUpdate>(String(req.body));
const inventory = await getInventory(accountId);
inventory.InfestedFoundry ??= {};
inventory.InfestedFoundry.InvigorationIndex = request.OfferingsIndex;
inventory.InfestedFoundry.InvigorationSuitOfferings = request.SuitTypes;
if (request.Extra) {
inventory.InfestedFoundry.InvigorationsApplied = 0;
}
await inventory.save();
res.json({
InventoryChanges: {
InfestedFoundry: inventory.toJSON().InfestedFoundry
}
});
break;
}
case "a": {
// subsume warframe
const request = getJSONfromString<IHelminthSubsumeRequest>(String(req.body));
const inventory = await getInventory(accountId);
const recipe = getRecipe(request.Recipe)!;
for (const ingredient of recipe.secretIngredients!) {
const resource = inventory.InfestedFoundry!.Resources!.find(x => x.ItemType == ingredient.ItemType);
if (resource) {
resource.Count -= ingredient.ItemCount;
}
}
const suit = inventory.Suits.id(request.SuitId.$oid)!;
inventory.Suits.pull(suit);
const consumedSuit: IConsumedSuit = { s: suit.ItemType };
if (suit.Configs && suit.Configs[0] && suit.Configs[0].pricol) {
consumedSuit.c = suit.Configs[0].pricol;
}
if ((inventory.InfestedFoundry!.XP ?? 0) < 73125_00) {
inventory.InfestedFoundry!.Slots!--;
}
inventory.InfestedFoundry!.ConsumedSuits ??= [];
inventory.InfestedFoundry!.ConsumedSuits.push(consumedSuit);
inventory.InfestedFoundry!.LastConsumedSuit = suit;
inventory.InfestedFoundry!.AbilityOverrideUnlockCooldown = new Date(
new Date().getTime() + 24 * 60 * 60 * 1000
);
const recipeChanges = addInfestedFoundryXP(inventory.InfestedFoundry!, 1600_00);
addRecipes(inventory, recipeChanges);
await inventory.save();
res.json({
InventoryChanges: {
Recipes: recipeChanges,
RemovedIdItems: [
{
ItemId: request.SuitId
}
],
SuitBin: {
count: -1,
platinum: 0,
Slots: 1
},
InfestedFoundry: inventory.toJSON().InfestedFoundry
}
});
break;
}
case "r": {
// rush subsume
const inventory = await getInventory(accountId);
const currencyChanges = updateCurrency(inventory, 50, true);
const recipeChanges = handleSubsumeCompletion(inventory);
await inventory.save();
res.json({
InventoryChanges: {
...currencyChanges,
Recipes: recipeChanges,
InfestedFoundry: inventory.toJSON().InfestedFoundry
}
});
break;
}
case "u": {
const request = getJSONfromString<IHelminthInvigorationRequest>(String(req.body));
const inventory = await getInventory(accountId);
const suit = inventory.Suits.id(request.SuitId.$oid)!;
const upgradesExpiry = new Date(new Date().getTime() + 7 * 24 * 60 * 60 * 1000);
suit.OffensiveUpgrade = request.OffensiveUpgradeType;
suit.DefensiveUpgrade = request.DefensiveUpgradeType;
suit.UpgradesExpiry = upgradesExpiry;
const recipeChanges = addInfestedFoundryXP(inventory.InfestedFoundry!, 4800_00);
addRecipes(inventory, recipeChanges);
for (let i = 0; i != request.ResourceTypes.length; ++i) {
inventory.InfestedFoundry!.Resources!.find(x => x.ItemType == request.ResourceTypes[i])!.Count -=
request.ResourceCosts[i];
}
inventory.InfestedFoundry!.InvigorationsApplied ??= 0;
inventory.InfestedFoundry!.InvigorationsApplied += 1;
await inventory.save();
res.json({
SuitId: request.SuitId,
OffensiveUpgrade: request.OffensiveUpgradeType,
DefensiveUpgrade: request.DefensiveUpgradeType,
UpgradesExpiry: toMongoDate(upgradesExpiry),
InventoryChanges: {
Recipes: recipeChanges,
InfestedFoundry: inventory.toJSON().InfestedFoundry
}
});
break;
}
case "custom_unlockall": {
const inventory = await getInventory(accountId);
inventory.InfestedFoundry ??= {};
inventory.InfestedFoundry.XP ??= 0;
if (151875_00 > inventory.InfestedFoundry.XP) {
const recipeChanges = addInfestedFoundryXP(
inventory.InfestedFoundry,
151875_00 - inventory.InfestedFoundry.XP
);
addRecipes(inventory, recipeChanges);
await inventory.save();
}
res.end();
break;
}
default:
throw new Error(`unhandled infestedFoundry mode: ${String(req.query.mode)}`);
throw new Error(`unhandled infestedFoundry mode: ${req.query.mode}`);
}
};
@ -340,166 +70,21 @@ interface IShardInstallRequest {
Color: string;
}
interface IShardUninstallRequest {
SuitId: IOid;
Slot: number;
}
interface IHelminthNameRequest {
newName: string;
}
interface IHelminthFeedRequest {
ResourceContributions: {
ItemType: string;
Date: number; // unix timestamp
}[];
}
export const addInfestedFoundryXP = (infestedFoundry: IInfestedFoundryDatabase, delta: number): ITypeCount[] => {
const recipeChanges: ITypeCount[] = [];
infestedFoundry.XP ??= 0;
const prevXP = infestedFoundry.XP;
infestedFoundry.XP += delta;
if (prevXP < 2250_00 && infestedFoundry.XP >= 2250_00) {
infestedFoundry.Slots ??= 0;
infestedFoundry.Slots += 3;
}
if (prevXP < 5625_00 && infestedFoundry.XP >= 5625_00) {
recipeChanges.push({
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthShieldsBlueprint",
ItemCount: 1
});
}
if (prevXP < 10125_00 && infestedFoundry.XP >= 10125_00) {
recipeChanges.push({ ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthHackBlueprint", ItemCount: 1 });
}
if (prevXP < 15750_00 && infestedFoundry.XP >= 15750_00) {
infestedFoundry.Slots ??= 0;
infestedFoundry.Slots += 10;
}
if (prevXP < 22500_00 && infestedFoundry.XP >= 22500_00) {
recipeChanges.push({
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthAmmoEfficiencyBlueprint",
ItemCount: 1
});
}
if (prevXP < 30375_00 && infestedFoundry.XP >= 30375_00) {
recipeChanges.push({ ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthStunBlueprint", ItemCount: 1 });
}
if (prevXP < 39375_00 && infestedFoundry.XP >= 39375_00) {
infestedFoundry.Slots ??= 0;
infestedFoundry.Slots += 20;
}
if (prevXP < 60750_00 && infestedFoundry.XP >= 60750_00) {
recipeChanges.push({ ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthStatusBlueprint", ItemCount: 1 });
}
if (prevXP < 73125_00 && infestedFoundry.XP >= 73125_00) {
infestedFoundry.Slots = 1;
}
if (prevXP < 86625_00 && infestedFoundry.XP >= 86625_00) {
recipeChanges.push({
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthShieldArmorBlueprint",
ItemCount: 1
});
}
if (prevXP < 101250_00 && infestedFoundry.XP >= 101250_00) {
recipeChanges.push({
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthProcBlockBlueprint",
ItemCount: 1
});
}
if (prevXP < 117000_00 && infestedFoundry.XP >= 117000_00) {
recipeChanges.push({
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthEnergyShareBlueprint",
ItemCount: 1
});
}
if (prevXP < 133875_00 && infestedFoundry.XP >= 133875_00) {
recipeChanges.push({
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthMaxStatusBlueprint",
ItemCount: 1
});
}
if (prevXP < 151875_00 && infestedFoundry.XP >= 151875_00) {
recipeChanges.push({
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthTreasureBlueprint",
ItemCount: 1
});
}
return recipeChanges;
};
interface IHelminthSubsumeRequest {
SuitId: IOid;
Recipe: string;
}
export const handleSubsumeCompletion = (inventory: TInventoryDatabaseDocument): ITypeCount[] => {
const [recipeType] = Object.entries(ExportRecipes).find(
([_recipeType, recipe]) =>
recipe.secretIngredientAction == "SIA_WARFRAME_ABILITY" &&
recipe.secretIngredients![0].ItemType == inventory.InfestedFoundry!.LastConsumedSuit!.ItemType
)!;
inventory.InfestedFoundry!.LastConsumedSuit = undefined;
inventory.InfestedFoundry!.AbilityOverrideUnlockCooldown = undefined;
const recipeChanges: ITypeCount[] = [
{
ItemType: recipeType,
ItemCount: 1
}
];
addRecipes(inventory, recipeChanges);
return recipeChanges;
};
interface IHelminthOfferingsUpdate {
OfferingsIndex: number;
SuitTypes: string[];
Extra: boolean;
}
interface IHelminthInvigorationRequest {
SuitId: IOid;
OffensiveUpgradeType: string;
DefensiveUpgradeType: string;
ResourceTypes: string[];
ResourceCosts: number[];
}
// A fitted model for observed apetite values. Likely slightly inaccurate.
//
// Hours remaining, percentage points gained (out of 30 total)
// 0, 30
// 5, 25.8
// 10, 21.6
// 12, 20
// 16, 16.6
// 17, 15.8
// 18, 15
// 20, 15
// 24, 15
// 36, 15
// 40, 13.6
// 47, 11.3
// 48, 11
// 50, 10.3
// 60, 7
// 70, 3.6
// 71, 3.3
// 72, 3
const apetiteModel = (x: number): number => {
if (x <= 0) {
return 30;
}
if (x < 18) {
return -0.84 * x + 30;
}
if (x <= 36) {
return 15;
}
if (x < 71.9) {
return -0.3327892 * x + 26.94135;
}
return 3;
const colorToShard: Record<string, string> = {
ACC_RED: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalAmar",
ACC_RED_MYTHIC: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalAmarMythic",
ACC_YELLOW: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalNira",
ACC_YELLOW_MYTHIC: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalNiraMythic",
ACC_BLUE: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalBoreal",
ACC_BLUE_MYTHIC: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalBorealMythic",
ACC_GREEN: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalGreen",
ACC_GREEN_MYTHIC: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalGreenMythic",
ACC_ORANGE: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalOrange",
ACC_ORANGE_MYTHIC: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalOrangeMythic",
ACC_PURPLE: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalViolet",
ACC_PURPLE_MYTHIC: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalVioletMythic"
};

View File

@ -1,149 +1,71 @@
import { RequestHandler } from "express";
import { getAccountForRequest } from "@/src/services/loginService";
import { Inventory, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { toInventoryResponse } from "@/src/helpers/inventoryHelpers";
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
import { config } from "@/src/services/configService";
import allDialogue from "@/static/fixed_responses/allDialogue.json";
import { ILoadoutDatabase } from "@/src/types/saveLoadoutTypes";
import { IInventoryClient, IShipInventory, equipmentKeys } from "@/src/types/inventoryTypes/inventoryTypes";
import { IPolarity, ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
import {
ExportCustoms,
ExportFlavour,
ExportRegions,
ExportResources,
ExportVirtuals
} from "warframe-public-export-plus";
import { handleSubsumeCompletion } from "./infestedFoundryController";
import { allDailyAffiliationKeys } from "@/src/services/inventoryService";
import { IInventoryDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
//import new_inventory from "@/static/fixed_responses/postTutorialInventory.json";
export const inventoryController: RequestHandler = async (request, response) => {
const account = await getAccountForRequest(request);
// eslint-disable-next-line @typescript-eslint/no-misused-promises
const inventoryController: RequestHandler = async (request, response) => {
let accountId;
try {
accountId = await getAccountIdForRequest(request);
} catch (e) {
response.status(400).send("Log-in expired");
return;
}
const inventory = await Inventory.findOne({ accountOwnerId: account._id.toString() });
const inventory = await Inventory.findOne({ accountOwnerId: accountId });
if (!inventory) {
response.status(400).json({ error: "inventory was undefined" });
return;
}
// Handle daily reset
const today: number = Math.trunc(new Date().getTime() / 86400000);
if (account.LastLoginDay != today) {
account.LastLoginDay = today;
await account.save();
//TODO: make a function that converts from database representation to client
const inventoryJSON: IInventoryDatabase = inventory.toJSON();
console.log(inventoryJSON.Ships);
for (const key of allDailyAffiliationKeys) {
inventory[key] = 16000 + inventory.PlayerLevel * 500;
}
inventory.DailyFocus = 250000 + inventory.PlayerLevel * 5000;
await inventory.save();
}
const inventoryResponse = toInventoryResponse(inventoryJSON);
if (
inventory.InfestedFoundry &&
inventory.InfestedFoundry.AbilityOverrideUnlockCooldown &&
new Date() >= inventory.InfestedFoundry.AbilityOverrideUnlockCooldown
) {
handleSubsumeCompletion(inventory);
await inventory.save();
}
response.json(await getInventoryResponse(inventory, "xpBasedLevelCapDisabled" in request.query));
};
export const getInventoryResponse = async (
inventory: TInventoryDatabaseDocument,
xpBasedLevelCapDisabled: boolean
): Promise<IInventoryClient> => {
const inventoryWithLoadOutPresets = await inventory.populate<{ LoadOutPresets: ILoadoutDatabase }>(
"LoadOutPresets"
);
const inventoryWithLoadOutPresetsAndShips = await inventoryWithLoadOutPresets.populate<{ Ships: IShipInventory }>(
"Ships"
);
const inventoryResponse = inventoryWithLoadOutPresetsAndShips.toJSON<IInventoryClient>();
if (config.infiniteCredits) {
if (config.infiniteResources) {
inventoryResponse.RegularCredits = 999999999;
}
if (config.infinitePlatinum) {
inventoryResponse.PremiumCreditsFree = 999999999;
inventoryResponse.PremiumCredits = 999999999;
}
if (config.infiniteEndo) {
inventoryResponse.FusionPoints = 999999999;
}
if (config.infiniteRegalAya) {
inventoryResponse.PrimeTokens = 999999999;
}
if (config.skipAllDialogue) {
inventoryResponse.TauntHistory = [
{
node: "TreasureTutorial",
state: "TS_COMPLETED"
}
];
for (const str of allDialogue) {
addString(inventoryResponse.NodeIntrosCompleted, str);
}
}
// if (config.unlockAllMissions) {
// //inventoryResponse.Missions = allMissions;
// //inventoryResponse.NodeIntrosCompleted.push("TeshinHardModeUnlocked");
// }
if (config.unlockAllMissions) {
inventoryResponse.Missions = [];
for (const tag of Object.keys(ExportRegions)) {
inventoryResponse.Missions.push({
Completes: 1,
Tier: 1,
Tag: tag
});
}
addString(inventoryResponse.NodeIntrosCompleted, "TeshinHardModeUnlocked");
}
// if (config.unlockAllMissions) {
// //inventoryResponse.Missions = allMissions;
// //addString(inventoryResponse.NodeIntrosCompleted, "TeshinHardModeUnlocked");
// }
if (config.unlockAllShipDecorations) {
inventoryResponse.ShipDecorations = [];
for (const [uniqueName, item] of Object.entries(ExportResources)) {
if (item.productCategory == "ShipDecorations") {
inventoryResponse.ShipDecorations.push({ ItemType: uniqueName, ItemCount: 1 });
}
}
}
// if (config.unlockAllFlavourItems) {
// inventoryResponse.FlavourItems = [];
// for (const uniqueName in ExportFlavour) {
// inventoryResponse.FlavourItems.push({ ItemType: uniqueName });
// }
// }
if (config.unlockAllFlavourItems) {
inventoryResponse.FlavourItems = [];
for (const uniqueName in ExportFlavour) {
inventoryResponse.FlavourItems.push({ ItemType: uniqueName });
}
}
if (config.unlockAllSkins) {
const missingWeaponSkins = new Set(Object.keys(ExportCustoms));
inventoryResponse.WeaponSkins.forEach(x => missingWeaponSkins.delete(x.ItemType));
for (const uniqueName of missingWeaponSkins) {
inventoryResponse.WeaponSkins.push({
ItemId: {
$oid: "ca70ca70ca70ca70" + catBreadHash(uniqueName).toString(16).padStart(8, "0")
},
ItemType: uniqueName
});
}
}
if (config.unlockAllCapturaScenes) {
for (const uniqueName of Object.keys(ExportResources)) {
if (resourceInheritsFrom(uniqueName, "/Lotus/Types/Items/MiscItems/PhotoboothTile")) {
inventoryResponse.MiscItems.push({
ItemType: uniqueName,
ItemCount: 1
});
}
}
}
// if (config.unlockAllSkins) {
// inventoryResponse.WeaponSkins = [];
// for (const uniqueName in ExportCustoms) {
// inventoryResponse.WeaponSkins.push({
// ItemId: {
// $id: "000000000000000000000000"
// },
// ItemType: uniqueName
// });
// }
// }
if (typeof config.spoofMasteryRank === "number" && config.spoofMasteryRank >= 0) {
inventoryResponse.PlayerLevel = config.spoofMasteryRank;
if (!xpBasedLevelCapDisabled) {
if (!("xpBasedLevelCapDisabled" in request.query)) {
// This client has not been patched to accept any mastery rank, need to fake the XP.
inventoryResponse.XPInfo = [];
let numFrames = getExpRequiredForMr(Math.min(config.spoofMasteryRank, 5030)) / 6000;
@ -156,79 +78,18 @@ export const getInventoryResponse = async (
}
}
if (config.universalPolarityEverywhere) {
const Polarity: IPolarity[] = [];
for (let i = 0; i != 12; ++i) {
Polarity.push({
Slot: i,
Value: ArtifactPolarity.Any
});
}
for (const key of equipmentKeys) {
if (key in inventoryResponse) {
for (const equipment of inventoryResponse[key]) {
equipment.Polarity = Polarity;
}
}
}
}
if (config.unlockDoubleCapacityPotatoesEverywhere) {
for (const key of equipmentKeys) {
if (key in inventoryResponse) {
for (const equipment of inventoryResponse[key]) {
equipment.Features ??= 0;
equipment.Features |= EquipmentFeatures.DOUBLE_CAPACITY;
}
}
}
}
if (config.unlockExilusEverywhere) {
for (const key of equipmentKeys) {
if (key in inventoryResponse) {
for (const equipment of inventoryResponse[key]) {
equipment.Features ??= 0;
equipment.Features |= EquipmentFeatures.UTILITY_SLOT;
}
}
}
}
if (config.unlockArcanesEverywhere) {
for (const key of equipmentKeys) {
if (key in inventoryResponse) {
for (const equipment of inventoryResponse[key]) {
equipment.Features ??= 0;
equipment.Features |= EquipmentFeatures.ARCANE_SLOT;
}
}
}
}
if (config.noDailyStandingLimits) {
for (const key of allDailyAffiliationKeys) {
inventoryResponse[key] = 999_999;
}
}
// Fix for #380
inventoryResponse.NextRefill = { $date: { $numberLong: "9999999999999" } };
// This determines if the "void fissures" tab is shown in navigation.
inventoryResponse.HasOwnedVoidProjectionsPreviously = true;
// Omitting this field so opening the navigation resyncs the inventory which is more desirable for typical usage.
//inventoryResponse.LastInventorySync = toOid(new Types.ObjectId());
return inventoryResponse;
response.json(inventoryResponse);
};
export const addString = (arr: string[], str: string): void => {
/*
const addString = (arr: string[], str: string): void => {
if (!arr.find(x => x == str)) {
arr.push(str);
}
};
};*/
const getExpRequiredForMr = (rank: number): number => {
if (rank <= 30) {
@ -237,29 +98,4 @@ const getExpRequiredForMr = (rank: number): number => {
return 2_250_000 + 147_500 * (rank - 30);
};
const resourceInheritsFrom = (resourceName: string, targetName: string): boolean => {
let parentName = resourceGetParent(resourceName);
for (; parentName != undefined; parentName = resourceGetParent(parentName)) {
if (parentName == targetName) {
return true;
}
}
return false;
};
const resourceGetParent = (resourceName: string): string | undefined => {
if (resourceName in ExportResources) {
return ExportResources[resourceName].parentName;
}
return ExportVirtuals[resourceName]?.parentName;
};
// This is FNV1a-32 except operating under modulus 2^31 because JavaScript is stinky and likes producing negative integers out of nowhere.
const catBreadHash = (name: string): number => {
let hash = 2166136261;
for (let i = 0; i != name.length; ++i) {
hash = (hash ^ name.charCodeAt(i)) & 0x7fffffff;
hash = (hash * 16777619) & 0x7fffffff;
}
return hash;
};
export { inventoryController };

View File

@ -1,5 +1,5 @@
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
import { updateCurrency } from "@/src/services/inventoryService";
import { RequestHandler } from "express";
import { updateSlots } from "@/src/services/inventoryService";
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
@ -18,16 +18,19 @@ import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
number of frames = extra - slots + 2
*/
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const inventorySlotsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
//const body = JSON.parse(req.body as string) as IInventorySlotsRequest;
//console.log(body);
//TODO: check which slot was purchased because pvpBonus is also possible
const inventory = await getInventory(accountId);
const currencyChanges = updateCurrency(inventory, 20, true);
updateSlots(inventory, InventorySlot.PVE_LOADOUTS, 1, 1);
await inventory.save();
const currencyChanges = await updateCurrency(20, true, accountId);
await updateSlots(accountId, InventorySlot.PVE_LOADOUTS, 1, 1);
//console.log({ InventoryChanges: currencyChanges }, " added loadout changes:");
res.json({ InventoryChanges: currencyChanges });
};

View File

@ -7,7 +7,7 @@ const joinSessionController: RequestHandler = (_req, res) => {
logger.debug(`JoinSession Request`, { reqBody });
const req = JSON.parse(String(_req.body));
const session = getSessionByID(req.sessionIds[0] as string);
res.json({ rewardSeed: session?.rewardSeed, sessionId: { $oid: session?.sessionId } });
res.json({ rewardSeed: session?.rewardSeed, sessionId: { $id: session?.sessionId } });
};
export { joinSessionController };

View File

@ -1,51 +1,43 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { RequestHandler } from "express";
import { config } from "@/src/services/configService";
import { buildConfig } from "@/src/services/buildConfigService";
import buildConfig from "@/static/data/buildConfig.json";
import { toLoginRequest } from "@/src/helpers/loginHelpers";
import { Account } from "@/src/models/loginModel";
import { createAccount, isCorrectPassword, isNameTaken } from "@/src/services/loginService";
import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes";
import { DTLS, groups, HUB, platformCDNs } from "@/static/fixed_responses/login_static";
import { createAccount, isCorrectPassword } from "@/src/services/loginService";
import { ILoginResponse } from "@/src/types/loginTypes";
import { logger } from "@/src/utils/logger";
export const loginController: RequestHandler = async (request, response) => {
const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
// eslint-disable-next-line @typescript-eslint/no-misused-promises
const loginController: RequestHandler = async (request, response) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-argument
const body = JSON.parse(request.body); // parse octet stream of json data to json object
const loginRequest = toLoginRequest(body);
const account = await Account.findOne({ email: loginRequest.email });
const nonce = Math.round(Math.random() * Number.MAX_SAFE_INTEGER);
const buildLabel: string =
typeof request.query.buildLabel == "string"
? request.query.buildLabel.split(" ").join("+")
: buildConfig.buildLabel;
if (!account && config.autoCreateAccount && loginRequest.ClientType != "webui") {
try {
const nameFromEmail = loginRequest.email.substring(0, loginRequest.email.indexOf("@"));
let name = nameFromEmail;
if (await isNameTaken(name)) {
let suffix = 0;
do {
++suffix;
name = nameFromEmail + suffix;
} while (await isNameTaken(name));
}
const newAccount = await createAccount({
email: loginRequest.email,
password: loginRequest.password,
DisplayName: name,
CountryCode: loginRequest.lang.toUpperCase(),
ClientType: loginRequest.ClientType,
CrossPlatformAllowed: true,
ForceLogoutVersion: 0,
ConsentNeeded: false,
TrackedSettings: [],
Nonce: nonce,
LatestEventMessageDate: new Date(0)
DisplayName: loginRequest.email.substring(0, loginRequest.email.indexOf("@")),
Nonce: nonce
});
logger.debug("created new account");
response.json(createLoginResponse(newAccount, buildLabel));
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { email, password, ...databaseAccount } = newAccount;
const newLoginResponse: ILoginResponse = {
...databaseAccount,
BuildLabel: buildConfig.buildLabel,
NatHash: "0",
SteamId: "0"
};
response.json(newLoginResponse);
return;
} catch (error: unknown) {
if (error instanceof Error) {
@ -63,34 +55,18 @@ export const loginController: RequestHandler = async (request, response) => {
if (account.Nonce == 0 || loginRequest.ClientType != "webui") {
account.Nonce = nonce;
}
if (loginRequest.ClientType != "webui") {
account.CountryCode = loginRequest.lang.toUpperCase();
}
await account.save();
response.json(createLoginResponse(account.toJSON(), buildLabel));
const { email, password, ...databaseAccount } = account.toJSON();
const newLoginResponse: ILoginResponse = {
...databaseAccount,
BuildLabel: buildConfig.buildLabel,
NatHash: "0",
SteamId: "0"
};
response.json(newLoginResponse);
};
const createLoginResponse = (account: IDatabaseAccountJson, buildLabel: string): ILoginResponse => {
return {
id: account.id,
DisplayName: account.DisplayName,
CountryCode: account.CountryCode,
ClientType: account.ClientType,
CrossPlatformAllowed: account.CrossPlatformAllowed,
ForceLogoutVersion: account.ForceLogoutVersion,
AmazonAuthToken: account.AmazonAuthToken,
AmazonRefreshToken: account.AmazonRefreshToken,
ConsentNeeded: account.ConsentNeeded,
TrackedSettings: account.TrackedSettings,
Nonce: account.Nonce,
Groups: groups,
platformCDNs: platformCDNs,
NRS: [config.myAddress],
DTLS: DTLS,
IRC: config.myIrcAddresses ?? [config.myAddress],
HUB: HUB,
BuildLabel: buildLabel,
MatchmakingBuildId: buildConfig.matchmakingBuildId
};
};
export { loginController };

View File

@ -2,6 +2,7 @@ import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { Account } from "@/src/models/loginModel";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
const logoutController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const account = await Account.findOne({ _id: accountId });

View File

@ -1,12 +1,10 @@
import { RequestHandler } from "express";
import { missionInventoryUpdate } from "@/src/services/inventoryService";
import { combineRewardAndLootInventory, getRewards } from "@/src/services/missionInventoryUpdateService";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IMissionInventoryUpdateRequest } from "@/src/types/requestTypes";
import { addMissionInventoryUpdates, addMissionRewards } from "@/src/services/missionInventoryUpdateService";
import { getInventory } from "@/src/services/inventoryService";
import { getInventoryResponse } from "./inventoryController";
import { logger } from "@/src/utils/logger";
/*
**** INPUT ****
- [ ] crossPlaySetting
@ -32,13 +30,13 @@ import { logger } from "@/src/utils/logger";
- [ ] hosts
- [x] ChallengeProgress
- [ ] SeasonChallengeHistory
- [ ] PS (anticheat data)
- [ ] PS (Passive anti-cheat data which includes your username, module list, process list, and system name.)
- [ ] ActiveDojoColorResearch
- [x] RewardInfo
- [ ] ReceivedCeremonyMsg
- [ ] LastCeremonyResetDate
- [ ] MissionPTS (Used to validate the mission/alive time above.)
- [ ] RepHash
- [ ] RepHash (A hash from the replication manager/RepMgr Unknown what it does.)
- [ ] EndOfMatchUpload
- [ ] ObjectiveReached
- [ ] FpsAvg
@ -46,40 +44,37 @@ import { logger } from "@/src/utils/logger";
- [ ] FpsMax
- [ ] FpsSamples
*/
//move credit calc in here, return MissionRewards: [] if no reward info
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const missionInventoryUpdateController: RequestHandler = async (req, res): Promise<void> => {
const missionInventoryUpdateController: RequestHandler = async (req, res): Promise<void> => {
const accountId = await getAccountIdForRequest(req);
const missionReport = getJSONfromString<IMissionInventoryUpdateRequest>((req.body as string).toString());
logger.debug("mission report:", missionReport);
const inventory = await getInventory(accountId);
const inventoryUpdates = addMissionInventoryUpdates(inventory, missionReport);
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call
const lootInventory = getJSONfromString(req.body.toString()) as IMissionInventoryUpdateRequest;
if (missionReport.MissionStatus !== "GS_SUCCESS") {
await inventory.save();
const inventoryResponse = await getInventoryResponse(inventory, true);
logger.debug("missionInventoryUpdate with lootInventory =", lootInventory);
const { InventoryChanges, MissionRewards } = getRewards(lootInventory);
const { combinedInventoryChanges, TotalCredits, CreditsBonus, MissionCredits } = combineRewardAndLootInventory(
InventoryChanges,
lootInventory
);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const InventoryJson = JSON.stringify(await missionInventoryUpdate(combinedInventoryChanges, accountId));
res.json({
InventoryJson: JSON.stringify(inventoryResponse),
MissionRewards: []
// InventoryJson, // this part will reset game data and missions will be locked
MissionRewards,
InventoryChanges,
TotalCredits,
CreditsBonus,
MissionCredits
});
return;
} catch (err) {
console.error("Error parsing JSON data:", err);
}
const { MissionRewards, inventoryChanges, credits } = await addMissionRewards(inventory, missionReport);
await inventory.save();
const inventoryResponse = await getInventoryResponse(inventory, true);
//TODO: figure out when to send inventory. it is needed for many cases.
res.json({
InventoryJson: JSON.stringify(inventoryResponse),
InventoryChanges: inventoryChanges,
MissionRewards,
...credits,
...inventoryUpdates,
FusionPoints: inventoryChanges?.FusionPoints
});
};
/*
@ -92,3 +87,5 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
- [x] InventoryChanges
- [x] FusionPoints
*/
export { missionInventoryUpdateController };

View File

@ -5,11 +5,7 @@ import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import { getInventory, updateCurrency, addEquipment, addMiscItems } from "@/src/services/inventoryService";
const modularWeaponTypes: Record<string, TEquipmentKey> = {
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary": "LongGuns",
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam": "LongGuns",
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryLauncher": "LongGuns",
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun": "LongGuns",
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimarySniper": "LongGuns",
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary": "Pistols",
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam": "Pistols",
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun": "Pistols",
@ -27,19 +23,26 @@ interface IModularCraftRequest {
Parts: string[];
}
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const modularWeaponCraftingController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const data = getJSONfromString<IModularCraftRequest>(String(req.body));
const data = getJSONfromString(String(req.body)) as IModularCraftRequest;
if (!(data.WeaponType in modularWeaponTypes)) {
throw new Error(`unknown modular weapon type: ${data.WeaponType}`);
}
const category = modularWeaponTypes[data.WeaponType];
const inventory = await getInventory(accountId);
// Give weapon
const weapon = addEquipment(inventory, category, data.WeaponType, data.Parts);
const weapon = await addEquipment(category, data.WeaponType, accountId, data.Parts);
// Remove credits & parts
// Remove credits
const currencyChanges = await updateCurrency(
category == "Hoverboards" || category == "MoaPets" ? 5000 : 4000,
false,
accountId
);
// Remove parts
const miscItemChanges = [];
for (const part of data.Parts) {
miscItemChanges.push({
@ -47,11 +50,7 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
ItemCount: -1
});
}
const currencyChanges = updateCurrency(
inventory,
category == "Hoverboards" || category == "MoaPets" ? 5000 : 4000,
false
);
const inventory = await getInventory(accountId);
addMiscItems(inventory, miscItemChanges);
await inventory.save();

View File

@ -8,10 +8,11 @@ interface INameWeaponRequest {
ItemName: string;
}
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const nameWeaponController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const body = getJSONfromString<INameWeaponRequest>(String(req.body));
const body = getJSONfromString(String(req.body)) as INameWeaponRequest;
const item = inventory[req.query.Category as string as TEquipmentKey].find(
item => item._id.toString() == (req.query.ItemId as string)
)!;
@ -20,9 +21,8 @@ export const nameWeaponController: RequestHandler = async (req, res) => {
} else {
item.ItemName = undefined;
}
const currencyChanges = updateCurrency(inventory, "webui" in req.query ? 0 : 15, true);
await inventory.save();
res.json({
InventoryChanges: currencyChanges
InventoryChanges: await updateCurrency("webui" in req.query ? 0 : 15, true, accountId)
});
};

View File

@ -1,31 +0,0 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IPlayerSkills } from "@/src/types/inventoryTypes/inventoryTypes";
import { RequestHandler } from "express";
export const playerSkillsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const request = getJSONfromString<IPlayerSkillsRequest>(String(req.body));
const oldRank: number = inventory.PlayerSkills[request.Skill as keyof IPlayerSkills];
const cost = (request.Pool == "LPP_DRIFTER" ? drifterCosts[oldRank] : 1 << oldRank) * 1000;
inventory.PlayerSkills[request.Pool as keyof IPlayerSkills] -= cost;
inventory.PlayerSkills[request.Skill as keyof IPlayerSkills]++;
await inventory.save();
res.json({
Pool: request.Pool,
PoolInc: -cost,
Skill: request.Skill,
Rank: oldRank + 1
});
};
interface IPlayerSkillsRequest {
Pool: string;
Skill: string;
}
const drifterCosts = [20, 25, 30, 45, 65, 90, 125, 160, 205, 255];

View File

@ -3,6 +3,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
import { ExportRelics, IRelic } from "warframe-public-export-plus";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const projectionManagerController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);

View File

@ -1,14 +1,12 @@
import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IPurchaseRequest } from "@/src/types/purchaseTypes";
import { toPurchaseRequest } from "@/src/helpers/purchaseHelpers";
import { handlePurchase } from "@/src/services/purchaseService";
import { getInventory } from "@/src/services/inventoryService";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const purchaseController: RequestHandler = async (req, res) => {
const purchaseRequest = JSON.parse(String(req.body)) as IPurchaseRequest;
const purchaseRequest = toPurchaseRequest(JSON.parse(String(req.body)));
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const response = await handlePurchase(purchaseRequest, inventory);
await inventory.save();
const response = await handlePurchase(purchaseRequest, accountId);
res.json(response);
};

View File

@ -1,19 +1,16 @@
import { getDojoClient, getGuildForRequest } from "@/src/services/guildService";
import { getGuildForRequest } from "@/src/services/guildService";
import { RequestHandler } from "express";
import { ExportDojoRecipes } from "warframe-public-export-plus";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const queueDojoComponentDestructionController: RequestHandler = async (req, res) => {
const guild = await getGuildForRequest(req);
const componentId = req.query.componentId as string;
const component = guild.DojoComponents!.splice(
guild.DojoComponents!.splice(
guild.DojoComponents!.findIndex(x => x._id.toString() === componentId),
1
)[0];
const room = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf);
if (room) {
guild.DojoCapacity -= room.capacity;
guild.DojoEnergy -= room.energy;
}
);
await guild.save();
res.json(getDojoClient(guild, 1));
res.json({
DojoRequestStatus: 1
});
};

View File

@ -1,104 +1,9 @@
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { ExportUpgrades } from "warframe-public-export-plus";
import { getRandomElement } from "@/src/services/rngService";
export const rerollRandomModController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const request = getJSONfromString<RerollRandomModRequest>(String(req.body));
if ("ItemIds" in request) {
const inventory = await getInventory(accountId, "Upgrades MiscItems");
const upgrade = inventory.Upgrades.id(request.ItemIds[0])!;
const fingerprint = JSON.parse(upgrade.UpgradeFingerprint!) as IUnveiledRivenFingerprint;
fingerprint.rerolls ??= 0;
const kuvaCost = fingerprint.rerolls < rerollCosts.length ? rerollCosts[fingerprint.rerolls] : 3500;
addMiscItems(inventory, [
{
ItemType: "/Lotus/Types/Items/MiscItems/Kuva",
ItemCount: kuvaCost * -1
}
]);
fingerprint.rerolls++;
upgrade.UpgradeFingerprint = JSON.stringify(fingerprint);
randomiseStats(upgrade.ItemType, fingerprint);
upgrade.PendingRerollFingerprint = JSON.stringify(fingerprint);
await inventory.save();
res.json({
changes: [
{
ItemId: { $oid: request.ItemIds[0] },
UpgradeFingerprint: upgrade.UpgradeFingerprint,
PendingRerollFingerprint: upgrade.PendingRerollFingerprint
}
],
cost: kuvaCost
});
} else {
const inventory = await getInventory(accountId, "Upgrades");
const upgrade = inventory.Upgrades.id(request.ItemId)!;
if (request.CommitReroll && upgrade.PendingRerollFingerprint) {
upgrade.UpgradeFingerprint = upgrade.PendingRerollFingerprint;
}
upgrade.PendingRerollFingerprint = undefined;
await inventory.save();
res.send(upgrade.UpgradeFingerprint);
}
const rerollRandomModController: RequestHandler = (_req, res) => {
logger.debug("RerollRandomMod Request", { info: _req.body.toString("hex").replace(/(.)(.)/g, "$1$2 ") });
res.json({});
};
const randomiseStats = (randomModType: string, fingerprint: IUnveiledRivenFingerprint): void => {
const meta = ExportUpgrades[randomModType];
fingerprint.buffs = [];
const numBuffs = 2 + Math.trunc(Math.random() * 2); // 2 or 3
const buffEntries = meta.upgradeEntries!.filter(x => x.canBeBuff);
for (let i = 0; i != numBuffs; ++i) {
const buffIndex = Math.trunc(Math.random() * buffEntries.length);
const entry = buffEntries[buffIndex];
fingerprint.buffs.push({ Tag: entry.tag, Value: Math.trunc(Math.random() * 0x40000000) });
buffEntries.splice(buffIndex, 1);
}
fingerprint.curses = [];
if (Math.random() < 0.5) {
const entry = getRandomElement(
meta.upgradeEntries!.filter(x => x.canBeCurse && !fingerprint.buffs.find(y => y.Tag == x.tag))
);
fingerprint.curses.push({ Tag: entry.tag, Value: Math.trunc(Math.random() * 0x40000000) });
}
};
type RerollRandomModRequest = LetsGoGamblingRequest | AwDangitRequest;
interface LetsGoGamblingRequest {
ItemIds: string[];
}
interface AwDangitRequest {
ItemId: string;
CommitReroll: boolean;
}
interface IUnveiledRivenFingerprint {
compat: string;
lim: number;
lvl: number;
lvlReq: 0;
rerolls?: number;
pol: string;
buffs: IRivenStat[];
curses: IRivenStat[];
}
interface IRivenStat {
Tag: string;
Value: number;
}
const rerollCosts = [900, 1000, 1200, 1400, 1700, 2000, 2350, 2750, 3150];
export { rerollRandomModController };

View File

@ -1,85 +0,0 @@
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { ICompletedDialogue } from "@/src/types/inventoryTypes/inventoryTypes";
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express";
export const saveDialogueController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const request = JSON.parse(String(req.body)) as SaveDialogueRequest;
if ("YearIteration" in request) {
const inventory = await getInventory(accountId);
if (inventory.DialogueHistory) {
inventory.DialogueHistory.YearIteration = request.YearIteration;
} else {
inventory.DialogueHistory = { YearIteration: request.YearIteration };
}
await inventory.save();
res.end();
} else {
const inventory = await getInventory(accountId);
if (!inventory.DialogueHistory) {
throw new Error("bad inventory state");
}
if (request.QueuedDialogues.length != 0 || request.OtherDialogueInfos.length != 0) {
logger.error(`saveDialogue request not fully handled: ${String(req.body)}`);
}
inventory.DialogueHistory.Dialogues ??= [];
let dialogue = inventory.DialogueHistory.Dialogues.find(x => x.DialogueName == request.DialogueName);
if (!dialogue) {
dialogue =
inventory.DialogueHistory.Dialogues[
inventory.DialogueHistory.Dialogues.push({
Rank: 0,
Chemistry: 0,
AvailableDate: new Date(0),
AvailableGiftDate: new Date(0),
RankUpExpiry: new Date(0),
BountyChemExpiry: new Date(0),
Gifts: [],
Booleans: [],
Completed: [],
DialogueName: request.DialogueName
}) - 1
];
}
dialogue.Rank = request.Rank;
dialogue.Chemistry = request.Chemistry;
//dialogue.QueuedDialogues = request.QueuedDialogues;
for (const bool of request.Booleans) {
dialogue.Booleans.push(bool);
}
for (const bool of request.ResetBooleans) {
const index = dialogue.Booleans.findIndex(x => x == bool);
if (index != -1) {
dialogue.Booleans.splice(index, 1);
}
}
dialogue.Completed.push(request.Data);
const tomorrowAt0Utc = (Math.trunc(Date.now() / (86400 * 1000)) + 1) * 86400 * 1000;
dialogue.AvailableDate = new Date(tomorrowAt0Utc);
await inventory.save();
res.json({
InventoryChanges: [],
AvailableDate: { $date: { $numberLong: tomorrowAt0Utc.toString() } }
});
}
};
type SaveDialogueRequest = SaveYearIterationRequest | SaveCompletedDialogueRequest;
interface SaveYearIterationRequest {
YearIteration: number;
}
interface SaveCompletedDialogueRequest {
DialogueName: string;
Rank: number;
Chemistry: number;
CompletionType: number;
QueuedDialogues: string[]; // unsure
Booleans: string[];
ResetBooleans: string[];
Data: ICompletedDialogue;
OtherDialogueInfos: string[]; // unsure
}

View File

@ -4,6 +4,7 @@ import { handleInventoryItemConfigChange } from "@/src/services/saveLoadoutServi
import { getAccountIdForRequest } from "@/src/services/loginService";
import { logger } from "@/src/utils/logger";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const saveLoadoutController: RequestHandler = async (req, res) => {
//validate here
const accountId = await getAccountIdForRequest(req);

View File

@ -1,22 +0,0 @@
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getInventory } from "@/src/services/inventoryService";
import { RequestHandler } from "express";
import { ISettings } from "../../types/inventoryTypes/inventoryTypes";
interface ISaveSettingsRequest {
Settings: ISettings;
}
const saveSettingsController: RequestHandler = async (req, res): Promise<void> => {
const accountId = await getAccountIdForRequest(req);
const settingResults = getJSONfromString<ISaveSettingsRequest>(String(req.body));
const inventory = await getInventory(accountId);
inventory.Settings = Object.assign(inventory.Settings, settingResults.Settings);
await inventory.save();
res.json(inventory.Settings);
};
export { saveSettingsController };

View File

@ -1,7 +1,9 @@
import { RequestHandler } from "express";
import { ISellRequest } from "@/src/types/sellTypes";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory, addMods, addRecipes, addMiscItems, addConsumables } from "@/src/services/inventoryService";
import { getInventory, addMods, addRecipes } from "@/src/services/inventoryService";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const sellController: RequestHandler = async (req, res) => {
const payload = JSON.parse(String(req.body)) as ISellRequest;
const accountId = await getAccountIdForRequest(req);
@ -12,20 +14,6 @@ export const sellController: RequestHandler = async (req, res) => {
inventory.RegularCredits += payload.SellPrice;
} else if (payload.SellCurrency == "SC_FusionPoints") {
inventory.FusionPoints += payload.SellPrice;
} else if (payload.SellCurrency == "SC_PrimeBucks") {
addMiscItems(inventory, [
{
ItemType: "/Lotus/Types/Items/MiscItems/PrimeBucks",
ItemCount: payload.SellPrice
}
]);
} else if (payload.SellCurrency == "SC_DistillPoints") {
addMiscItems(inventory, [
{
ItemType: "/Lotus/Types/Items/MiscItems/DistillPoints",
ItemCount: payload.SellPrice
}
]);
} else {
throw new Error("Unknown SellCurrency: " + payload.SellCurrency);
}
@ -51,51 +39,6 @@ export const sellController: RequestHandler = async (req, res) => {
inventory.Melee.pull({ _id: sellItem.String });
});
}
if (payload.Items.SpaceSuits) {
payload.Items.SpaceSuits.forEach(sellItem => {
inventory.SpaceSuits.pull({ _id: sellItem.String });
});
}
if (payload.Items.SpaceGuns) {
payload.Items.SpaceGuns.forEach(sellItem => {
inventory.SpaceGuns.pull({ _id: sellItem.String });
});
}
if (payload.Items.SpaceMelee) {
payload.Items.SpaceMelee.forEach(sellItem => {
inventory.SpaceMelee.pull({ _id: sellItem.String });
});
}
if (payload.Items.Sentinels) {
payload.Items.Sentinels.forEach(sellItem => {
inventory.Sentinels.pull({ _id: sellItem.String });
});
}
if (payload.Items.SentinelWeapons) {
payload.Items.SentinelWeapons.forEach(sellItem => {
inventory.SentinelWeapons.pull({ _id: sellItem.String });
});
}
if (payload.Items.OperatorAmps) {
payload.Items.OperatorAmps.forEach(sellItem => {
inventory.OperatorAmps.pull({ _id: sellItem.String });
});
}
if (payload.Items.Hoverboards) {
payload.Items.Hoverboards.forEach(sellItem => {
inventory.Hoverboards.pull({ _id: sellItem.String });
});
}
if (payload.Items.Consumables) {
const consumablesChanges = [];
for (const sellItem of payload.Items.Consumables) {
consumablesChanges.push({
ItemType: sellItem.String,
ItemCount: sellItem.Count * -1
});
}
addConsumables(inventory, consumablesChanges);
}
if (payload.Items.Recipes) {
const recipeChanges = [];
for (const sellItem of payload.Items.Recipes) {
@ -120,51 +63,7 @@ export const sellController: RequestHandler = async (req, res) => {
}
});
}
if (payload.Items.MiscItems) {
payload.Items.MiscItems.forEach(sellItem => {
addMiscItems(inventory, [
{
ItemType: sellItem.String,
ItemCount: sellItem.Count * -1
}
]);
});
}
await inventory.save();
res.json({});
};
interface ISellRequest {
Items: {
Suits?: ISellItem[];
LongGuns?: ISellItem[];
Pistols?: ISellItem[];
Melee?: ISellItem[];
Consumables?: ISellItem[];
Recipes?: ISellItem[];
Upgrades?: ISellItem[];
MiscItems?: ISellItem[];
SpaceSuits?: ISellItem[];
SpaceGuns?: ISellItem[];
SpaceMelee?: ISellItem[];
Sentinels?: ISellItem[];
SentinelWeapons?: ISellItem[];
OperatorAmps?: ISellItem[];
Hoverboards?: ISellItem[];
};
SellPrice: number;
SellCurrency:
| "SC_RegularCredits"
| "SC_PrimeBucks"
| "SC_FusionPoints"
| "SC_DistillPoints"
| "SC_CrewShipFusionPoints"
| "SC_Resources";
buildLabel: string;
}
interface ISellItem {
String: string; // oid or uniqueName
Count: number;
}

View File

@ -1,18 +1,7 @@
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
export const setActiveQuestController: RequestHandler<
Record<string, never>,
undefined,
undefined,
{ quest: string | undefined }
> = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const quest = req.query.quest;
const inventory = await getInventory(accountId, "ActiveQuest");
inventory.ActiveQuest = quest ?? "";
await inventory.save();
res.status(200).end();
const setActiveQuestController: RequestHandler = (_req, res) => {
res.sendStatus(200);
};
export { setActiveQuestController };

View File

@ -4,6 +4,7 @@ import { parseString } from "@/src/helpers/general";
import { RequestHandler } from "express";
import { Types } from "mongoose";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const setActiveShipController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const shipId = parseString(req.query.shipId);

View File

@ -2,23 +2,12 @@ import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getPersonalRooms } from "@/src/services/personalRoomsService";
import { TBootLocation } from "@/src/types/shipTypes";
import { getInventory } from "@/src/services/inventoryService";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const setBootLocationController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const personalRooms = await getPersonalRooms(accountId);
personalRooms.Ship.BootLocation = req.query.bootLocation as string as TBootLocation;
await personalRooms.save();
if (personalRooms.Ship.BootLocation == "SHOP") {
// Temp fix so the motorcycle in the backroom doesn't appear broken.
// This code may be removed when quests are fully implemented.
const inventory = await getInventory(accountId);
if (inventory.Motorcycles.length == 0) {
inventory.Motorcycles.push({ ItemType: "/Lotus/Types/Vehicles/Motorcycle/MotorcyclePowerSuit" });
await inventory.save();
}
}
res.end();
};

View File

@ -1,18 +0,0 @@
import { RequestHandler } from "express";
import { getDojoClient, getGuildForRequest } from "@/src/services/guildService";
export const setDojoComponentMessageController: RequestHandler = async (req, res) => {
const guild = await getGuildForRequest(req);
// At this point, we know that a member of the guild is making this request. Assuming they are allowed to change the message.
const component = guild.DojoComponents!.find(x => x._id.equals(req.query.componentId as string))!;
const payload = JSON.parse(String(req.body)) as SetDojoComponentMessageRequest;
if ("Name" in payload) {
component.Name = payload.Name;
} else {
component.Message = payload.Message;
}
await guild.save();
res.json(getDojoClient(guild, 1));
};
type SetDojoComponentMessageRequest = { Name: string } | { Message: string };

View File

@ -1,17 +0,0 @@
import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
export const setEquippedInstrumentController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const body = getJSONfromString<ISetEquippedInstrumentRequest>(String(req.body));
inventory.EquippedInstrument = body.Instrument;
await inventory.save();
res.end();
};
interface ISetEquippedInstrumentRequest {
Instrument: string;
}

View File

@ -1,11 +0,0 @@
import { getAccountIdForRequest } from "@/src/services/loginService";
import { ISetPlacedDecoInfoRequest } from "@/src/types/shipTypes";
import { RequestHandler } from "express";
import { handleSetPlacedDecoInfo } from "@/src/services/shipCustomizationsService";
export const setPlacedDecoInfoController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const payload = JSON.parse(req.body as string) as ISetPlacedDecoInfoRequest;
await handleSetPlacedDecoInfo(accountId, payload);
res.end();
};

View File

@ -1,15 +1,14 @@
import { getAccountIdForRequest } from "@/src/services/loginService";
import { setShipCustomizations } from "@/src/services/shipCustomizationsService";
import { ISetShipCustomizationsRequest } from "@/src/types/shipTypes";
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const setShipCustomizationsController: RequestHandler = async (req, res) => {
try {
const accountId = await getAccountIdForRequest(req);
const setShipCustomizationsRequest = JSON.parse(req.body as string) as ISetShipCustomizationsRequest;
const setShipCustomizationsResponse = await setShipCustomizations(accountId, setShipCustomizationsRequest);
const setShipCustomizationsResponse = await setShipCustomizations(setShipCustomizationsRequest);
res.json(setShipCustomizationsResponse);
} catch (error: unknown) {
if (error instanceof Error) {

View File

@ -1,31 +0,0 @@
import { getAccountIdForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
import { getPersonalRooms } from "@/src/services/personalRoomsService";
import { IOid } from "@/src/types/commonTypes";
import { Types } from "mongoose";
export const setShipFavouriteLoadoutController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const personalRooms = await getPersonalRooms(accountId);
const body = JSON.parse(String(req.body)) as ISetShipFavouriteLoadoutRequest;
if (body.BootLocation != "SHOP") {
throw new Error(`unexpected BootLocation: ${body.BootLocation}`);
}
const display = personalRooms.TailorShop.FavouriteLoadouts.find(x => x.Tag == body.TagName);
if (display) {
display.LoadoutId = new Types.ObjectId(body.FavouriteLoadoutId.$oid);
} else {
personalRooms.TailorShop.FavouriteLoadouts.push({
Tag: body.TagName,
LoadoutId: new Types.ObjectId(body.FavouriteLoadoutId.$oid)
});
}
await personalRooms.save();
res.json({});
};
interface ISetShipFavouriteLoadoutRequest {
BootLocation: string;
FavouriteLoadoutId: IOid;
TagName: string;
}

View File

@ -2,6 +2,7 @@ import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const setSupportedSyndicateController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);

View File

@ -4,10 +4,11 @@ import { getInventory } from "@/src/services/inventoryService";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { WeaponTypeInternal } from "@/src/services/itemDataService";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const setWeaponSkillTreeController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const payload = getJSONfromString<ISetWeaponSkillTreeRequest>(String(req.body));
const payload = getJSONfromString(String(req.body)) as ISetWeaponSkillTreeRequest;
const item = inventory[req.query.Category as WeaponTypeInternal].find(
item => item._id.toString() == (req.query.ItemId as string)

View File

@ -4,6 +4,7 @@ import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express";
import { handleSetShipDecorations } from "@/src/services/shipCustomizationsService";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const shipDecorationsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const shipDecorationsRequest = JSON.parse(req.body as string) as IShipDecorationsRequest;
@ -13,7 +14,7 @@ export const shipDecorationsController: RequestHandler = async (req, res) => {
res.send(placedDecoration);
} catch (error: unknown) {
if (error instanceof Error) {
logger.error(`error in shipDecorationsController: ${error.message}`);
logger.error(`error in saveLoadoutController: ${error.message}`);
res.status(400).json({ error: error.message });
}
}

View File

@ -1,34 +1,29 @@
import { RequestHandler } from "express";
import { IDojoComponentClient } from "@/src/types/guildTypes";
import { getDojoClient, getGuildForRequest } from "@/src/services/guildService";
import { getGuildForRequest } from "@/src/services/guildService";
import { Types } from "mongoose";
import { ExportDojoRecipes } from "warframe-public-export-plus";
interface IStartDojoRecipeRequest {
PlacedComponent: IDojoComponentClient;
Revision: number;
}
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const startDojoRecipeController: RequestHandler = async (req, res) => {
const guild = await getGuildForRequest(req);
// At this point, we know that a member of the guild is making this request. Assuming they are allowed to start a build.
const request = JSON.parse(String(req.body)) as IStartDojoRecipeRequest;
const room = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == request.PlacedComponent.pf);
if (room) {
guild.DojoCapacity += room.capacity;
guild.DojoEnergy += room.energy;
}
guild.DojoComponents!.push({
_id: new Types.ObjectId(),
pf: request.PlacedComponent.pf,
ppf: request.PlacedComponent.ppf,
pi: new Types.ObjectId(request.PlacedComponent.pi!.$oid),
pi: new Types.ObjectId(request.PlacedComponent.pi!.$id),
op: request.PlacedComponent.op,
pp: request.PlacedComponent.pp,
CompletionTime: new Date(Date.now()) // TOOD: Omit this field & handle the "Collecting Materials" state.
});
await guild.save();
res.json(getDojoClient(guild, 0));
res.json({
DojoRequestStatus: 0
});
};

View File

@ -1,106 +1,21 @@
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { startRecipe } from "@/src/services/recipeService";
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express";
import { getRecipe } from "@/src/services/itemDataService";
import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inventoryService";
import { unixTimesInMs } from "@/src/constants/timeConstants";
import { Types } from "mongoose";
import { ISpectreLoadout } from "@/src/types/inventoryTypes/inventoryTypes";
interface IStartRecipeRequest {
RecipeName: string;
Ids: string[];
}
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const startRecipeController: RequestHandler = async (req, res) => {
const startRecipeRequest = getJSONfromString<IStartRecipeRequest>(String(req.body));
const startRecipeRequest = getJSONfromString(String(req.body)) as IStartRecipeRequest;
logger.debug("StartRecipe Request", { startRecipeRequest });
const accountId = await getAccountIdForRequest(req);
const recipeName = startRecipeRequest.RecipeName;
const recipe = getRecipe(recipeName);
if (!recipe) {
throw new Error(`unknown recipe ${recipeName}`);
}
const ingredientsInverse = recipe.ingredients.map(component => ({
ItemType: component.ItemType,
ItemCount: component.ItemCount * -1
}));
const inventory = await getInventory(accountId);
updateCurrency(inventory, recipe.buildPrice, false);
addMiscItems(inventory, ingredientsInverse);
//buildtime is in seconds
const completionDate = new Date(Date.now() + recipe.buildTime * unixTimesInMs.second);
inventory.PendingRecipes.push({
ItemType: recipeName,
CompletionDate: completionDate,
_id: new Types.ObjectId()
});
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
const spectreLoadout: ISpectreLoadout = {
ItemType: recipe.resultType,
Suits: "",
LongGuns: "",
Pistols: "",
Melee: ""
};
for (
let secretIngredientsIndex = 0;
secretIngredientsIndex != recipe.secretIngredients!.length;
++secretIngredientsIndex
) {
const type = recipe.secretIngredients![secretIngredientsIndex].ItemType;
const oid = startRecipeRequest.Ids[recipe.ingredients.length + secretIngredientsIndex];
if (oid == "ffffffffffffffffffffffff") {
// user chose to preserve the active loadout
break;
}
if (type == "/Lotus/Types/Game/PowerSuits/PlayerPowerSuit") {
const item = inventory.Suits.id(oid)!;
spectreLoadout.Suits = item.ItemType;
} else if (type == "/Lotus/Weapons/Tenno/Pistol/LotusPistol") {
const item = inventory.Pistols.id(oid)!;
spectreLoadout.Pistols = item.ItemType;
spectreLoadout.PistolsModularParts = item.ModularParts;
} else if (type == "/Lotus/Weapons/Tenno/LotusLongGun") {
const item = inventory.LongGuns.id(oid)!;
spectreLoadout.LongGuns = item.ItemType;
spectreLoadout.LongGunsModularParts = item.ModularParts;
} else {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
console.assert(type == "/Lotus/Types/Game/LotusMeleeWeapon");
const item = inventory.Melee.id(oid)!;
spectreLoadout.Melee = item.ItemType;
spectreLoadout.MeleeModularParts = item.ModularParts;
}
}
if (
spectreLoadout.Suits != "" &&
spectreLoadout.LongGuns != "" &&
spectreLoadout.Pistols != "" &&
spectreLoadout.Melee != ""
) {
inventory.PendingSpectreLoadouts ??= [];
const existingIndex = inventory.PendingSpectreLoadouts.findIndex(x => x.ItemType == recipe.resultType);
if (existingIndex != -1) {
inventory.PendingSpectreLoadouts.splice(existingIndex, 1);
}
inventory.PendingSpectreLoadouts.push(spectreLoadout);
logger.debug("pending spectre loadout", spectreLoadout);
}
}
const newInventory = await inventory.save();
res.json({
RecipeId: { $oid: newInventory.PendingRecipes[newInventory.PendingRecipes.length - 1]._id.toString() }
});
const newRecipeId = await startRecipe(startRecipeRequest.RecipeName, accountId);
res.json(newRecipeId);
};

View File

@ -3,6 +3,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService";
import { IStepSequencer } from "@/src/types/inventoryTypes/inventoryTypes";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const stepSequencersController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);

View File

@ -1,84 +1,25 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { syndicateSacrifice } from "@/src/services/inventoryService";
import { ISyndicateSacrifice } from "@/src/types/syndicateTypes";
import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { ExportSyndicates, ISyndicateSacrifice } from "warframe-public-export-plus";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { addMiscItems, combineInventoryChanges, getInventory, updateCurrency } from "@/src/services/inventoryService";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
export const syndicateSacrificeController: RequestHandler = async (request, response) => {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
const syndicateSacrificeController: RequestHandler = async (request, response) => {
const accountId = await getAccountIdForRequest(request);
const inventory = await getInventory(accountId);
const data = getJSONfromString<ISyndicateSacrificeRequest>(String(request.body));
let syndicate = inventory.Affiliations.find(x => x.Tag == data.AffiliationTag);
if (!syndicate) {
syndicate = inventory.Affiliations[inventory.Affiliations.push({ Tag: data.AffiliationTag, Standing: 0 }) - 1];
}
const level = data.SacrificeLevel - (syndicate.Title ?? 0);
const res: ISyndicateSacrificeResponse = {
AffiliationTag: data.AffiliationTag,
InventoryChanges: {},
Level: data.SacrificeLevel,
LevelIncrease: level <= 0 ? 1 : level,
NewEpisodeReward: syndicate.Tag == "RadioLegionIntermission9Syndicate"
};
const manifest = ExportSyndicates[data.AffiliationTag];
let sacrifice: ISyndicateSacrifice | undefined;
let reward: string | undefined;
if (data.SacrificeLevel == 0) {
sacrifice = manifest.initiationSacrifice;
reward = manifest.initiationReward;
syndicate.Initiated = true;
} else {
sacrifice = manifest.titles?.find(x => x.level == data.SacrificeLevel)?.sacrifice;
}
if (sacrifice) {
res.InventoryChanges = { ...updateCurrency(inventory, sacrifice.credits, false) };
const miscItemChanges = sacrifice.items.map(x => ({
ItemType: x.ItemType,
ItemCount: x.ItemCount * -1
}));
addMiscItems(inventory, miscItemChanges);
res.InventoryChanges.MiscItems = miscItemChanges;
}
syndicate.Title ??= 0;
syndicate.Title += 1;
if (syndicate.Title > 0 && manifest.favours.length != 0) {
syndicate.FreeFavorsEarned ??= [];
if (!syndicate.FreeFavorsEarned.includes(syndicate.Title)) {
syndicate.FreeFavorsEarned.push(syndicate.Title);
const update = getJSONfromString(String(request.body)) as ISyndicateSacrifice;
let reply = {};
try {
if (typeof update !== "object") {
throw new Error("Invalid data format");
}
reply = await syndicateSacrifice(update, accountId);
} catch (err) {
console.error("Error parsing JSON data:", err);
}
if (reward) {
combineInventoryChanges(
res.InventoryChanges,
(await handleStoreItemAcquisition(reward, inventory)).InventoryChanges
);
}
await inventory.save();
response.json(res);
response.json(reply);
};
interface ISyndicateSacrificeRequest {
AffiliationTag: string;
SacrificeLevel: number;
AllowMultiple: boolean;
}
interface ISyndicateSacrificeResponse {
AffiliationTag: string;
Level: number;
LevelIncrease: number;
InventoryChanges: IInventoryChanges;
NewEpisodeReward: boolean;
}
export { syndicateSacrificeController };

View File

@ -1,72 +0,0 @@
import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { addMiscItems, getInventory, getStandingLimit, updateStandingLimit } from "@/src/services/inventoryService";
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import { IOid } from "@/src/types/commonTypes";
import { ExportSyndicates } from "warframe-public-export-plus";
import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper";
export const syndicateStandingBonusController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const request = JSON.parse(String(req.body)) as ISyndicateStandingBonusRequest;
const syndicateMeta = ExportSyndicates[request.Operation.AffiliationTag];
let gainedStanding = 0;
request.Operation.Items.forEach(item => {
const medallion = (syndicateMeta.medallions ?? []).find(medallion => medallion.itemType == item.ItemType);
if (medallion) {
gainedStanding += medallion.standing * item.ItemCount;
}
item.ItemCount *= -1;
});
const inventory = await getInventory(accountId);
addMiscItems(inventory, request.Operation.Items);
let syndicate = inventory.Affiliations.find(x => x.Tag == request.Operation.AffiliationTag);
if (!syndicate) {
syndicate =
inventory.Affiliations[
inventory.Affiliations.push({ Tag: request.Operation.AffiliationTag, Standing: 0 }) - 1
];
}
const max = getMaxStanding(syndicateMeta, syndicate.Title ?? 0);
if (syndicate.Standing + gainedStanding > max) {
gainedStanding = max - syndicate.Standing;
}
if (syndicateMeta.medallionsCappedByDailyLimit) {
if (gainedStanding > getStandingLimit(inventory, syndicateMeta.dailyLimitBin)) {
gainedStanding = getStandingLimit(inventory, syndicateMeta.dailyLimitBin);
}
updateStandingLimit(inventory, syndicateMeta.dailyLimitBin, gainedStanding);
}
syndicate.Standing += gainedStanding;
await inventory.save();
res.json({
InventoryChanges: {
MiscItems: request.Operation.Items
},
AffiliationMods: [
{
Tag: request.Operation.AffiliationTag,
Standing: gainedStanding
}
]
});
};
interface ISyndicateStandingBonusRequest {
Operation: {
AffiliationTag: string;
AlternateBonusReward: ""; // ???
Items: IMiscItem[];
};
ModularWeaponId: IOid; // Seems to just be "000000000000000000000000", also note there's a "Category" query field
}

View File

@ -2,20 +2,18 @@ import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService";
import { ITaunt } from "@/src/types/inventoryTypes/inventoryTypes";
import { logger } from "@/src/utils/logger";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const tauntHistoryController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const clientTaunt = JSON.parse(String(req.body)) as ITaunt;
logger.debug(`updating taunt ${clientTaunt.node} to state ${clientTaunt.state}`);
inventory.TauntHistory ??= [];
const taunt = inventory.TauntHistory.find(x => x.node == clientTaunt.node);
if (taunt) {
taunt.state = clientTaunt.state;
} else {
if (req.body !== undefined) {
const clientTaunt = JSON.parse(String(req.body)) as ITaunt;
inventory.TauntHistory ??= [];
inventory.TauntHistory.push(clientTaunt);
await inventory.save();
res.end();
} else {
res.json({});
}
await inventory.save();
res.end();
};

View File

@ -1,30 +1,24 @@
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getInventory } from "@/src/services/inventoryService";
import { IMongoDate } from "@/src/types/commonTypes";
import { RequestHandler } from "express";
import { unixTimesInMs } from "@/src/constants/timeConstants";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
interface ITrainingResultsRequest {
numLevelsGained: number;
}
interface ITrainingResultsResponse {
NewTrainingDate: IMongoDate;
NewLevel: number;
InventoryChanges: IInventoryChanges;
InventoryChanges: any[];
}
// eslint-disable-next-line @typescript-eslint/no-misused-promises
const trainingResultController: RequestHandler = async (req, res): Promise<void> => {
const accountId = await getAccountIdForRequest(req);
const trainingResults = getJSONfromString<ITrainingResultsRequest>(String(req.body));
const numLevelsGained = parseInt(req.query.numLevelsGained as string);
const inventory = await getInventory(accountId);
console.log(req.query);
inventory.TrainingDate = new Date(Date.now() + unixTimesInMs.day);
if (trainingResults.numLevelsGained == 1) {
inventory.TrainingDate = new Date(Date.now() + unixTimesInMs.hour * 23);
if (numLevelsGained == 1) {
inventory.PlayerLevel += 1;
}
@ -34,8 +28,8 @@ const trainingResultController: RequestHandler = async (req, res): Promise<void>
NewTrainingDate: {
$date: { $numberLong: changedinventory.TrainingDate.getTime().toString() }
},
NewLevel: trainingResults.numLevelsGained == 1 ? changedinventory.PlayerLevel : inventory.PlayerLevel,
InventoryChanges: {}
NewLevel: numLevelsGained == 1 ? changedinventory.PlayerLevel : inventory.PlayerLevel,
InventoryChanges: []
} satisfies ITrainingResultsResponse);
};

View File

@ -1,12 +0,0 @@
import { RequestHandler } from "express";
import { updateShipFeature } from "@/src/services/personalRoomsService";
import { IUnlockShipFeatureRequest } from "@/src/types/requestTypes";
import { parseString } from "@/src/helpers/general";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const unlockShipFeatureController: RequestHandler = async (req, res) => {
const accountId = parseString(req.query.accountId);
const shipFeatureRequest = JSON.parse((req.body as string).toString()) as IUnlockShipFeatureRequest;
await updateShipFeature(accountId, shipFeatureRequest.Feature);
res.send([]);
};

View File

@ -4,8 +4,9 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
import { updateChallengeProgress } from "@/src/services/inventoryService";
import { IUpdateChallengeProgressRequest } from "@/src/types/requestTypes";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
const updateChallengeProgressController: RequestHandler = async (req, res) => {
const payload = getJSONfromString<IUpdateChallengeProgressRequest>(String(req.body));
const payload = getJSONfromString(String(req.body)) as IUpdateChallengeProgressRequest;
const accountId = await getAccountIdForRequest(req);
await updateChallengeProgress(payload, accountId);

View File

@ -0,0 +1,156 @@
import { getAccountIdForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
import { missionInventoryUpdate } from "@/src/services/inventoryService";
import { combineRewardAndLootInventory } from "@/src/services/missionInventoryUpdateService";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { IMissionInventoryUpdateRequest } from "@/src/types/requestTypes";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const updateInventoryController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const lootInventory = getJSONfromString(req.body as string) as IMissionInventoryUpdateRequest;
const { combinedInventoryChanges, TotalCredits, CreditsBonus, MissionCredits } = combineRewardAndLootInventory(
lootInventory,
lootInventory
);
await missionInventoryUpdate(combinedInventoryChanges, accountId);
res.json({
// InventoryJson, // this part will reset game data and missions will be locked
combinedInventoryChanges,
TotalCredits,
CreditsBonus,
MissionCredits
});
};
/*
{
"LongGuns" : [
{
"ItemType" : "",
"ItemId" : {
"$id" : ""
},
"XP" : 882,
"UpgradeVer" : 0,
"UnlockLevel" : 0,
"ExtraCapacity" : 4,
"ExtraRemaining" : 4
}
],
"Pistols" : [
{
"ItemType" : "",
"ItemId" : {
"$id" : ""
},
"XP" : 0,
"UpgradeVer" : 0,
"UnlockLevel" : 0,
"ExtraCapacity" : 4,
"ExtraRemaining" : 4
}
],
"Suits" : [
{
"ItemType" : "",
"ItemId" : {
"$id" : ""
},
"XP" : 982,
"UpgradeVer" : 101,
"UnlockLevel" : 0,
"ExtraCapacity" : 4,
"ExtraRemaining" : 4
}
],
"Melee" : [
{
"ItemType" : "",
"ItemId" : {
"$id" : ""
},
"XP" : 0,
"UpgradeVer" : 0,
"UnlockLevel" : 0,
"ExtraCapacity" : 4,
"ExtraRemaining" : 4
}
],
"WeaponSkins" : [],
"Upgrades" : [],
"Boosters" : [],
"Robotics" : [],
"Consumables" : [],
"FlavourItems" : [],
"MiscItems" : [],
"Cards" : [],
"Recipes" : [],
"XPInfo" : [],
"Sentinels" : [],
"SentinelWeapons" : [],
"SuitBin" : {
"Slots" : 0,
"Extra" : 0
},
"WeaponBin" : {
"Slots" : 0,
"Extra" : 0
},
"MiscBin" : {
"Slots" : 0,
"Extra" : 0
},
"SentinelBin" : {
"Slots" : 0,
"Extra" : 0
},
"RegularCredits" : 1304,
"PremiumCredits" : 0,
"PlayerXP" : 784,
"AdditionalPlayerXP" : 0,
"Rating" : 15,
"PlayerLevel" : 0,
"TrainingDate" : {
"sec" : "",
"usec" : ""
},
"AliveTime" : 193.78572,
"Missions" : {
"Tag" : "SolNode103",
"Completes" : 1,
"BestRating" : 0.2
},
"AssignedMissions" : [],
"CompletedAlerts" : [],
"DeathMarks" : [],
"MissionReport" : {
"HostId" : "",
"MishStartTime" : "1725359860",
"MishName" : "SolNode103",
"PlayerReport" : {
"ReporterId" : "",
"FullReport" : true,
"PlayerMishInfos" : [
{
"Pid" : "",
"Creds" : 304,
"CredBonus" : 1000,
"Xp" : 784,
"XpBonus" : 0,
"SuitXpBonus" : 590,
"PistolXpBonus" : 0,
"RfileXpBonus" : 490,
"MeleeXpBonus" : 0,
"SentnlXPBonus" : 0,
"SentnlWepXpBonus" : 0,
"Rating" : 0.2,
"Upgrades" : []
}
]
}
}
}
*/

View File

@ -1,47 +0,0 @@
import { RequestHandler } from "express";
import { parseString } from "@/src/helpers/general";
import { logger } from "@/src/utils/logger";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { updateQuestKey, IUpdateQuestRequest } from "@/src/services/questService";
import { getQuestCompletionItems } from "@/src/services/itemDataService";
import { addItems, getInventory } from "@/src/services/inventoryService";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const updateQuestController: RequestHandler = async (req, res) => {
const accountId = parseString(req.query.accountId);
const updateQuestRequest = getJSONfromString<IUpdateQuestRequest>((req.body as string).toString());
// updates should be made only to one quest key per request
if (updateQuestRequest.QuestKeys.length > 1) {
throw new Error(`quest keys array should only have 1 item, but has ${updateQuestRequest.QuestKeys.length}`);
}
const inventory = await getInventory(accountId);
const updateQuestResponse: { CustomData?: string; InventoryChanges?: IInventoryChanges; MissionRewards: [] } = {
MissionRewards: []
};
updateQuestKey(inventory, updateQuestRequest.QuestKeys);
if (updateQuestRequest.QuestKeys[0].Completed) {
logger.debug(`completed quest ${updateQuestRequest.QuestKeys[0].ItemType} `);
const questKeyName = updateQuestRequest.QuestKeys[0].ItemType;
const questCompletionItems = getQuestCompletionItems(questKeyName);
logger.debug(`quest completion items`, questCompletionItems);
if (questCompletionItems) {
const inventoryChanges = await addItems(inventory, questCompletionItems);
updateQuestResponse.InventoryChanges = inventoryChanges;
}
inventory.ActiveQuest = "";
}
//TODO: might need to parse the custom data and add the associated items to inventory
if (updateQuestRequest.QuestKeys[0].CustomData) {
updateQuestResponse.CustomData = updateQuestRequest.QuestKeys[0].CustomData;
}
await inventory.save();
res.send(updateQuestResponse);
};

View File

@ -5,8 +5,8 @@ const updateSessionGetController: RequestHandler = (_req, res) => {
res.json({});
};
const updateSessionPostController: RequestHandler = (_req, res) => {
//console.log("UpdateSessions POST Request:", JSON.parse(String(_req.body)));
//console.log("ReqID:", _req.query.sessionId as string);
console.log("UpdateSessions POST Request:", JSON.parse(String(_req.body)));
console.log("ReqID:", _req.query.sessionId as string);
updateSession(_req.query.sessionId as string, String(_req.body));
res.json({});
};

View File

@ -4,12 +4,13 @@ import { updateTheme } from "@/src/services/inventoryService";
import { IThemeUpdateRequest } from "@/src/types/requestTypes";
import { RequestHandler } from "express";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
const updateThemeController: RequestHandler = async (request, response) => {
const accountId = await getAccountIdForRequest(request);
const body = String(request.body);
try {
const json = getJSONfromString<IThemeUpdateRequest>(body);
const json = getJSONfromString(body) as IThemeUpdateRequest;
if (typeof json !== "object") {
throw new Error("Invalid data format");
}

View File

@ -1,171 +1,34 @@
import { RequestHandler } from "express";
import { IUpgradesRequest } from "@/src/types/requestTypes";
import {
ArtifactPolarity,
IEquipmentDatabase,
EquipmentFeatures,
IAbilityOverride
} from "@/src/types/inventoryTypes/commonInventoryTypes";
import { IInventoryClient, IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { addMiscItems, addRecipes, getInventory, updateCurrency } from "@/src/services/inventoryService";
import { getRecipeByResult } from "@/src/services/itemDataService";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { addInfestedFoundryXP } from "./infestedFoundryController";
import { getInventory } from "@/src/services/inventoryService";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const upgradesController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const payload = JSON.parse(String(req.body)) as IUpgradesRequest;
const inventory = await getInventory(accountId);
const inventoryChanges: IInventoryChanges = {};
for (const operation of payload.Operations) {
if (
operation.UpgradeRequirement == "/Lotus/Types/Items/MiscItems/ModSlotUnlocker" ||
operation.UpgradeRequirement == "/Lotus/Types/Items/MiscItems/CustomizationSlotUnlocker"
) {
updateCurrency(inventory, 10, true);
} else {
addMiscItems(inventory, [
{
ItemType: operation.UpgradeRequirement,
ItemCount: -1
} satisfies IMiscItem
]);
console.log(req.body);
for (const item of payload.UpgradesToAttach) {
for (const upgrade of inventory.Upgrades) {
if (upgrade._id?.toString() == item.ItemId.$id) {
upgrade.UpgradeFingerprint = item.UpgradeFingerprint;
upgrade.Slot = item.Slot;
upgrade.ParentId = payload.Weapon.ItemId;
}
}
if (operation.OperationType == "UOT_ABILITY_OVERRIDE") {
console.assert(payload.ItemCategory == "Suits");
const suit = inventory.Suits.id(payload.ItemId.$oid)!;
let newAbilityOverride: IAbilityOverride | undefined;
let totalPercentagePointsConsumed = 0;
if (operation.UpgradeRequirement != "") {
newAbilityOverride = {
Ability: operation.UpgradeRequirement,
Index: operation.PolarizeSlot
};
const recipe = getRecipeByResult(operation.UpgradeRequirement)!;
for (const ingredient of recipe.ingredients) {
totalPercentagePointsConsumed += ingredient.ItemCount / 10;
inventory.InfestedFoundry!.Resources!.find(x => x.ItemType == ingredient.ItemType)!.Count -=
ingredient.ItemCount;
}
}
for (const entry of operation.PolarityRemap) {
suit.Configs[entry.Slot] ??= {};
suit.Configs[entry.Slot].AbilityOverride = newAbilityOverride;
}
const recipeChanges = addInfestedFoundryXP(inventory.InfestedFoundry!, totalPercentagePointsConsumed * 8);
addRecipes(inventory, recipeChanges);
inventoryChanges.Recipes = recipeChanges;
inventoryChanges.InfestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry;
} else
switch (operation.UpgradeRequirement) {
case "/Lotus/Types/Items/MiscItems/OrokinReactor":
case "/Lotus/Types/Items/MiscItems/OrokinCatalyst":
for (const item of inventory[payload.ItemCategory]) {
if (item._id.toString() == payload.ItemId.$oid) {
item.Features ??= 0;
item.Features |= EquipmentFeatures.DOUBLE_CAPACITY;
break;
}
}
break;
case "/Lotus/Types/Items/MiscItems/UtilityUnlocker":
case "/Lotus/Types/Items/MiscItems/WeaponUtilityUnlocker":
for (const item of inventory[payload.ItemCategory]) {
if (item._id.toString() == payload.ItemId.$oid) {
item.Features ??= 0;
item.Features |= EquipmentFeatures.UTILITY_SLOT;
break;
}
}
break;
case "/Lotus/Types/Items/MiscItems/HeavyWeaponCatalyst":
console.assert(payload.ItemCategory == "SpaceGuns");
for (const item of inventory[payload.ItemCategory]) {
if (item._id.toString() == payload.ItemId.$oid) {
item.Features ??= 0;
item.Features |= EquipmentFeatures.GRAVIMAG_INSTALLED;
break;
}
}
break;
case "/Lotus/Types/Items/MiscItems/WeaponPrimaryArcaneUnlocker":
case "/Lotus/Types/Items/MiscItems/WeaponSecondaryArcaneUnlocker":
case "/Lotus/Types/Items/MiscItems/WeaponMeleeArcaneUnlocker":
case "/Lotus/Types/Items/MiscItems/WeaponAmpArcaneUnlocker":
for (const item of inventory[payload.ItemCategory]) {
if (item._id.toString() == payload.ItemId.$oid) {
item.Features ??= 0;
item.Features |= EquipmentFeatures.ARCANE_SLOT;
break;
}
}
break;
case "/Lotus/Types/Items/MiscItems/Forma":
case "/Lotus/Types/Items/MiscItems/FormaUmbra":
case "/Lotus/Types/Items/MiscItems/FormaAura":
case "/Lotus/Types/Items/MiscItems/FormaStance":
for (const item of inventory[payload.ItemCategory]) {
if (item._id.toString() == payload.ItemId.$oid) {
item.XP = 0;
setSlotPolarity(item, operation.PolarizeSlot, operation.PolarizeValue);
item.Polarized ??= 0;
item.Polarized += 1;
break;
}
}
break;
case "/Lotus/Types/Items/MiscItems/ModSlotUnlocker":
for (const item of inventory[payload.ItemCategory]) {
if (item._id.toString() == payload.ItemId.$oid) {
item.ModSlotPurchases ??= 0;
item.ModSlotPurchases += 1;
break;
}
}
break;
case "/Lotus/Types/Items/MiscItems/CustomizationSlotUnlocker":
for (const item of inventory[payload.ItemCategory]) {
if (item._id.toString() == payload.ItemId.$oid) {
item.CustomizationSlotPurchases ??= 0;
item.CustomizationSlotPurchases += 1;
break;
}
}
break;
case "":
console.assert(operation.OperationType == "UOT_SWAP_POLARITY");
for (const item of inventory[payload.ItemCategory]) {
if (item._id.toString() == payload.ItemId.$oid) {
for (let i = 0; i != operation.PolarityRemap.length; ++i) {
if (operation.PolarityRemap[i].Slot != i) {
setSlotPolarity(item, i, operation.PolarityRemap[i].Value);
}
}
break;
}
}
break;
default:
throw new Error("Unsupported upgrade: " + operation.UpgradeRequirement);
}
}
for (const item of payload.UpgradesToDetach) {
for (const upgrade of inventory.Upgrades) {
if (upgrade._id?.toString() == item.ItemId.$id) {
upgrade.UpgradeFingerprint = undefined;
upgrade.Slot = undefined;
upgrade.ParentId = undefined;
}
}
}
await inventory.save();
res.json({ InventoryChanges: inventoryChanges });
};
const setSlotPolarity = (item: IEquipmentDatabase, slot: number, polarity: ArtifactPolarity): void => {
item.Polarity ??= [];
const entry = item.Polarity.find(entry => entry.Slot == slot);
if (entry) {
entry.Value = polarity;
} else {
item.Polarity.push({ Slot: slot, Value: polarity });
}
res.json({});
};

View File

@ -1,17 +0,0 @@
import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService";
export const addCurrencyController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const request = req.body as IAddCurrencyRequest;
inventory[request.currency] += request.delta;
await inventory.save();
res.end();
};
interface IAddCurrencyRequest {
currency: "RegularCredits" | "PremiumCredits" | "FusionPoints" | "PrimeTokens";
delta: number;
}

View File

@ -0,0 +1,28 @@
import { getAccountIdForRequest } from "@/src/services/loginService";
import { ItemType, toAddItemRequest } from "@/src/helpers/customHelpers/addItemHelpers";
import { getWeaponType } from "@/src/services/itemDataService";
import { addPowerSuit, addEquipment } from "@/src/services/inventoryService";
import { RequestHandler } from "express";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
const addItemController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const request = toAddItemRequest(req.body);
switch (request.type) {
case ItemType.Powersuit:
const powersuit = await addPowerSuit(request.InternalName, accountId);
res.json(powersuit);
return;
case ItemType.Weapon:
const weaponType = getWeaponType(request.InternalName);
const weapon = await addEquipment(weaponType, request.InternalName, accountId);
res.json(weapon);
break;
default:
res.status(400).json({ error: "something went wrong" });
break;
}
};
export { addItemController };

View File

@ -1,19 +0,0 @@
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory, addItem } from "@/src/services/inventoryService";
import { RequestHandler } from "express";
export const addItemsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const requests = req.body as IAddItemRequest[];
const inventory = await getInventory(accountId);
for (const request of requests) {
await addItem(inventory, request.ItemType, request.ItemCount);
}
await inventory.save();
res.end();
};
interface IAddItemRequest {
ItemType: string;
ItemCount: number;
}

View File

@ -1,31 +0,0 @@
import { addGearExpByCategory, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import { RequestHandler } from "express";
import { ExportMisc } from "warframe-public-export-plus";
export const addXpController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const request = req.body as IAddXpRequest;
for (const [category, gear] of Object.entries(request)) {
for (const clientItem of gear) {
const dbItem = inventory[category as TEquipmentKey].id(clientItem.ItemId.$oid);
if (dbItem) {
if (dbItem.ItemType in ExportMisc.uniqueLevelCaps) {
if ((dbItem.Polarized ?? 0) < 5) {
dbItem.Polarized = 5;
}
}
}
}
addGearExpByCategory(inventory, gear, category as TEquipmentKey);
}
await inventory.save();
res.end();
};
type IAddXpRequest = {
[_ in TEquipmentKey]: IEquipmentClient[];
};

View File

@ -1,16 +1,15 @@
import { toCreateAccount, toDatabaseAccount } from "@/src/helpers/customHelpers/customHelpers";
import { createAccount, isNameTaken } from "@/src/services/loginService";
import { createAccount } from "@/src/services/loginService";
import { RequestHandler } from "express";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
const createAccountController: RequestHandler = async (req, res) => {
const createAccountData = toCreateAccount(req.body);
if (await isNameTaken(createAccountData.DisplayName)) {
res.status(409).json("Name already in use");
} else {
const databaseAccount = toDatabaseAccount(createAccountData);
const account = await createAccount(databaseAccount);
res.json(account);
}
const databaseAccount = toDatabaseAccount(createAccountData);
const account = await createAccount(databaseAccount);
res.json(account);
};
export { createAccountController };

View File

@ -1,14 +0,0 @@
import { createMessage, IMessageCreationTemplate } from "@/src/services/inboxService";
import { RequestHandler } from "express";
export const createMessageController: RequestHandler = async (req, res) => {
const message = req.body as (IMessageCreationTemplate & { ownerId: string })[] | undefined;
if (!message) {
res.status(400).send("No message provided");
return;
}
const savedMessages = await createMessage(message[0].ownerId, message);
res.json(savedMessages);
};

View File

@ -1,23 +0,0 @@
import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { Account } from "@/src/models/loginModel";
import { Inbox } from "@/src/models/inboxModel";
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
import { PersonalRooms } from "@/src/models/personalRoomsModel";
import { Ship } from "@/src/models/shipModel";
import { Stats } from "@/src/models/statsModel";
export const deleteAccountController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
await Promise.all([
Account.deleteOne({ _id: accountId }),
Inbox.deleteMany({ ownerId: accountId }),
Inventory.deleteOne({ accountOwnerId: accountId }),
Loadout.deleteOne({ loadoutOwnerId: accountId }),
PersonalRooms.deleteOne({ personalRoomsOwnerId: accountId }),
Ship.deleteOne({ ShipOwnerId: accountId }),
Stats.deleteOne({ accountOwnerId: accountId })
]);
res.end();
};

View File

@ -1,14 +1,8 @@
import { RequestHandler } from "express";
import { config } from "@/src/services/configService";
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
const getConfigDataController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
if (isAdministrator(account)) {
res.json(config);
} else {
res.status(401).end();
}
const getConfigDataController: RequestHandler = (_req, res) => {
res.json(config);
};
export { getConfigDataController };

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