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
296 changed files with 23215 additions and 101030 deletions

View File

@ -1,19 +0,0 @@
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
language: "en-US"
early_access: false
reviews:
profile: "chill"
request_changes_workflow: false
changed_files_summary: false
high_level_summary: false
poem: false
review_status: true
commit_status: false
collapse_walkthrough: false
sequence_diagrams: false
related_prs: false
auto_review:
enabled: true
drafts: false
chat:
auto_reply: true

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

View File

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

View File

@ -1,25 +0,0 @@
name: Build Docker image
on:
push:
branches:
- main
jobs:
docker:
if: github.repository == 'OpenWF/SpaceNinjaServer'
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 }}

3
.gitignore vendored
View File

@ -16,6 +16,3 @@ yarn.lock
# MongoDB VSCode extension playground scripts # MongoDB VSCode extension playground scripts
/database_scripts /database_scripts
# Default Docker directory
/docker-data

View File

@ -1,3 +1,2 @@
static/webui/libs/ static/webui/libs/
*.html *.html
*.md

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 EXPOSE 27017
ENV APP_MY_ADDRESS=localhost
ENV APP_HTTP_PORT=80
ENV APP_HTTPS_PORT=443
ENV APP_AUTO_CREATE_ACCOUNT=true
ENV APP_SKIP_STORY_MODE_CHOICE=true
ENV APP_SKIP_TUTORIAL=true
ENV APP_SKIP_ALL_DIALOGUE=true
ENV APP_UNLOCK_ALL_SCANS=true
ENV APP_UNLOCK_ALL_MISSIONS=true
ENV APP_UNLOCK_ALL_QUESTS=true
ENV APP_COMPLETE_ALL_QUESTS=true
ENV APP_INFINITE_RESOURCES=true
ENV APP_UNLOCK_ALL_SHIP_FEATURES=true
ENV APP_UNLOCK_ALL_SHIP_DECORATIONS=true
ENV APP_UNLOCK_ALL_FLAVOUR_ITEMS=true
ENV APP_UNLOCK_ALL_SKINS=true
ENV APP_UNIVERSAL_POLARITY_EVERYWHERE=true
ENV APP_SPOOF_MASTERY_RANK=-1
RUN apk add --no-cache bash sed wget jq CMD ["mongod"]
COPY . /app
WORKDIR /app
ENTRYPOINT ["/app/docker-entrypoint.sh"]

View File

@ -1,14 +1,3 @@
# Space Ninja Server # Space Ninja Server
More information for the moment here: [https://discord.gg/PNNZ3asUuY](https://discord.gg/PNNZ3asUuY) More information for the moment here: [https://discord.gg/PNNZ3asUuY](https://discord.gg/PNNZ3asUuY)
## Project Status
This project is in active development at <https://onlyg.it/OpenWF/SpaceNinjaServer>.
To get an idea of what functionality you can expect to be missing [have a look through the issues](https://onlyg.it/OpenWF/SpaceNinjaServer/issues?q=&type=all&state=open&labels=-4%2C-10&milestone=0&assignee=0&poster=). However, many things have been implemented and *should* work as expected. Please open an issue for anything where that's not the case and/or the server is reporting errors.
## config.json
- `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,26 +0,0 @@
@echo off
echo Updating SpaceNinjaServer...
git config remote.origin.url https://openwf.io/SpaceNinjaServer.git
git fetch --prune
git stash
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 --omit=dev
call npm run build
if %errorlevel% == 0 (
call npm run start
echo SpaceNinjaServer seems to have crashed.
)
:a
pause > nul
goto a

View File

@ -1,47 +1,26 @@
{ {
"mongodbUrl": "mongodb://127.0.0.1:27017/openWF", "mongodbUrl": "mongodb://127.0.0.1:27017/openWF_2013",
"logger": { "logger": {
"files": true, "files": true,
"level": "trace" "level": "trace",
"__valid_levels": "fatal, error, warn, info, http, debug, trace"
}, },
"myAddress": "localhost", "myAddress": "localhost",
"httpPort": 80, "httpPort": 80,
"httpsPort": 443, "httpsPort": 443,
"NRS": ["localhost"],
"administratorNames": [],
"autoCreateAccount": true, "autoCreateAccount": true,
"skipTutorial": false, "skipStoryModeChoice": true,
"skipAllDialogue": false, "skipTutorial": true,
"unlockAllScans": false, "skipAllDialogue": true,
"unlockAllMissions": false, "unlockAllScans": true,
"infiniteCredits": false, "unlockAllMissions": true,
"infinitePlatinum": false, "unlockAllQuests": true,
"infiniteEndo": false, "completeAllQuests": false,
"infiniteRegalAya": false, "infiniteResources": true,
"infiniteHelminthMaterials": false, "unlockAllShipFeatures": true,
"unlockAllShipFeatures": false, "unlockAllShipDecorations": true,
"unlockAllShipDecorations": false, "unlockAllFlavourItems": true,
"unlockAllFlavourItems": false, "unlockAllSkins": true,
"unlockAllSkins": false, "universalPolarityEverywhere": true,
"unlockAllCapturaScenes": false, "spoofMasteryRank": -1
"universalPolarityEverywhere": false,
"unlockDoubleCapacityPotatoesEverywhere": false,
"unlockExilusEverywhere": false,
"unlockArcanesEverywhere": false,
"noDailyStandingLimits": false,
"noArgonCrystalDecay": false,
"noVendorPurchaseLimits": true,
"instantResourceExtractorDrones": false,
"noDojoRoomBuildStage": false,
"fastDojoRoomDestruction": false,
"noDojoResearchCosts": false,
"noDojoResearchTime": false,
"fastClanAscension": false,
"spoofMasteryRank": -1,
"events": {
"creditBoost": false,
"affinityBoost": false,
"resourceBoost": false,
"starDays": true
}
} }

View File

@ -1,43 +1,24 @@
version: "3.9"
services: 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: mongodb:
image: docker.io/library/mongo:8.0.0-noble container_name: mongodb
image: mongodb
restart: always
build:
context: .
dockerfile: Dockerfile
target: base
environment: environment:
MONGO_INITDB_ROOT_USERNAME: openwfagent MONGO_INITDB_ROOT_USERNAME: ${DATABASE_USERNAME}
MONGO_INITDB_ROOT_PASSWORD: spaceninjaserver MONGO_INITDB_ROOT_PASSWORD: ${DATABASE_PASSWORD}
volumes: ports:
- ./docker-data/database:/data/db - ${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

2216
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,46 +0,0 @@
// Based on https://onlyg.it/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,44 +1,65 @@
import express from "express"; import express from "express";
import bodyParser from "body-parser";
import { unknownEndpointHandler } from "@/src/middleware/middleware"; import { unknownEndpointHandler } from "@/src/middleware/middleware";
import { requestLogger } from "@/src/middleware/morgenMiddleware"; import { requestLogger } from "@/src/middleware/morgenMiddleware";
import { errorHandler } from "@/src/middleware/errorHandler";
import { apiRouter } from "@/src/routes/api"; import { apiRouter } from "@/src/routes/api";
//import { testRouter } from "@/src/routes/test";
import { cacheRouter } from "@/src/routes/cache"; 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 { customRouter } from "@/src/routes/custom";
import { dynamicController } from "@/src/routes/dynamic"; import { dynamicController } from "@/src/routes/dynamic";
import { payRouter } from "@/src/routes/pay";
import { statsRouter } from "@/src/routes/stats"; import { statsRouter } from "@/src/routes/stats";
import { webuiRouter } from "@/src/routes/webui"; 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(); const app = express();
app.use((req, _res, next) => { app.use(function (req, _res, next) {
// 38.5.0 introduced "ezip" for encrypted body blobs. const buffer: Buffer[] = [];
// The bootstrapper decrypts it for us but having an unsupported Content-Encoding here would still be an issue for Express, so removing it. req.on("data", function (chunk: Buffer) {
if (req.headers["content-encoding"] == "ezip") { if (chunk !== undefined && chunk.length > 2 && chunk[0] == 0x1f && chunk[1] == 0x8b) {
req.headers["content-encoding"] = undefined; 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(); next();
}); });
});
});
app.use(bodyParser.raw()); app.use(bodyParser.raw());
app.use(express.json({ limit: "4mb" })); app.use(express.json());
app.use(bodyParser.text()); app.use(bodyParser.text());
app.use(requestLogger); app.use(requestLogger);
//app.use(requestLogger);
app.use("/api", apiRouter); app.use("/api", apiRouter);
//app.use("/test", testRouter);
app.use("/", cacheRouter); app.use("/", cacheRouter);
app.use("/custom", customRouter); app.use("/custom", customRouter);
app.use("/dynamic", dynamicController);
app.use("/:id/dynamic", dynamicController); app.use("/:id/dynamic", dynamicController);
app.use("/pay", payRouter);
app.post("/pay/steamPacks.php", steamPacksController);
app.use("/stats", statsRouter); app.use("/stats", statsRouter);
app.use("/", webuiRouter); app.use("/", webuiRouter);
app.use(unknownEndpointHandler); app.use(unknownEndpointHandler);
app.use(errorHandler);
//app.use(errorHandler)
export { app }; export { app };

View File

@ -1,11 +0,0 @@
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
export const abandonLibraryDailyTaskController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
inventory.LibraryActiveDailyTaskInfo = undefined;
await inventory.save();
res.status(200).end();
};

View File

@ -1,46 +0,0 @@
import {
getDojoClient,
getGuildForRequestEx,
hasAccessToDojo,
hasGuildPermission,
removeDojoDeco,
removeDojoRoom
} from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes";
import { RequestHandler } from "express";
export const abortDojoComponentController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "GuildId LevelKeys");
const guild = await getGuildForRequestEx(req, inventory);
const request = JSON.parse(String(req.body)) as IAbortDojoComponentRequest;
if (
!hasAccessToDojo(inventory) ||
!(await hasGuildPermission(
guild,
accountId,
request.DecoId ? GuildPermission.Decorator : GuildPermission.Architect
))
) {
res.json({ DojoRequestStatus: -1 });
return;
}
if (request.DecoId) {
removeDojoDeco(guild, request.ComponentId, request.DecoId);
} else {
removeDojoRoom(guild, request.ComponentId);
}
await guild.save();
res.json(await getDojoClient(guild, 0, request.ComponentId));
};
interface IAbortDojoComponentRequest {
DecoType?: string;
ComponentId: string;
DecoId?: string;
}

View File

@ -1,21 +0,0 @@
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes";
import { RequestHandler } from "express";
export const abortDojoComponentDestructionController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "GuildId LevelKeys");
const guild = await getGuildForRequestEx(req, inventory);
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Architect))) {
res.json({ DojoRequestStatus: -1 });
return;
}
const componentId = req.query.componentId as string;
guild.DojoComponents.id(componentId)!.DestructionTime = undefined;
await guild.save();
res.json(await getDojoClient(guild, 0, componentId));
};

View File

@ -1,40 +0,0 @@
import { toOid } from "@/src/helpers/inventoryHelpers";
import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "@/src/helpers/rivenHelper";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { addMods, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getRandomElement } from "@/src/services/rngService";
import { RequestHandler } from "express";
import { ExportUpgrades } from "warframe-public-export-plus";
export const activateRandomModController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const request = getJSONfromString<IActiveRandomModRequest>(String(req.body));
addMods(inventory, [
{
ItemType: request.ItemType,
ItemCount: -1
}
]);
const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType]);
const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
const upgradeIndex =
inventory.Upgrades.push({
ItemType: rivenType,
UpgradeFingerprint: JSON.stringify(fingerprint)
}) - 1;
await inventory.save();
// For some reason, in this response, the UpgradeFingerprint is simply a nested object and not a string
res.json({
NewMod: {
UpgradeFingerprint: fingerprint,
ItemType: rivenType,
ItemId: toOid(inventory.Upgrades[upgradeIndex]._id)
}
});
};
interface IActiveRandomModRequest {
ItemType: string;
}

View File

@ -1,25 +1,17 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { IUpdateGlyphRequest } from "@/src/types/requestTypes";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { Inventory } from "@/src/models/inventoryModels/inventoryModel"; import { getInventory } from "@/src/services/inventoryService";
export const addFriendImageController: RequestHandler = async (req, res) => { // eslint-disable-next-line @typescript-eslint/no-misused-promises
const addFriendImageController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); 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);
await Inventory.updateOne( inventory.ActiveAvatarImageType = json.AvatarImageType;
{ await inventory.save();
accountOwnerId: accountId
},
{
ActiveAvatarImageType: json.AvatarImageType
}
);
res.json({}); res.json({});
}; };
interface IUpdateGlyphRequest { export { addFriendImageController };
AvatarImageType: string;
AvatarImage: string;
}

View File

@ -1,77 +0,0 @@
import { Guild, GuildMember } from "@/src/models/guildModel";
import { Account } from "@/src/models/loginModel";
import { fillInInventoryDataForGuildMember, hasGuildPermission } from "@/src/services/guildService";
import { createMessage } from "@/src/services/inboxService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
import { IOid } from "@/src/types/commonTypes";
import { GuildPermission, IGuildMemberClient } from "@/src/types/guildTypes";
import { RequestHandler } from "express";
import { ExportFlavour } from "warframe-public-export-plus";
export const addToGuildController: RequestHandler = async (req, res) => {
const payload = JSON.parse(String(req.body)) as IAddToGuildRequest;
const account = await Account.findOne({ DisplayName: payload.UserName });
if (!account) {
res.status(400).json("Username does not exist");
return;
}
const guild = (await Guild.findOne({ _id: payload.GuildId.$oid }, "Name"))!;
const senderAccount = await getAccountForRequest(req);
if (!(await hasGuildPermission(guild, senderAccount._id.toString(), GuildPermission.Recruiter))) {
res.status(400).json("Invalid permission");
}
if (
await GuildMember.exists({
accountId: account._id,
guildId: payload.GuildId.$oid
})
) {
res.status(400).json("User already invited to clan");
return;
}
await GuildMember.insertOne({
accountId: account._id,
guildId: payload.GuildId.$oid,
status: 2 // outgoing invite
});
const senderInventory = await getInventory(senderAccount._id.toString(), "ActiveAvatarImageType");
await createMessage(account._id.toString(), [
{
sndr: getSuffixedName(senderAccount),
msg: "/Lotus/Language/Menu/Mailbox_ClanInvite_Body",
arg: [
{
Key: "clan",
Tag: guild.Name
}
],
sub: "/Lotus/Language/Menu/Mailbox_ClanInvite_Title",
icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
contextInfo: payload.GuildId.$oid,
highPriority: true,
acceptAction: "GUILD_INVITE",
declineAction: "GUILD_INVITE",
hasAccountAction: true
}
]);
const member: IGuildMemberClient = {
_id: { $oid: account._id.toString() },
DisplayName: account.DisplayName,
Rank: 7,
Status: 2
};
await fillInInventoryDataForGuildMember(member);
res.json({ NewMember: member });
};
interface IAddToGuildRequest {
UserName: string;
GuildId: IOid;
}

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,124 +0,0 @@
import { toOid } from "@/src/helpers/inventoryHelpers";
import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "@/src/helpers/rivenHelper";
import { addMiscItems, addMods, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getRandomElement, getRandomWeightedReward, getRandomWeightedRewardUc } from "@/src/services/rngService";
import { IOid } from "@/src/types/commonTypes";
import { RequestHandler } from "express";
import { ExportBoosterPacks, ExportUpgrades, TRarity } from "warframe-public-export-plus";
export const artifactTransmutationController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const payload = JSON.parse(String(req.body)) as IArtifactTransmutationRequest;
inventory.RegularCredits -= payload.Cost;
inventory.FusionPoints -= payload.FusionPointCost;
if (payload.RivenTransmute) {
addMiscItems(inventory, [
{
ItemType: "/Lotus/Types/Gameplay/Eidolon/Resources/SentientSecretItem",
ItemCount: -1
}
]);
payload.Consumed.forEach(upgrade => {
inventory.Upgrades.pull({ _id: upgrade.ItemId.$oid });
});
const rawRivenType = getRandomRawRivenType();
const rivenType = getRandomElement(rivenRawToRealWeighted[rawRivenType]);
const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
const upgradeIndex =
inventory.Upgrades.push({
ItemType: rivenType,
UpgradeFingerprint: JSON.stringify(fingerprint)
}) - 1;
await inventory.save();
res.json({
NewMods: [
{
ItemId: toOid(inventory.Upgrades[upgradeIndex]._id),
ItemType: rivenType,
UpgradeFingerprint: fingerprint
}
]
});
} else {
const counts: Record<TRarity, number> = {
COMMON: 0,
UNCOMMON: 0,
RARE: 0,
LEGENDARY: 0
};
payload.Consumed.forEach(upgrade => {
const meta = ExportUpgrades[upgrade.ItemType];
counts[meta.rarity] += upgrade.ItemCount;
addMods(inventory, [
{
ItemType: upgrade.ItemType,
ItemCount: upgrade.ItemCount * -1
}
]);
});
// Based on the table on https://wiki.warframe.com/w/Transmutation
const weights: Record<TRarity, number> = {
COMMON: counts.COMMON * 95 + counts.UNCOMMON * 15 + counts.RARE * 4,
UNCOMMON: counts.COMMON * 4 + counts.UNCOMMON * 80 + counts.RARE * 10,
RARE: counts.COMMON * 1 + counts.UNCOMMON * 5 + counts.RARE * 50,
LEGENDARY: 0
};
const options: { uniqueName: string; rarity: TRarity }[] = [];
Object.entries(ExportUpgrades).forEach(([uniqueName, upgrade]) => {
if (upgrade.canBeTransmutation) {
options.push({ uniqueName, rarity: upgrade.rarity });
}
});
const newModType = getRandomWeightedReward(options, weights)!.uniqueName;
addMods(inventory, [
{
ItemType: newModType,
ItemCount: 1
}
]);
await inventory.save();
res.json({
NewMods: [
{
ItemType: newModType,
ItemCount: 1
}
]
});
}
};
const getRandomRawRivenType = (): string => {
const pack = ExportBoosterPacks["/Lotus/Types/BoosterPacks/CalendarRivenPack"];
return getRandomWeightedRewardUc(pack.components, pack.rarityWeightsPerRoll[0])!.Item;
};
interface IArtifactTransmutationRequest {
Upgrade: IAgnosticUpgradeClient;
LevelDiff: number;
Consumed: IAgnosticUpgradeClient[];
Cost: number;
FusionPointCost: number;
RivenTransmute?: boolean;
}
interface IAgnosticUpgradeClient {
ItemType: string;
ItemId: IOid;
FromSKU: boolean;
UpgradeFingerprint: string;
PendingRerollFingerprint: string;
ItemCount: number;
LastAdded: IOid;
}

View File

@ -1,70 +1,22 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { upgradeMod } from "@/src/services/inventoryService";
import { IArtifactsRequest } from "@/src/types/requestTypes";
import { RequestHandler } from "express"; 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 accountId = await getAccountIdForRequest(req);
const artifactsData = getJSONfromString<IArtifactsRequest>(String(req.body));
const { Upgrade, LevelDiff, Cost, FusionPointCost } = artifactsData; try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call
const inventory = await getInventory(accountId); const artifactsData = getJSONfromString(req.body.toString()) as IArtifactsRequest;
const { Upgrades } = inventory; // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const { ItemType, UpgradeFingerprint, ItemId } = Upgrade; const upgradeModId = await upgradeMod(artifactsData, accountId);
res.send(upgradeModId);
const safeUpgradeFingerprint = UpgradeFingerprint || '{"lvl":0}'; } catch (err) {
const parsedUpgradeFingerprint = JSON.parse(safeUpgradeFingerprint) as { lvl: number }; console.error("Error parsing JSON data:", err);
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 }]);
} }
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 { export { artifactsController };
Upgrade: IUpgradeClient;
LevelDiff: number;
Cost: number;
FusionPointCost: number;
LegendaryFusion?: boolean;
}

View File

@ -1,97 +0,0 @@
import { RequestHandler } from "express";
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
import { logger } from "@/src/utils/logger";
import { GuildPermission, IDojoComponentDatabase } from "@/src/types/guildTypes";
import { Types } from "mongoose";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService";
export const changeDojoRootController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "GuildId LevelKeys");
const guild = await getGuildForRequestEx(req, inventory);
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Architect))) {
res.json({ DojoRequestStatus: -1 });
return;
}
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(await 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

@ -1,38 +0,0 @@
import { GuildMember } from "@/src/models/guildModel";
import { getGuildForRequest, hasGuildPermissionEx } from "@/src/services/guildService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes";
import { RequestHandler } from "express";
export const changeGuildRankController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const member = (await GuildMember.findOne({
accountId: accountId,
guildId: req.query.guildId as string
}))!;
const newRank: number = parseInt(req.query.rankChange as string);
const guild = await getGuildForRequest(req);
if (newRank < member.rank || !hasGuildPermissionEx(guild, member, GuildPermission.Promoter)) {
res.status(400).json("Invalid permission");
return;
}
const target = (await GuildMember.findOne({
guildId: req.query.guildId as string,
accountId: req.query.targetId as string
}))!;
target.rank = parseInt(req.query.rankChange as string);
await target.save();
if (newRank == 0) {
// If we just promoted someone else to Founding Warlord, we need to demote ourselves to Warlord.
member.rank = 1;
await member.save();
}
res.json({
_id: req.query.targetId as string,
Rank: newRank
});
};

View File

@ -7,120 +7,82 @@ import { getRecipe } from "@/src/services/itemDataService";
import { IOid } from "@/src/types/commonTypes"; import { IOid } from "@/src/types/commonTypes";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { import { getInventory, updateCurrency, addItem, addMiscItems, addRecipes } from "@/src/services/inventoryService";
getInventory,
updateCurrency,
addItem,
addMiscItems,
addRecipes,
occupySlot
} from "@/src/services/inventoryService";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { IMiscItem, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
export interface IClaimCompletedRecipeRequest { export interface IClaimCompletedRecipeRequest {
RecipeIds: IOid[]; RecipeIds: IOid[];
} }
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const claimCompletedRecipeController: RequestHandler = async (req, res) => { 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); const accountId = await getAccountIdForRequest(req);
if (!accountId) throw new Error("no account id"); if (!accountId) throw new Error("no account id");
const inventory = await getInventory(accountId); 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) { 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 //check recipe is indeed ready to be completed
// if (pendingRecipe.CompletionDate > new Date()) { // 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`); // throw new Error(`recipe ${pendingRecipe._id} is not ready to be completed`);
// } // }
inventory.PendingRecipes.pull(pendingRecipe._id); inventory.PendingRecipes.pull(pendingRecipe._id);
await inventory.save();
const recipe = getRecipe(pendingRecipe.ItemType); const recipe = getRecipe(pendingRecipe.ItemType);
if (!recipe) { 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) { if (req.query.cancel) {
const inventoryChanges: IInventoryChanges = { const currencyChanges = await updateCurrency(recipe.buildPrice * -1, false, accountId);
...updateCurrency(inventory, recipe.buildPrice * -1, false)
};
const nonMiscItemIngredients = new Set();
for (const category of ["LongGuns", "Pistols", "Melee"] as const) {
if (pendingRecipe[category]) {
pendingRecipe[category].forEach(item => {
const index = inventory[category].push(item) - 1;
inventoryChanges[category] ??= [];
inventoryChanges[category].push(inventory[category][index].toJSON<IEquipmentClient>());
nonMiscItemIngredients.add(item.ItemType);
occupySlot(inventory, InventorySlot.WEAPONS, false);
inventoryChanges.WeaponBin ??= { Slots: 0 };
inventoryChanges.WeaponBin.Slots -= 1;
});
}
}
const miscItemChanges: IMiscItem[] = [];
recipe.ingredients.forEach(ingredient => {
if (!nonMiscItemIngredients.has(ingredient.ItemType)) {
miscItemChanges.push(ingredient);
}
});
addMiscItems(inventory, miscItemChanges);
inventoryChanges.MiscItems = miscItemChanges;
const inventory = await getInventory(accountId);
addMiscItems(inventory, recipe.ingredients);
await inventory.save(); await inventory.save();
res.json(inventoryChanges); // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
// Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
res.json({
...currencyChanges,
MiscItems: recipe.ingredients
});
} else { } else {
logger.debug("Claiming Recipe", { recipe, pendingRecipe }); logger.debug("Claiming Recipe", { recipe, pendingRecipe });
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
inventory.PendingSpectreLoadouts ??= [];
inventory.SpectreLoadouts ??= [];
const pendingLoadoutIndex = inventory.PendingSpectreLoadouts.findIndex(
x => x.ItemType == recipe.resultType
);
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);
}
}
let InventoryChanges = {}; let InventoryChanges = {};
if (recipe.consumeOnUse) { if (recipe.consumeOnUse) {
addRecipes(inventory, [ const recipeChanges = [
{ {
ItemType: pendingRecipe.ItemType, ItemType: pendingRecipe.ItemType,
ItemCount: -1 ItemCount: -1
} }
]); ];
InventoryChanges = { ...InventoryChanges, Recipes: recipeChanges };
const inventory = await getInventory(accountId);
addRecipes(inventory, recipeChanges);
await inventory.save();
} }
if (req.query.rush) { if (req.query.rush) {
InventoryChanges = { InventoryChanges = {
...InventoryChanges, ...InventoryChanges,
...updateCurrency(inventory, recipe.skipBuildTimePrice, true) ...(await updateCurrency(recipe.skipBuildTimePrice, true, accountId))
}; };
} }
InventoryChanges = { res.json({
InventoryChanges: {
...InventoryChanges, ...InventoryChanges,
...(await addItem(inventory, recipe.resultType, recipe.num, false)) ...(await addItem(accountId, recipe.resultType, recipe.num)).InventoryChanges
}; }
await inventory.save(); });
res.json({ InventoryChanges });
} }
}; };

View File

@ -1,31 +0,0 @@
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
export const claimLibraryDailyTaskRewardController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const rewardQuantity = inventory.LibraryActiveDailyTaskInfo!.RewardQuantity;
const rewardStanding = inventory.LibraryActiveDailyTaskInfo!.RewardStanding;
inventory.LibraryActiveDailyTaskInfo = undefined;
inventory.LibraryAvailableDailyTaskInfo = undefined;
let syndicate = inventory.Affiliations.find(x => x.Tag == "LibrarySyndicate");
if (!syndicate) {
syndicate = inventory.Affiliations[inventory.Affiliations.push({ Tag: "LibrarySyndicate", Standing: 0 }) - 1];
}
syndicate.Standing += rewardStanding;
inventory.FusionPoints += 80 * rewardQuantity;
await inventory.save();
res.json({
RewardItem: "/Lotus/StoreItems/Upgrades/Mods/FusionBundles/RareFusionBundle",
RewardQuantity: rewardQuantity,
StandingAwarded: rewardStanding,
InventoryChanges: {
FusionPoints: 80 * rewardQuantity
}
});
};

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

@ -1,45 +0,0 @@
import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inventoryService";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { createUnveiledRivenFingerprint } from "@/src/helpers/rivenHelper";
import { ExportUpgrades } from "warframe-public-export-plus";
export const completeRandomModChallengeController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const request = getJSONfromString<ICompleteRandomModChallengeRequest>(String(req.body));
let inventoryChanges: IInventoryChanges = {};
// Remove 20 plat or riven cipher
if ((req.query.p as string) == "1") {
inventoryChanges = { ...updateCurrency(inventory, 20, true) };
} else {
const miscItemChanges: IMiscItem[] = [
{
ItemType: "/Lotus/Types/Items/MiscItems/RivenIdentifier",
ItemCount: -1
}
];
addMiscItems(inventory, miscItemChanges);
inventoryChanges.MiscItems = miscItemChanges;
}
// Update riven fingerprint to a randomised unveiled state
const upgrade = inventory.Upgrades.id(request.ItemId)!;
const meta = ExportUpgrades[upgrade.ItemType];
upgrade.UpgradeFingerprint = JSON.stringify(createUnveiledRivenFingerprint(meta));
await inventory.save();
res.json({
InventoryChanges: inventoryChanges,
Fingerprint: upgrade.UpgradeFingerprint
});
};
interface ICompleteRandomModChallengeRequest {
ItemId: string;
}

View File

@ -1,46 +0,0 @@
import { Guild, GuildMember } from "@/src/models/guildModel";
import { getGuildClient, updateInventoryForConfirmedGuildJoin } from "@/src/services/guildService";
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
import { RequestHandler } from "express";
import { Types } from "mongoose";
export const confirmGuildInvitationController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
const guildMember = await GuildMember.findOne({
accountId: account._id,
guildId: req.query.clanId as string
});
if (guildMember) {
guildMember.status = 0;
await guildMember.save();
await updateInventoryForConfirmedGuildJoin(
account._id.toString(),
new Types.ObjectId(req.query.clanId as string)
);
const guild = (await Guild.findOne({ _id: req.query.clanId as string }))!;
guild.RosterActivity ??= [];
guild.RosterActivity.push({
dateTime: new Date(),
entryType: 6,
details: getSuffixedName(account)
});
await guild.save();
res.json({
...(await getGuildClient(guild, account._id.toString())),
InventoryChanges: {
Recipes: [
{
ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
ItemCount: 1
}
]
}
});
} else {
res.end();
}
};

View File

@ -1,104 +0,0 @@
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Guild, GuildMember } from "@/src/models/guildModel";
import { config } from "@/src/services/configService";
import { createMessage } from "@/src/services/inboxService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
import { Types } from "mongoose";
export const contributeGuildClassController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const payload = getJSONfromString<IContributeGuildClassRequest>(String(req.body));
const guild = (await Guild.findOne({ _id: payload.GuildId }))!;
// First contributor initiates ceremony and locks the pending class.
if (!guild.CeremonyContributors) {
guild.CeremonyContributors = [];
guild.CeremonyClass = guildXpToClass(guild.XP);
guild.CeremonyEndo = 0;
for (let i = guild.Class; i != guild.CeremonyClass; ++i) {
guild.CeremonyEndo += (i + 1) * 1000;
}
guild.ClassChanges ??= [];
guild.ClassChanges.push({
dateTime: new Date(),
entryType: 13,
details: guild.CeremonyClass
});
}
guild.CeremonyContributors.push(new Types.ObjectId(accountId));
// Once required contributor count is hit, the class is committed and there's 72 hours to claim endo.
if (guild.CeremonyContributors.length == payload.RequiredContributors) {
guild.Class = guild.CeremonyClass!;
guild.CeremonyClass = undefined;
guild.CeremonyResetDate = new Date(Date.now() + (config.fastClanAscension ? 5_000 : 72 * 3600_000));
if (!config.fastClanAscension) {
// Send message to all active guild members
const members = await GuildMember.find({ guildId: payload.GuildId, status: 0 }, "accountId");
for (const member of members) {
// somewhat unfaithful as on live the "msg" is not a loctag, but since we don't have the string, we'll let the client fill it in with "arg".
await createMessage(member.accountId.toString(), [
{
sndr: guild.Name,
msg: "/Lotus/Language/Clan/Clan_AscensionCeremonyInProgressDetails",
arg: [
{
Key: "RESETDATE",
Tag:
guild.CeremonyResetDate.getUTCMonth() +
"/" +
guild.CeremonyResetDate.getUTCDate() +
"/" +
(guild.CeremonyResetDate.getUTCFullYear() % 100) +
" " +
guild.CeremonyResetDate.getUTCHours().toString().padStart(2, "0") +
":" +
guild.CeremonyResetDate.getUTCMinutes().toString().padStart(2, "0")
}
],
sub: "/Lotus/Language/Clan/Clan_AscensionCeremonyInProgress",
icon: "/Lotus/Interface/Graphics/ClanTileImages/ClanEnterDojo.png",
highPriority: true
}
]);
}
}
}
await guild.save();
// Either way, endo is given to the contributor.
const inventory = await getInventory(accountId, "FusionPoints");
inventory.FusionPoints += guild.CeremonyEndo!;
await inventory.save();
res.json({
NumContributors: guild.CeremonyContributors.length,
FusionPointReward: guild.CeremonyEndo,
Class: guild.Class,
CeremonyResetDate: guild.CeremonyResetDate ? toMongoDate(guild.CeremonyResetDate) : undefined
});
};
interface IContributeGuildClassRequest {
GuildId: string;
RequiredContributors: number;
}
const guildXpToClass = (xp: number): number => {
const cummXp = [
0, 11000, 34000, 69000, 114000, 168000, 231000, 302000, 381000, 68000, 563000, 665000, 774000, 891000
];
let highest = 0;
for (let i = 0; i != cummXp.length; ++i) {
if (xp < cummXp[i]) {
break;
}
highest = i;
}
return highest;
};

View File

@ -1,155 +0,0 @@
import { TGuildDatabaseDocument } from "@/src/models/guildModel";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import {
getDojoClient,
getGuildForRequestEx,
hasAccessToDojo,
processDojoBuildMaterialsGathered,
scaleRequiredCount,
setDojoRoomLogFunded
} from "@/src/services/guildService";
import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IDojoContributable } from "@/src/types/guildTypes";
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { RequestHandler } from "express";
import { ExportDojoRecipes, IDojoBuild } from "warframe-public-export-plus";
interface IContributeToDojoComponentRequest {
ComponentId: string;
DecoId?: string;
DecoType?: string;
IngredientContributions: IMiscItem[];
RegularCredits: number;
VaultIngredientContributions: IMiscItem[];
VaultCredits: number;
}
export const contributeToDojoComponentController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
// Any clan member should have permission to contribute although notably permission is denied if they have not crafted the dojo key and were simply invited in.
if (!hasAccessToDojo(inventory)) {
res.json({ DojoRequestStatus: -1 });
return;
}
const guild = await getGuildForRequestEx(req, inventory);
const request = JSON.parse(String(req.body)) as IContributeToDojoComponentRequest;
const component = guild.DojoComponents.id(request.ComponentId)!;
const inventoryChanges: IInventoryChanges = {};
if (!component.CompletionTime) {
// Room is in "Collecting Materials" state
if (request.DecoId) {
throw new Error("attempt to contribute to a deco in an unfinished room?!");
}
const meta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!;
processContribution(guild, request, inventory, inventoryChanges, meta, component);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (component.CompletionTime) {
setDojoRoomLogFunded(guild, component);
}
} else {
// Room is past "Collecting Materials"
if (request.DecoId) {
const deco = component.Decos!.find(x => x._id.equals(request.DecoId))!;
const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == deco.Type)!;
processContribution(guild, request, inventory, inventoryChanges, meta, deco);
}
}
await guild.save();
await inventory.save();
res.json({
...(await getDojoClient(guild, 0, component._id)),
InventoryChanges: inventoryChanges
});
};
const processContribution = (
guild: TGuildDatabaseDocument,
request: IContributeToDojoComponentRequest,
inventory: TInventoryDatabaseDocument,
inventoryChanges: IInventoryChanges,
meta: IDojoBuild,
component: IDojoContributable
): void => {
component.RegularCredits ??= 0;
if (request.RegularCredits) {
component.RegularCredits += request.RegularCredits;
inventoryChanges.RegularCredits = -request.RegularCredits;
updateCurrency(inventory, request.RegularCredits, false);
}
if (request.VaultCredits) {
component.RegularCredits += request.VaultCredits;
guild.VaultRegularCredits! -= request.VaultCredits;
}
if (component.RegularCredits > scaleRequiredCount(meta.price)) {
guild.VaultRegularCredits ??= 0;
guild.VaultRegularCredits += component.RegularCredits - scaleRequiredCount(meta.price);
component.RegularCredits = scaleRequiredCount(meta.price);
}
component.MiscItems ??= [];
if (request.VaultIngredientContributions.length) {
for (const ingredientContribution of request.VaultIngredientContributions) {
const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredientContribution.ItemType);
if (componentMiscItem) {
const ingredientMeta = meta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
if (
componentMiscItem.ItemCount + ingredientContribution.ItemCount >
scaleRequiredCount(ingredientMeta.ItemCount)
) {
ingredientContribution.ItemCount =
scaleRequiredCount(ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
}
componentMiscItem.ItemCount += ingredientContribution.ItemCount;
} else {
component.MiscItems.push(ingredientContribution);
}
const vaultMiscItem = guild.VaultMiscItems!.find(x => x.ItemType == ingredientContribution.ItemType)!;
vaultMiscItem.ItemCount -= ingredientContribution.ItemCount;
}
}
if (request.IngredientContributions.length) {
const miscItemChanges: IMiscItem[] = [];
for (const ingredientContribution of request.IngredientContributions) {
const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredientContribution.ItemType);
if (componentMiscItem) {
const ingredientMeta = meta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
if (
componentMiscItem.ItemCount + ingredientContribution.ItemCount >
scaleRequiredCount(ingredientMeta.ItemCount)
) {
ingredientContribution.ItemCount =
scaleRequiredCount(ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
}
componentMiscItem.ItemCount += ingredientContribution.ItemCount;
} else {
component.MiscItems.push(ingredientContribution);
}
miscItemChanges.push({
ItemType: ingredientContribution.ItemType,
ItemCount: ingredientContribution.ItemCount * -1
});
}
addMiscItems(inventory, miscItemChanges);
inventoryChanges.MiscItems = miscItemChanges;
}
if (component.RegularCredits >= scaleRequiredCount(meta.price)) {
let fullyFunded = true;
for (const ingredient of meta.ingredients) {
const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredient.ItemType);
if (!componentMiscItem || componentMiscItem.ItemCount < scaleRequiredCount(ingredient.ItemCount)) {
fullyFunded = false;
break;
}
}
if (fullyFunded) {
component.CompletionTime = new Date(Date.now() + meta.time * 1000);
processDojoBuildMaterialsGathered(guild, meta);
}
}
};

View File

@ -1,49 +0,0 @@
import { getGuildForRequestEx } from "@/src/services/guildService";
import { addFusionTreasures, addMiscItems, addShipDecorations, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IFusionTreasure, IMiscItem, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
import { RequestHandler } from "express";
export const contributeToVaultController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const guild = await getGuildForRequestEx(req, inventory);
const request = JSON.parse(String(req.body)) as IContributeToVaultRequest;
if (request.RegularCredits) {
guild.VaultRegularCredits ??= 0;
guild.VaultRegularCredits += request.RegularCredits;
}
if (request.MiscItems.length) {
guild.VaultMiscItems ??= [];
for (const item of request.MiscItems) {
guild.VaultMiscItems.push(item);
addMiscItems(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
}
}
if (request.ShipDecorations.length) {
guild.VaultShipDecorations ??= [];
for (const item of request.ShipDecorations) {
guild.VaultShipDecorations.push(item);
addShipDecorations(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
}
}
if (request.FusionTreasures.length) {
guild.VaultFusionTreasures ??= [];
for (const item of request.FusionTreasures) {
guild.VaultFusionTreasures.push(item);
addFusionTreasures(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
}
}
await guild.save();
await inventory.save();
res.end();
};
interface IContributeToVaultRequest {
RegularCredits: number;
MiscItems: IMiscItem[];
ShipDecorations: ITypeCount[];
FusionTreasures: IFusionTreasure[];
}

View File

@ -1,36 +1,38 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Guild, GuildMember } from "@/src/models/guildModel"; import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
import { import { Guild } from "@/src/models/guildModel";
createUniqueClanName, import { ICreateGuildRequest } from "@/src/types/guildTypes";
getGuildClient,
updateInventoryForConfirmedGuildJoin
} from "@/src/services/guildService";
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 accountId = await getAccountIdForRequest(req);
const payload = getJSONfromString<ICreateGuildRequest>(String(req.body)); const payload = getJSONfromString(String(req.body)) as ICreateGuildRequest;
// Create guild on database // Create guild on database
const guild = new Guild({ const guild = new Guild({
Name: await createUniqueClanName(payload.guildName) Name: payload.guildName
}); });
await guild.save(); await guild.save();
// Create guild member on database // Update inventory
await GuildMember.insertOne({ const inventory = await Inventory.findOne({ accountOwnerId: accountId });
accountId: accountId, if (inventory) {
guildId: guild._id, // Set GuildId
status: 0, inventory.GuildId = guild._id;
rank: 0
// Give clan key (TODO: This should only be a blueprint)
inventory.LevelKeys ??= [];
inventory.LevelKeys.push({
ItemType: "/Lotus/Types/Keys/DojoKey",
ItemCount: 1
}); });
await updateInventoryForConfirmedGuildJoin(accountId, guild._id); await inventory.save();
}
res.json(await getGuildClient(guild, accountId)); res.json(guild);
}; };
interface ICreateGuildRequest { export { createGuildController };
guildName: string;
}

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 = 0;
response.PremiumCredits = 999999999;
}
res.json(response);
};

View File

@ -1,21 +0,0 @@
import { getGuildForRequest, hasGuildPermission } from "@/src/services/guildService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission, IGuildRank } from "@/src/types/guildTypes";
import { RequestHandler } from "express";
export const customizeGuildRanksController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const guild = await getGuildForRequest(req);
const payload = JSON.parse(String(req.body)) as ICustomizeGuildRanksRequest;
if (!(await hasGuildPermission(guild, accountId, GuildPermission.Ruler))) {
res.status(400).json("Invalid permission");
return;
}
guild.Ranks = payload.GuildRanks;
await guild.save();
res.end();
};
interface ICustomizeGuildRanksRequest {
GuildRanks: IGuildRank[];
}

View File

@ -1,14 +0,0 @@
import { GuildMember } from "@/src/models/guildModel";
import { getAccountForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
export const declineGuildInviteController: RequestHandler = async (req, res) => {
const accountId = await getAccountForRequest(req);
await GuildMember.deleteOne({
accountId: accountId,
guildId: req.query.clanId as string
});
res.end();
};

View File

@ -1,33 +0,0 @@
import {
getDojoClient,
getGuildForRequestEx,
hasAccessToDojo,
hasGuildPermission,
removeDojoDeco
} from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes";
import { RequestHandler } from "express";
export const destroyDojoDecoController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "GuildId LevelKeys");
const guild = await getGuildForRequestEx(req, inventory);
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Decorator))) {
res.json({ DojoRequestStatus: -1 });
return;
}
const request = JSON.parse(String(req.body)) as IDestroyDojoDecoRequest;
removeDojoDeco(guild, request.ComponentId, request.DecoId);
await guild.save();
res.json(await getDojoClient(guild, 0, request.ComponentId));
};
interface IDestroyDojoDecoRequest {
DecoType: string;
ComponentId: string;
DecoId: string;
}

View File

@ -1,66 +0,0 @@
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, scaleRequiredCount } from "@/src/services/guildService";
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IDojoContributable } from "@/src/types/guildTypes";
import { RequestHandler } from "express";
import { ExportDojoRecipes, IDojoBuild } from "warframe-public-export-plus";
interface IDojoComponentRushRequest {
DecoType?: string;
DecoId?: string;
ComponentId: string;
Amount: number;
VaultAmount: number;
AllianceVaultAmount: number;
}
export const dojoComponentRushController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
if (!hasAccessToDojo(inventory)) {
res.json({ DojoRequestStatus: -1 });
return;
}
const guild = await getGuildForRequestEx(req, inventory);
const request = JSON.parse(String(req.body)) as IDojoComponentRushRequest;
const component = guild.DojoComponents.id(request.ComponentId)!;
let platinumDonated = request.Amount;
const inventoryChanges = updateCurrency(inventory, request.Amount, true);
if (request.VaultAmount) {
platinumDonated += request.VaultAmount;
guild.VaultPremiumCredits! -= request.VaultAmount;
}
if (request.DecoId) {
const deco = component.Decos!.find(x => x._id.equals(request.DecoId))!;
const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == deco.Type)!;
processContribution(deco, meta, platinumDonated);
} else {
const meta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!;
processContribution(component, meta, platinumDonated);
const entry = guild.RoomChanges?.find(x => x.componentId.equals(component._id));
if (entry) {
entry.dateTime = component.CompletionTime!;
}
}
await guild.save();
await inventory.save();
res.json({
...(await getDojoClient(guild, 0, component._id)),
InventoryChanges: inventoryChanges
});
};
const processContribution = (component: IDojoContributable, meta: IDojoBuild, platinumDonated: number): void => {
const fullPlatinumCost = scaleRequiredCount(meta.skipTimePrice);
const fullDurationSeconds = meta.time;
const secondsPerPlatinum = fullDurationSeconds / fullPlatinumCost;
component.CompletionTime = new Date(
component.CompletionTime!.getTime() - secondsPerPlatinum * platinumDonated * 1000
);
component.RushPlatinum ??= 0;
component.RushPlatinum += platinumDonated;
};

View File

@ -1,143 +1,7 @@
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
import { config } from "@/src/services/configService";
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
import { fromStoreItem } from "@/src/services/itemDataService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getRandomInt, getRandomWeightedRewardUc } from "@/src/services/rngService";
import { IMongoDate, IOid } from "@/src/types/commonTypes";
import { IDroneClient } from "@/src/types/inventoryTypes/inventoryTypes";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { ExportDrones, ExportResources, ExportSystems } from "warframe-public-export-plus";
export const dronesController: RequestHandler = async (req, res) => { const dronesController: RequestHandler = (_req, res) => {
const accountId = await getAccountIdForRequest(req);
if ("GetActive" in req.query) {
const inventory = await getInventory(accountId, "Drones");
const activeDrones: IActiveDrone[] = [];
for (const drone of inventory.Drones) {
if (drone.DeployTime) {
activeDrones.push({
DeployTime: toMongoDate(drone.DeployTime),
System: drone.System!,
ItemId: toOid(drone._id),
ItemType: drone.ItemType,
CurrentHP: drone.CurrentHP,
DamageTime: toMongoDate(drone.DamageTime!),
PendingDamage: drone.PendingDamage!,
Resources: [
{
ItemType: drone.ResourceType!,
BinTotal: drone.ResourceCount!,
StartTime: toMongoDate(drone.DeployTime)
}
]
});
}
}
res.json({
ActiveDrones: activeDrones
});
} else if ("droneId" in req.query && "systemIndex" in req.query) {
const inventory = await getInventory(accountId, "Drones");
const drone = inventory.Drones.id(req.query.droneId as string)!;
const droneMeta = ExportDrones[drone.ItemType];
drone.DeployTime = config.instantResourceExtractorDrones ? new Date(0) : new Date();
if (drone.RepairStart) {
const repairMinutes = (Date.now() - drone.RepairStart.getTime()) / 60_000;
const hpPerMinute = droneMeta.repairRate / 60;
drone.CurrentHP = Math.min(drone.CurrentHP + Math.round(repairMinutes * hpPerMinute), droneMeta.durability);
drone.RepairStart = undefined;
}
drone.System = parseInt(req.query.systemIndex as string);
const system = ExportSystems[drone.System - 1];
drone.DamageTime = config.instantResourceExtractorDrones
? new Date()
: new Date(Date.now() + getRandomInt(3 * 3600 * 1000, 4 * 3600 * 1000));
drone.PendingDamage =
Math.random() < system.damageChance
? getRandomInt(system.droneDamage.minValue, system.droneDamage.maxValue)
: 0;
const resource = getRandomWeightedRewardUc(system.resources, droneMeta.probabilities)!;
//logger.debug(`drone rolled`, resource);
drone.ResourceType = fromStoreItem(resource.StoreItem);
const resourceMeta = ExportResources[drone.ResourceType];
if (resourceMeta.pickupQuantity) {
const pickupsToCollect = droneMeta.binCapacity * droneMeta.capacityMultipliers[resource.Rarity];
drone.ResourceCount = 0;
for (let i = 0; i != pickupsToCollect; ++i) {
drone.ResourceCount += getRandomInt(
resourceMeta.pickupQuantity.minValue,
resourceMeta.pickupQuantity.maxValue
);
}
} else {
drone.ResourceCount = 1;
}
await inventory.save();
res.json({}); res.json({});
} else if ("collectDroneId" in req.query) {
const inventory = await getInventory(accountId);
const drone = inventory.Drones.id(req.query.collectDroneId as string)!;
if (new Date() >= drone.DamageTime!) {
drone.CurrentHP -= drone.PendingDamage!;
drone.RepairStart = new Date();
}
const inventoryChanges: IInventoryChanges = {};
if (drone.CurrentHP <= 0) {
inventory.RegularCredits += 100;
inventoryChanges.RegularCredits = 100;
inventory.Drones.pull({ _id: req.query.collectDroneId as string });
inventoryChanges.RemovedIdItems = [
{
ItemId: { $oid: req.query.collectDroneId }
}
];
} else {
const completionTime = drone.DeployTime!.getTime() + ExportDrones[drone.ItemType].fillRate * 3600_000;
if (Date.now() >= completionTime) {
const miscItemChanges = [
{
ItemType: drone.ResourceType!,
ItemCount: drone.ResourceCount!
}
];
addMiscItems(inventory, miscItemChanges);
inventoryChanges.MiscItems = miscItemChanges;
}
drone.DeployTime = undefined;
drone.System = undefined;
drone.DamageTime = undefined;
drone.PendingDamage = undefined;
drone.ResourceType = undefined;
drone.ResourceCount = undefined;
inventoryChanges.Drones = [drone.toJSON<IDroneClient>()];
}
await inventory.save();
res.json({
InventoryChanges: inventoryChanges
});
} else {
throw new Error(`drones.php query not handled`);
}
}; };
interface IActiveDrone { export { dronesController };
DeployTime: IMongoDate;
System: number;
ItemId: IOid;
ItemType: string;
CurrentHP: number;
DamageTime: IMongoDate;
PendingDamage: number;
Resources: {
ItemType: string;
BinTotal: number;
StartTime: IMongoDate;
}[];
}

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

View File

@ -1,21 +1,18 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService"; 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 { 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"; import { EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const evolveWeaponController: RequestHandler = async (req, res) => { export const evolveWeaponController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
const 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)!; // TODO: We should remove the Genesis item & its resources, but currently we don't know these "recipes".
if (payload.Action == "EWA_INSTALL") {
addMiscItems(
inventory,
recipe.ingredients.map(x => ({ ItemType: x.ItemType, ItemCount: x.ItemCount * -1 }))
);
const item = inventory[payload.Category].find(item => item._id.toString() == (req.query.ItemId as string))!; const item = inventory[payload.Category].find(item => item._id.toString() == (req.query.ItemId as string))!;
item.Features ??= 0; item.Features ??= 0;
@ -31,26 +28,13 @@ export const evolveWeaponController: RequestHandler = async (req, res) => {
ItemType: payload.EvoType 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}`);
}
await inventory.save(); await inventory.save();
res.end(); res.end();
}; };
interface IEvolveWeaponRequest { interface IEvolveWeaponRequest {
Action: string; Action: "EWA_INSTALL";
Category: WeaponTypeInternal; Category: WeaponTypeInternal;
Recipe: string; // e.g. "/Lotus/Types/Items/MiscItems/IncarnonAdapters/UnlockerBlueprints/DespairIncarnonBlueprint" Recipe: string; // e.g. "/Lotus/Types/Items/MiscItems/IncarnonAdapters/UnlockerBlueprints/DespairIncarnonBlueprint"
UninstallRecipe: ""; UninstallRecipe: "";

View File

@ -1,28 +1,31 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getSession } from "@/src/managers/sessionManager"; import { getSession } from "@/src/managers/sessionManager";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { IFindSessionRequest } from "@/src/types/session";
export const findSessionsController: RequestHandler = (_req, res) => { //TODO: cleanup
const req = JSON.parse(String(_req.body)) as IFindSessionRequest; const findSessionsController: RequestHandler = (_req, res) => {
logger.debug("FindSession Request ", req); const reqBody = JSON.parse(String(_req.body));
logger.debug("FindSession Request ", { reqBody });
const req = JSON.parse(String(_req.body));
if (req.id != undefined) { if (req.id != undefined) {
logger.debug("Found ID"); 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 res.json({});
} else if (req.originalSessionId != undefined) { } else if (req.originalSessionId != undefined) {
logger.debug("Found OriginalSessionID"); logger.debug("Found OriginalSessionID");
const session = getSession(req.originalSessionId); const session = getSession(req.originalSessionId 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 res.json({});
} else { } else {
logger.debug("Found SessionRequest"); logger.debug("Found SessionRequest");
const session = getSession(req); const session = getSession(String(_req.body));
if (session.length) res.json({ queryId: req.queryId, Sessions: session }); if (session) res.json({ queryId: req.queryId, Sessions: session });
else res.json({}); 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,42 +1,19 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory, addMiscItems, addEquipment, occupySlot } from "@/src/services/inventoryService"; import { getInventory, addMiscItems, addEquipment } from "@/src/services/inventoryService";
import { IMiscItem, TFocusPolarity, TEquipmentKey, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes"; import { IMiscItem, TFocusPolarity } from "@/src/types/inventoryTypes/inventoryTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { ExportFocusUpgrades } from "warframe-public-export-plus"; import { ExportFocusUpgrades } from "warframe-public-export-plus";
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const focusController: RequestHandler = async (req, res) => { export const focusController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
switch (req.query.op) { switch (req.query.op) {
default: default:
logger.error("Unhandled focus op type: " + String(req.query.op)); logger.error("Unhandled focus op type: " + req.query.op);
logger.debug(String(req.body)); logger.debug(req.body.toString());
res.end(); res.end();
break; 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: { case FocusOperation.UnlockWay: {
const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType; const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType;
const focusPolarity = focusTypeToPolarity(focusType); const focusPolarity = focusTypeToPolarity(focusType);
@ -56,16 +33,9 @@ export const focusController: RequestHandler = async (req, res) => {
} }
case FocusOperation.ActivateWay: { case FocusOperation.ActivateWay: {
const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType; const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType;
const inventory = await getInventory(accountId);
await Inventory.updateOne( inventory.FocusAbility = focusType;
{ await inventory.save();
accountOwnerId: accountId
},
{
FocusAbility: focusType
}
);
res.end(); res.end();
break; break;
} }
@ -78,7 +48,7 @@ export const focusController: RequestHandler = async (req, res) => {
cost += ExportFocusUpgrades[focusType].baseFocusPointCost; cost += ExportFocusUpgrades[focusType].baseFocusPointCost;
inventory.FocusUpgrades.push({ ItemType: focusType, Level: 0 }); inventory.FocusUpgrades.push({ ItemType: focusType, Level: 0 });
} }
inventory.FocusXP![focusPolarity] -= cost; inventory.FocusXP[focusPolarity] -= cost;
await inventory.save(); await inventory.save();
res.json({ res.json({
FocusTypes: request.FocusTypes, FocusTypes: request.FocusTypes,
@ -96,7 +66,7 @@ export const focusController: RequestHandler = async (req, res) => {
const focusUpgradeDb = inventory.FocusUpgrades.find(entry => entry.ItemType == focusUpgrade.ItemType)!; const focusUpgradeDb = inventory.FocusUpgrades.find(entry => entry.ItemType == focusUpgrade.ItemType)!;
focusUpgradeDb.Level = focusUpgrade.Level; focusUpgradeDb.Level = focusUpgrade.Level;
} }
inventory.FocusXP![focusPolarity] -= cost; inventory.FocusXP[focusPolarity] -= cost;
await inventory.save(); await inventory.save();
res.json({ res.json({
FocusInfos: request.FocusInfos, FocusInfos: request.FocusInfos,
@ -111,18 +81,15 @@ export const focusController: RequestHandler = async (req, res) => {
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingChassis", "/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingChassis",
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingBarrel" "/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingBarrel"
]; ];
const inventory = await getInventory(accountId); const result = await addEquipment("OperatorAmps", request.StartingWeaponType, accountId, parts);
const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, parts); res.json(result);
occupySlot(inventory, InventorySlot.AMPS, false);
await inventory.save();
res.json((inventoryChanges.OperatorAmps as IEquipmentClient[])[0]);
break; break;
} }
case FocusOperation.UnbindUpgrade: { case FocusOperation.UnbindUpgrade: {
const request = JSON.parse(String(req.body)) as IUnbindUpgradeRequest; const request = JSON.parse(String(req.body)) as IUnbindUpgradeRequest;
const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]); const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]);
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
inventory.FocusXP![focusPolarity] -= 750_000 * request.FocusTypes.length; inventory.FocusXP[focusPolarity] -= 750_000 * request.FocusTypes.length;
addMiscItems(inventory, [ addMiscItems(inventory, [
{ {
ItemType: "/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantItem", ItemType: "/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantItem",
@ -177,7 +144,6 @@ export const focusController: RequestHandler = async (req, res) => {
}; };
enum FocusOperation { enum FocusOperation {
InstallLens = "1",
UnlockWay = "2", UnlockWay = "2",
UnlockUpgrade = "3", UnlockUpgrade = "3",
LevelUpUpgrade = "4", LevelUpUpgrade = "4",
@ -220,12 +186,6 @@ interface ISentTrainingAmplifierRequest {
StartingWeaponType: string; StartingWeaponType: string;
} }
interface ILensInstallRequest {
LensType: string;
Category: TEquipmentKey;
WeaponId: string;
}
// Works for ways & upgrades // Works for ways & upgrades
const focusTypeToPolarity = (type: string): TFocusPolarity => { const focusTypeToPolarity = (type: string): TFocusPolarity => {
return ("AP_" + type.substr(1).split("/")[3].toUpperCase()) as TFocusPolarity; return ("AP_" + type.substr(1).split("/")[3].toUpperCase()) as TFocusPolarity;

View File

@ -1,52 +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;
// Swap treasures
const oldTreasure = parseFusionTreasure(request.oldTreasureName, -1);
const newTreasure = parseFusionTreasure(request.newTreasureName, 1);
const fusionTreasureChanges = [oldTreasure, newTreasure];
addFusionTreasures(inventory, fusionTreasureChanges);
// 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();
// The response itself is the inventory changes for this endpoint.
res.json({
MiscItems: miscItemChanges,
FusionTreasures: fusionTreasureChanges
});
};

View File

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

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,7 +1,6 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
// POST with {} instead of GET as of 38.5.0 const getFriendsController = (_request: Request, response: Response) => {
const getFriendsController = (_request: Request, response: Response): void => {
response.writeHead(200, { response.writeHead(200, {
//Connection: "keep-alive", //Connection: "keep-alive",
//"Content-Encoding": "gzip", //"Content-Encoding": "gzip",

View File

@ -1,34 +1,7 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { Guild } from "@/src/models/guildModel";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { logger } from "@/src/utils/logger";
import { getInventory } from "@/src/services/inventoryService";
import { createUniqueClanName, getGuildClient } from "@/src/services/guildService";
const getGuildController: RequestHandler = async (req, res) => { const getGuildController: RequestHandler = (_, res) => {
const accountId = await getAccountIdForRequest(req); res.json({});
const inventory = await getInventory(accountId, "GuildId");
if (inventory.GuildId) {
const guild = await Guild.findOne({ _id: inventory.GuildId });
if (guild) {
// Handle guilds created before we added discriminators
if (guild.Name.indexOf("#") == -1) {
guild.Name = await createUniqueClanName(guild.Name);
await guild.save();
}
if (guild.CeremonyResetDate && Date.now() >= guild.CeremonyResetDate.getTime()) {
logger.debug(`ascension ceremony is over`);
guild.CeremonyEndo = undefined;
guild.CeremonyContributors = undefined;
guild.CeremonyResetDate = undefined;
await guild.save();
}
res.json(await getGuildClient(guild, accountId));
return;
}
}
res.sendStatus(200);
}; };
export { getGuildController }; export { getGuildController };

View File

@ -1,8 +1,10 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { Guild } from "@/src/models/guildModel"; import { Guild } from "@/src/models/guildModel";
import { getDojoClient } from "@/src/services/guildService"; import { 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) => { export const getGuildDojoController: RequestHandler = async (req, res) => {
const guildId = req.query.guildId as string; const guildId = req.query.guildId as string;
@ -13,21 +15,46 @@ export const getGuildDojoController: RequestHandler = async (req, res) => {
} }
// Populate dojo info if not present // Populate dojo info if not present
if (guild.DojoComponents.length == 0) { if (!guild.DojoComponents || guild.DojoComponents.length == 0) {
guild.DojoComponents.push({ guild.DojoComponents = [
{
_id: new Types.ObjectId(), _id: new Types.ObjectId(),
pf: "/Lotus/Levels/ClanDojo/DojoHall.level", pf: "/Lotus/Levels/ClanDojo/DojoHall.level",
ppf: "", ppf: "",
CompletionTime: new Date(Date.now()), CompletionTime: new Date(Date.now())
DecoCapacity: 600 }
}); ];
await guild.save(); await guild.save();
} }
const payload: IGetGuildDojoRequest = req.body ? (JSON.parse(String(req.body)) as IGetGuildDojoRequest) : {}; const dojo: IDojoClient = {
res.json(await getDojoClient(guild, 0, payload.ComponentId)); _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 => {
interface IGetGuildDojoRequest { const clientComponent: IDojoComponentClient = {
ComponentId?: string; 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

@ -1,60 +1,11 @@
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
import { Guild } from "@/src/models/guildModel";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IMongoDate } from "@/src/types/commonTypes";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
export const getGuildLogController: RequestHandler = async (req, res) => { export const getGuildLogController: RequestHandler = (_req, res) => {
const accountId = await getAccountIdForRequest(req); res.json({
const inventory = await getInventory(accountId, "GuildId");
if (inventory.GuildId) {
const guild = await Guild.findOne({ _id: inventory.GuildId });
if (guild) {
const log: Record<string, IGuildLogEntryClient[]> = {
RoomChanges: [], RoomChanges: [],
TechChanges: [], TechChanges: [],
RosterActivity: [], RosterActivity: [],
StandingsUpdates: [], StandingsUpdates: [],
ClassChanges: [] ClassChanges: []
});
}; };
guild.RoomChanges?.forEach(entry => {
log.RoomChanges.push({
dateTime: toMongoDate(entry.dateTime ?? new Date()),
entryType: entry.entryType,
details: entry.details
});
});
guild.TechChanges?.forEach(entry => {
log.TechChanges.push({
dateTime: toMongoDate(entry.dateTime ?? new Date()),
entryType: entry.entryType,
details: entry.details
});
});
guild.RosterActivity?.forEach(entry => {
log.RosterActivity.push({
dateTime: toMongoDate(entry.dateTime),
entryType: entry.entryType,
details: entry.details
});
});
guild.ClassChanges?.forEach(entry => {
log.ClassChanges.push({
dateTime: toMongoDate(entry.dateTime),
entryType: entry.entryType,
details: entry.details
});
});
res.json(log);
return;
}
}
res.sendStatus(200);
};
interface IGuildLogEntryClient {
dateTime: IMongoDate;
entryType: number;
details: number | string;
}

View File

@ -1,26 +1,13 @@
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
export const getNewRewardSeedController: RequestHandler = async (req, res) => { const getNewRewardSeedController: RequestHandler = (_req, res) => {
const accountId = await getAccountIdForRequest(req); res.json({ rewardSeed: generateRewardSeed() });
const rewardSeed = generateRewardSeed();
logger.debug(`generated new reward seed: ${rewardSeed}`);
await Inventory.updateOne(
{
accountOwnerId: accountId
},
{
RewardSeed: rewardSeed
}
);
res.json({ rewardSeed: rewardSeed });
}; };
export function generateRewardSeed(): number { function generateRewardSeed(): number {
const min = -Number.MAX_SAFE_INTEGER; const min = -Number.MAX_SAFE_INTEGER;
const max = Number.MAX_SAFE_INTEGER; const max = Number.MAX_SAFE_INTEGER;
return Math.floor(Math.random() * (max - min + 1)) + min; return Math.floor(Math.random() * (max - min + 1)) + min;
} }
export { getNewRewardSeedController };

View File

@ -4,32 +4,31 @@ import allShipFeatures from "@/static/fixed_responses/allShipFeatures.json";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { getPersonalRooms } from "@/src/services/personalRoomsService"; import { getPersonalRooms } from "@/src/services/personalRoomsService";
import { getShip } from "@/src/services/shipService"; 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 { toOid } from "@/src/helpers/inventoryHelpers";
import { IGetShipResponse } from "@/src/types/shipTypes"; 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) => { export const getShipController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const personalRoomsDb = await getPersonalRooms(accountId); const personalRooms = await getPersonalRooms(accountId);
const personalRooms = personalRoomsDb.toJSON<IPersonalRooms>();
const loadout = await getLoadout(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 = { const getShipResponse: IGetShipResponse = {
ShipOwnerId: accountId, ShipOwnerId: accountId,
LoadOutInventory: { LoadOutPresets: loadout.toJSON() }, LoadOutInventory: { LoadOutPresets: loadout.toJSON() },
Ship: { Ship: {
...personalRooms.Ship, ...personalRooms.toJSON().Ship,
ShipId: toOid(personalRoomsDb.activeShipId), ShipId: toOid(personalRooms.activeShipId),
ShipInterior: { ShipInterior: {
Colors: personalRooms.ShipInteriorColors, Colors: ship.ShipInteriorColors,
ShipAttachments: ship.ShipAttachments, ShipAttachments: ship.ShipAttachments,
SkinFlavourItem: ship.SkinFlavourItem SkinFlavourItem: ship.SkinFlavourItem
} }
}, },
Apartment: personalRooms.Apartment, Apartment: personalRooms.Apartment
TailorShop: personalRooms.TailorShop
}; };
if (config.unlockAllShipFeatures) { if (config.unlockAllShipFeatures) {
@ -38,3 +37,14 @@ export const getShipController: RequestHandler = async (req, res) => {
res.json(getShipResponse); 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 { 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) => { export const getVendorInfoController: RequestHandler = (req, res) => {
if (typeof req.query.vendor == "string") { switch (req.query.vendor as string) {
const manifest = getVendorManifestByTypeName(req.query.vendor); case "/Lotus/Types/Game/VendorManifests/Zariman/ArchimedeanVendorManifest":
if (!manifest) { 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}`); throw new Error(`Unknown vendor: ${req.query.vendor}`);
} }
res.json(manifest);
} else {
res.status(400).end();
}
}; };

View File

@ -1,38 +0,0 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { crackRelic } from "@/src/helpers/relicHelper";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IVoidTearParticipantInfo } from "@/src/types/requestTypes";
import { RequestHandler } from "express";
export const getVoidProjectionRewardsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const data = getJSONfromString<IVoidProjectionRewardRequest>(String(req.body));
if (data.ParticipantInfo.QualifiesForReward && !data.ParticipantInfo.HaveRewardResponse) {
const inventory = await getInventory(accountId);
await crackRelic(inventory, data.ParticipantInfo);
await inventory.save();
}
const response: IVoidProjectionRewardResponse = {
CurrentWave: data.CurrentWave,
ParticipantInfo: data.ParticipantInfo,
DifficultyTier: data.DifficultyTier
};
res.json(response);
};
interface IVoidProjectionRewardRequest {
CurrentWave: number;
ParticipantInfo: IVoidTearParticipantInfo;
VoidTier: string;
DifficultyTier: number;
VoidProjectionRemovalHash: string;
}
interface IVoidProjectionRewardResponse {
CurrentWave: number;
ParticipantInfo: IVoidTearParticipantInfo;
DifficultyTier: number;
}

View File

@ -1,47 +1,50 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { addMiscItems, getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
import { WeaponTypeInternal } from "@/src/services/itemDataService"; import { WeaponTypeInternal } from "@/src/services/itemDataService";
import { ArtifactPolarity, EquipmentFeatures, IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { ExportRecipes } from "warframe-public-export-plus";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
const modularWeaponCategory: (WeaponTypeInternal | "Hoverboards")[] = [ const modularWeaponCategory: (WeaponTypeInternal | "Hoverboards")[] = [
"LongGuns", "LongGuns",
"Pistols", "Pistols",
"Melee", "Melee",
"OperatorAmps", "OperatorAmps",
"Hoverboards" "Hoverboards" // Not sure about hoverboards just coppied from modual crafting
]; ];
interface IGildWeaponRequest { interface IGildWeaponRequest {
ItemName: string; ItemName: string;
Recipe: string; // e.g. /Lotus/Weapons/SolarisUnited/LotusGildKitgunBlueprint Recipe: string; // /Lotus/Weapons/SolarisUnited/LotusGildKitgunBlueprint
PolarizeSlot?: number; PolarizeSlot?: number;
PolarizeValue?: ArtifactPolarity; PolarizeValue?: ArtifactPolarity;
ItemId: string; ItemId: string;
Category: WeaponTypeInternal | "Hoverboards"; Category: WeaponTypeInternal | "Hoverboards";
} }
// 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) => { export const gildWeaponController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const data = getJSONfromString<IGildWeaponRequest>(String(req.body)); const data: IGildWeaponRequest = getJSONfromString(String(req.body));
data.ItemId = String(req.query.ItemId); data.ItemId = String(req.query.ItemId);
if (!modularWeaponCategory.includes(req.query.Category as WeaponTypeInternal | "Hoverboards")) { 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"; data.Category = req.query.Category as WeaponTypeInternal | "Hoverboards";
const inventory = await getInventory(accountId); 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); const weaponIndex = inventory[data.Category].findIndex(x => String(x._id) === data.ItemId);
if (weaponIndex === -1) { 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]; const weapon = inventory[data.Category][weaponIndex];
weapon.Features ??= 0; weapon.Features = EquipmentFeatures.GILDED; // maybe 9 idk if DOUBLE_CAPACITY is also given
weapon.Features |= EquipmentFeatures.GILDED;
weapon.ItemName = data.ItemName; weapon.ItemName = data.ItemName;
weapon.XP = 0; weapon.XP = 0;
if (data.Category != "OperatorAmps" && data.PolarizeSlot && data.PolarizeValue) { if (data.Category != "OperatorAmps" && data.PolarizeSlot && data.PolarizeValue) {
@ -53,29 +56,11 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
]; ];
} }
inventory[data.Category][weaponIndex] = weapon; inventory[data.Category][weaponIndex] = weapon;
const inventoryChanges: IInventoryChanges = {};
inventoryChanges[data.Category] = [weapon.toJSON<IEquipmentClient>()];
const recipe = ExportRecipes[data.Recipe];
inventoryChanges.MiscItems = recipe.secretIngredients!.map(ingredient => ({
ItemType: ingredient.ItemType,
ItemCount: ingredient.ItemCount * -1
}));
addMiscItems(inventory, inventoryChanges.MiscItems);
const affiliationMods = [];
if (recipe.syndicateStandingChange) {
const affiliation = inventory.Affiliations.find(x => x.Tag == recipe.syndicateStandingChange!.tag)!;
affiliation.Standing += recipe.syndicateStandingChange.value;
affiliationMods.push({
Tag: recipe.syndicateStandingChange.tag,
Standing: recipe.syndicateStandingChange.value
});
}
await inventory.save(); await inventory.save();
res.json({ res.json({
InventoryChanges: inventoryChanges, InventoryChanges: {
AffiliationMods: affiliationMods [data.Category]: [weapon]
}
}); });
}; };

View File

@ -1,23 +0,0 @@
import { RequestHandler } from "express";
import { parseString } from "@/src/helpers/general";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getInventory } from "@/src/services/inventoryService";
import { IGroup } from "@/src/types/loginTypes";
import { giveKeyChainItem } 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 giveKeyChainItem(inventory, keyChainInfo);
await inventory.save();
res.send(inventoryChanges);
};
export interface IKeyChainRequest {
KeyChain: string;
ChainStage: number;
Groups?: IGroup[];
}

View File

@ -1,16 +0,0 @@
import { IKeyChainRequest } from "@/src/controllers/api/giveKeyChainTriggeredItemsController";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { giveKeyChainMessage } 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 inventory = await getInventory(accountId, "QuestKeys");
await giveKeyChainMessage(inventory, accountId, keyChainInfo);
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,97 +1,14 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { InventoryDocumentProps } from "@/src/models/inventoryModels/inventoryModel";
import {
addEquipment,
addItem,
addPowerSuit,
combineInventoryChanges,
getInventory,
updateSlots
} from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { IInventoryClient, IInventoryDatabase, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { HydratedDocument } from "mongoose"; import { getInventory } from "@/src/services/inventoryService";
type TPartialStartingGear = Pick<IInventoryClient, "LongGuns" | "Suits" | "Pistols" | "Melee">;
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const giveStartingGearController: RequestHandler = async (req, res) => { export const giveStartingGearController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const startingGear = getJSONfromString<TPartialStartingGear>(String(req.body));
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
const inventoryChanges = await addStartingGear(inventory, startingGear);
await inventory.save();
res.send(inventoryChanges);
};
//TODO: RawUpgrades might need to return a LastAdded
const awakeningRewards = [
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem1",
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem2",
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem3",
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem4",
"/Lotus/Types/Restoratives/LisetAutoHack",
"/Lotus/Upgrades/Mods/Warframe/AvatarShieldMaxMod"
];
export const addStartingGear = async (
inventory: HydratedDocument<IInventoryDatabase, InventoryDocumentProps>,
startingGear: TPartialStartingGear | undefined = undefined
): Promise<IInventoryChanges> => {
const { LongGuns, Pistols, Suits, Melee } = startingGear || {
LongGuns: [{ ItemType: "/Lotus/Weapons/Tenno/Rifle/Rifle" }],
Pistols: [{ ItemType: "/Lotus/Weapons/Tenno/Pistol/Pistol" }],
Suits: [{ ItemType: "/Lotus/Powersuits/Excalibur/Excalibur" }],
Melee: [{ ItemType: "/Lotus/Weapons/Tenno/Melee/LongSword/LongSword" }]
};
//TODO: properly merge weapon bin changes it is currently static here
const inventoryChanges: IInventoryChanges = {};
addEquipment(inventory, "LongGuns", LongGuns[0].ItemType, undefined, inventoryChanges);
addEquipment(inventory, "Pistols", Pistols[0].ItemType, undefined, inventoryChanges);
addEquipment(inventory, "Melee", Melee[0].ItemType, undefined, inventoryChanges);
addPowerSuit(inventory, Suits[0].ItemType, inventoryChanges);
addEquipment(
inventory,
"DataKnives",
"/Lotus/Weapons/Tenno/HackingDevices/TnHackingDevice/TnHackingDeviceWeapon",
undefined,
inventoryChanges,
{ XP: 450_000 }
);
addEquipment(
inventory,
"Scoops",
"/Lotus/Weapons/Tenno/Speedball/SpeedballWeaponTest",
undefined,
inventoryChanges
);
updateSlots(inventory, InventorySlot.SUITS, 0, 1);
updateSlots(inventory, InventorySlot.WEAPONS, 0, 3);
inventoryChanges.SuitBin = { count: 1, platinum: 0, Slots: -1 };
inventoryChanges.WeaponBin = { count: 3, platinum: 0, Slots: -3 };
await addItem(inventory, "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain");
inventory.ActiveQuest = "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain";
inventory.PremiumCredits = 50;
inventory.PremiumCreditsFree = 50;
inventoryChanges.PremiumCredits = 50;
inventoryChanges.PremiumCreditsFree = 50;
inventory.RegularCredits = 3000;
inventoryChanges.RegularCredits = 3000;
for (const item of awakeningRewards) {
const inventoryDelta = await addItem(inventory, item);
combineInventoryChanges(inventoryChanges, inventoryDelta);
}
inventory.PlayedParkourTutorial = true;
inventory.ReceivedStartingGear = true; inventory.ReceivedStartingGear = true;
console.log(req.query);
return inventoryChanges; await inventory.save();
res.status(200);
}; };

View File

@ -1,290 +1,5 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import {
getGuildForRequestEx,
getGuildVault,
hasAccessToDojo,
hasGuildPermission,
removePigmentsFromGuildMembers,
scaleRequiredCount
} from "@/src/services/guildService";
import { ExportDojoRecipes, IDojoResearch } from "warframe-public-export-plus";
import { getAccountIdForRequest } from "@/src/services/loginService";
import {
addItem,
addMiscItems,
addRecipes,
combineInventoryChanges,
getInventory,
updateCurrency
} from "@/src/services/inventoryService";
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { config } from "@/src/services/configService";
import { GuildPermission, ITechProjectClient, ITechProjectDatabase } from "@/src/types/guildTypes";
import { TGuildDatabaseDocument } from "@/src/models/guildModel";
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
import { logger } from "@/src/utils/logger";
export const guildTechController: RequestHandler = async (req, res) => { export const guildTechController: RequestHandler = (_req, res) => {
const accountId = await getAccountIdForRequest(req); res.status(500).end(); // This is what I got for a fresh clan.
const inventory = await getInventory(accountId);
const guild = await getGuildForRequestEx(req, inventory);
const data = JSON.parse(String(req.body)) as TGuildTechRequest;
if (data.Action == "Sync") {
let needSave = false;
const techProjects: ITechProjectClient[] = [];
if (guild.TechProjects) {
for (const project of guild.TechProjects) {
const techProject: ITechProjectClient = {
ItemType: project.ItemType,
ReqCredits: project.ReqCredits,
ReqItems: project.ReqItems,
State: project.State
}; };
if (project.CompletionDate) {
techProject.CompletionDate = toMongoDate(project.CompletionDate);
if (Date.now() >= project.CompletionDate.getTime()) {
needSave ||= setTechLogState(guild, project.ItemType, 4, project.CompletionDate);
}
}
techProjects.push(techProject);
}
}
if (needSave) {
await guild.save();
}
res.json({ TechProjects: techProjects });
} else if (data.Action == "Start") {
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
res.status(400).send("-1").end();
return;
}
const recipe = ExportDojoRecipes.research[data.RecipeType];
guild.TechProjects ??= [];
if (!guild.TechProjects.find(x => x.ItemType == data.RecipeType)) {
const techProject =
guild.TechProjects[
guild.TechProjects.push({
ItemType: data.RecipeType,
ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(recipe.price),
ReqItems: recipe.ingredients.map(x => ({
ItemType: x.ItemType,
ItemCount: config.noDojoResearchCosts ? 0 : scaleRequiredCount(x.ItemCount)
})),
State: 0
}) - 1
];
setTechLogState(guild, techProject.ItemType, 5);
if (config.noDojoResearchCosts) {
processFundedProject(guild, techProject, recipe);
} else {
if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
guild.ActiveDojoColorResearch = data.RecipeType;
}
}
}
await guild.save();
res.end();
} else if (data.Action == "Contribute") {
if (!hasAccessToDojo(inventory)) {
res.status(400).send("-1").end();
return;
}
const contributions = data;
const techProject = guild.TechProjects!.find(x => x.ItemType == contributions.RecipeType)!;
if (contributions.VaultCredits) {
if (contributions.VaultCredits > techProject.ReqCredits) {
contributions.VaultCredits = techProject.ReqCredits;
}
techProject.ReqCredits -= contributions.VaultCredits;
guild.VaultRegularCredits! -= contributions.VaultCredits;
}
if (contributions.RegularCredits > techProject.ReqCredits) {
contributions.RegularCredits = techProject.ReqCredits;
}
techProject.ReqCredits -= contributions.RegularCredits;
if (contributions.VaultMiscItems.length) {
for (const miscItem of contributions.VaultMiscItems) {
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
if (reqItem) {
if (miscItem.ItemCount > reqItem.ItemCount) {
miscItem.ItemCount = reqItem.ItemCount;
}
reqItem.ItemCount -= miscItem.ItemCount;
const vaultMiscItem = guild.VaultMiscItems!.find(x => x.ItemType == miscItem.ItemType)!;
vaultMiscItem.ItemCount -= miscItem.ItemCount;
}
}
}
const miscItemChanges = [];
for (const miscItem of contributions.MiscItems) {
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
if (reqItem) {
if (miscItem.ItemCount > reqItem.ItemCount) {
miscItem.ItemCount = reqItem.ItemCount;
}
reqItem.ItemCount -= miscItem.ItemCount;
miscItemChanges.push({
ItemType: miscItem.ItemType,
ItemCount: miscItem.ItemCount * -1
});
}
}
addMiscItems(inventory, miscItemChanges);
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, contributions.RegularCredits, false);
inventoryChanges.MiscItems = miscItemChanges;
if (techProject.ReqCredits == 0 && !techProject.ReqItems.find(x => x.ItemCount > 0)) {
// This research is now fully funded.
const recipe = ExportDojoRecipes.research[data.RecipeType];
processFundedProject(guild, techProject, recipe);
if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
guild.ActiveDojoColorResearch = "";
await removePigmentsFromGuildMembers(guild._id);
}
}
await guild.save();
await inventory.save();
res.json({
InventoryChanges: inventoryChanges,
Vault: getGuildVault(guild)
});
} else if (data.Action.split(",")[0] == "Buy") {
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
res.status(400).send("-1").end();
return;
}
const purchase = data as IGuildTechBuyRequest;
const quantity = parseInt(data.Action.split(",")[1]);
const recipeChanges = [
{
ItemType: purchase.RecipeType,
ItemCount: quantity
}
];
addRecipes(inventory, recipeChanges);
const currencyChanges = updateCurrency(
inventory,
ExportDojoRecipes.research[purchase.RecipeType].replicatePrice,
false
);
await inventory.save();
// Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`.
res.json({
inventoryChanges: {
...currencyChanges,
Recipes: recipeChanges
}
});
} else if (data.Action == "Fabricate") {
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
res.status(400).send("-1").end();
return;
}
const recipe = ExportDojoRecipes.fabrications[data.RecipeType];
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, recipe.price, false);
inventoryChanges.MiscItems = recipe.ingredients.map(x => ({
ItemType: x.ItemType,
ItemCount: x.ItemCount * -1
}));
addMiscItems(inventory, inventoryChanges.MiscItems);
combineInventoryChanges(inventoryChanges, await addItem(inventory, recipe.resultType));
await inventory.save();
// Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`.
res.json({ inventoryChanges: inventoryChanges });
} else if (data.Action == "Pause") {
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
res.status(400).send("-1").end();
return;
}
const project = guild.TechProjects!.find(x => x.ItemType == data.RecipeType)!;
project.State = -2;
guild.ActiveDojoColorResearch = "";
await guild.save();
await removePigmentsFromGuildMembers(guild._id);
res.end();
} else if (data.Action == "Unpause") {
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
res.status(400).send("-1").end();
return;
}
const project = guild.TechProjects!.find(x => x.ItemType == data.RecipeType)!;
project.State = 0;
guild.ActiveDojoColorResearch = data.RecipeType;
await guild.save();
res.end();
} else {
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
throw new Error(`unknown guildTech action: ${data.Action}`);
}
};
const processFundedProject = (
guild: TGuildDatabaseDocument,
techProject: ITechProjectDatabase,
recipe: IDojoResearch
): void => {
techProject.State = 1;
techProject.CompletionDate = new Date(Date.now() + (config.noDojoResearchTime ? 0 : recipe.time) * 1000);
if (recipe.guildXpValue) {
guild.XP += recipe.guildXpValue;
}
setTechLogState(guild, techProject.ItemType, config.noDojoResearchTime ? 4 : 3, techProject.CompletionDate);
};
const setTechLogState = (
guild: TGuildDatabaseDocument,
type: string,
state: number,
dateTime: Date | undefined = undefined
): boolean => {
guild.TechChanges ??= [];
const entry = guild.TechChanges.find(x => x.details == type);
if (entry) {
if (entry.entryType == state) {
return false;
}
entry.dateTime = dateTime;
entry.entryType = state;
} else {
guild.TechChanges.push({
dateTime: dateTime,
entryType: state,
details: type
});
}
return true;
};
type TGuildTechRequest =
| { Action: "Sync" | "SomethingElseThatWeMightNotKnowAbout" }
| IGuildTechBasicRequest
| IGuildTechContributeRequest;
interface IGuildTechBasicRequest {
Action: "Start" | "Fabricate" | "Pause" | "Unpause";
Mode: "Guild";
RecipeType: string;
}
interface IGuildTechBuyRequest {
Action: string;
Mode: "Guild";
RecipeType: string;
}
interface IGuildTechContributeRequest {
Action: "Contribute";
ResearchId: "";
RecipeType: string;
RegularCredits: number;
MiscItems: IMiscItem[];
VaultCredits: number;
VaultMiscItems: IMiscItem[];
}

View File

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

View File

@ -1,87 +1,8 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { Inbox } from "@/src/models/inboxModel"; import inbox from "@/static/fixed_responses/inbox.json";
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";
export const inboxController: RequestHandler = async (req, res) => { const inboxController: RequestHandler = (_req, res) => {
const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query; res.json(inbox);
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 });
}
}; };
export { inboxController };

View File

@ -1,34 +1,18 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getInventory, addMiscItems, updateCurrency, addRecipes, freeUpSlot } from "@/src/services/inventoryService"; import { getInventory, addMiscItems } from "@/src/services/inventoryService";
import { IOid } from "@/src/types/commonTypes"; import { IOid } from "@/src/types/commonTypes";
import {
IConsumedSuit,
IHelminthFoodRecord,
IInfestedFoundryClient,
IInfestedFoundryDatabase,
IInventoryClient,
IMiscItem,
InventorySlot,
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";
import { config } from "@/src/services/configService";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const infestedFoundryController: RequestHandler = async (req, res) => { export const infestedFoundryController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
switch (req.query.mode) { switch (req.query.mode) {
case "s": { case "s": {
// shard installation // shard installation
const request = getJSONfromString<IShardInstallRequest>(String(req.body)); const request = getJSONfromString(String(req.body)) as IShardInstallRequest;
const inventory = await getInventory(accountId); 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) { if (!suit.ArchonCrystalUpgrades || suit.ArchonCrystalUpgrades.length != 5) {
suit.ArchonCrystalUpgrades = [{}, {}, {}, {}, {}]; suit.ArchonCrystalUpgrades = [{}, {}, {}, {}, {}];
} }
@ -52,52 +36,9 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
break; 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)!;
const miscItemChanges: IMiscItem[] = [];
if (suit.ArchonCrystalUpgrades![request.Slot].Color) {
// refund shard
const shard = Object.entries(colorToShard).find(
([color]) => color == suit.ArchonCrystalUpgrades![request.Slot].Color
)![1];
miscItemChanges.push({
ItemType: shard,
ItemCount: 1
});
addMiscItems(inventory, miscItemChanges);
}
// remove from suit
suit.ArchonCrystalUpgrades![request.Slot] = {};
if (!config.infiniteHelminthMaterials) {
// remove bile
const bile = inventory.InfestedFoundry!.Resources!.find(
x => x.ItemType == "/Lotus/Types/Items/InfestedFoundry/HelminthBile"
)!;
bile.Count -= 300;
}
await inventory.save();
const infestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry!;
applyCheatsToInfestedFoundry(infestedFoundry);
res.json({
InventoryChanges: {
MiscItems: miscItemChanges,
InfestedFoundry: infestedFoundry
}
});
break;
}
case "n": { case "n": {
// name the beast // name the beast
const request = getJSONfromString<IHelminthNameRequest>(String(req.body)); const request = getJSONfromString(String(req.body)) as IHelminthNameRequest;
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
inventory.InfestedFoundry ??= {}; inventory.InfestedFoundry ??= {};
inventory.InfestedFoundry.Name = request.newName; inventory.InfestedFoundry.Name = request.newName;
@ -112,251 +53,13 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
break; break;
} }
case "c": { case "o": // offerings update
// consume items // {"OfferingsIndex":540,"SuitTypes":["/Lotus/Powersuits/PaxDuviricus/PaxDuviricusBaseSuit","/Lotus/Powersuits/Nezha/NezhaBaseSuit","/Lotus/Powersuits/Devourer/DevourerBaseSuit"],"Extra":false}
res.status(404).end();
if (config.infiniteHelminthMaterials) {
res.status(400).end();
return;
}
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(Date.now() / 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
}
});
break; 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();
const infestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry!;
applyCheatsToInfestedFoundry(infestedFoundry);
res.json({
InventoryChanges: {
InfestedFoundry: infestedFoundry
}
});
break;
}
case "a": {
// subsume warframe
const request = getJSONfromString<IHelminthSubsumeRequest>(String(req.body));
const inventory = await getInventory(accountId);
const recipe = getRecipe(request.Recipe)!;
if (!config.infiniteHelminthMaterials) {
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[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(Date.now() + 24 * 60 * 60 * 1000);
const recipeChanges = addInfestedFoundryXP(inventory.InfestedFoundry!, 1600_00);
addRecipes(inventory, recipeChanges);
freeUpSlot(inventory, InventorySlot.SUITS);
await inventory.save();
const infestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry!;
applyCheatsToInfestedFoundry(infestedFoundry);
res.json({
InventoryChanges: {
Recipes: recipeChanges,
RemovedIdItems: [
{
ItemId: request.SuitId
}
],
SuitBin: {
count: -1,
platinum: 0,
Slots: 1
},
InfestedFoundry: infestedFoundry
}
});
break;
}
case "r": {
// rush subsume
const inventory = await getInventory(accountId);
const currencyChanges = updateCurrency(inventory, 50, true);
const recipeChanges = handleSubsumeCompletion(inventory);
await inventory.save();
const infestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry!;
applyCheatsToInfestedFoundry(infestedFoundry);
res.json({
InventoryChanges: {
...currencyChanges,
Recipes: recipeChanges,
InfestedFoundry: 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(Date.now() + 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);
if (!config.infiniteHelminthMaterials) {
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();
const infestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry!;
applyCheatsToInfestedFoundry(infestedFoundry);
res.json({
SuitId: request.SuitId,
OffensiveUpgrade: request.OffensiveUpgradeType,
DefensiveUpgrade: request.DefensiveUpgradeType,
UpgradesExpiry: toMongoDate(upgradesExpiry),
InventoryChanges: {
Recipes: recipeChanges,
InfestedFoundry: 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: default:
logger.debug(`data provided to ${req.path}: ${String(req.body)}`); throw new Error(`unhandled infestedFoundry mode: ${req.query.mode}`);
throw new Error(`unhandled infestedFoundry mode: ${String(req.query.mode)}`);
} }
}; };
@ -367,179 +70,21 @@ interface IShardInstallRequest {
Color: string; Color: string;
} }
interface IShardUninstallRequest {
SuitId: IOid;
Slot: number;
}
interface IHelminthNameRequest { interface IHelminthNameRequest {
newName: string; newName: string;
} }
interface IHelminthFeedRequest { const colorToShard: Record<string, string> = {
ResourceContributions: { ACC_RED: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalAmar",
ItemType: string; ACC_RED_MYTHIC: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalAmarMythic",
Date: number; // unix timestamp 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",
export const addInfestedFoundryXP = (infestedFoundry: IInfestedFoundryDatabase, delta: number): ITypeCount[] => { ACC_GREEN: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalGreen",
const recipeChanges: ITypeCount[] = []; ACC_GREEN_MYTHIC: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalGreenMythic",
infestedFoundry.XP ??= 0; ACC_ORANGE: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalOrange",
const prevXP = infestedFoundry.XP; ACC_ORANGE_MYTHIC: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalOrangeMythic",
infestedFoundry.XP += delta; ACC_PURPLE: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalViolet",
if (prevXP < 2250_00 && infestedFoundry.XP >= 2250_00) { ACC_PURPLE_MYTHIC: "/Lotus/Types/Gameplay/NarmerSorties/ArchonCrystalVioletMythic"
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;
};
export const applyCheatsToInfestedFoundry = (infestedFoundry: IInfestedFoundryClient): void => {
if (config.infiniteHelminthMaterials) {
infestedFoundry.Resources = [
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthCalx", Count: 1000 },
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthBiotics", Count: 1000 },
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthSynthetics", Count: 1000 },
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthPheromones", Count: 1000 },
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthBile", Count: 1000 },
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthOxides", Count: 1000 }
];
}
};
interface IHelminthOfferingsUpdate {
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;
}; };

View File

@ -1,24 +1,20 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { Inventory, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { toInventoryResponse } from "@/src/helpers/inventoryHelpers";
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
import { config } from "@/src/services/configService"; import { config } from "@/src/services/configService";
import allDialogue from "@/static/fixed_responses/allDialogue.json"; import { IInventoryDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
import { ILoadoutDatabase } from "@/src/types/saveLoadoutTypes"; //import new_inventory from "@/static/fixed_responses/postTutorialInventory.json";
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 { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "./infestedFoundryController";
import { addMiscItems, allDailyAffiliationKeys, createLibraryDailyTask } from "@/src/services/inventoryService";
import { logger } from "@/src/utils/logger";
export const inventoryController: RequestHandler = async (request, response) => { // eslint-disable-next-line @typescript-eslint/no-misused-promises
const accountId = await getAccountIdForRequest(request); 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: accountId }); const inventory = await Inventory.findOne({ accountOwnerId: accountId });
@ -27,162 +23,49 @@ export const inventoryController: RequestHandler = async (request, response) =>
return; return;
} }
// Handle daily reset //TODO: make a function that converts from database representation to client
if (!inventory.NextRefill || Date.now() >= inventory.NextRefill.getTime()) { const inventoryJSON: IInventoryDatabase = inventory.toJSON();
for (const key of allDailyAffiliationKeys) { console.log(inventoryJSON.Ships);
inventory[key] = 16000 + inventory.PlayerLevel * 500;
}
inventory.DailyFocus = 250000 + inventory.PlayerLevel * 5000;
inventory.LibraryAvailableDailyTaskInfo = createLibraryDailyTask(); const inventoryResponse = toInventoryResponse(inventoryJSON);
if (inventory.NextRefill) { if (config.infiniteResources) {
if (config.noArgonCrystalDecay) {
inventory.FoundToday = undefined;
} else {
const lastLoginDay = Math.trunc(inventory.NextRefill.getTime() / 86400000) - 1;
const today = Math.trunc(Date.now() / 86400000);
const daysPassed = today - lastLoginDay;
for (let i = 0; i != daysPassed; ++i) {
const numArgonCrystals =
inventory.MiscItems.find(x => x.ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal")
?.ItemCount ?? 0;
if (numArgonCrystals == 0) {
break;
}
const numStableArgonCrystals =
inventory.FoundToday?.find(x => x.ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal")
?.ItemCount ?? 0;
const numDecayingArgonCrystals = numArgonCrystals - numStableArgonCrystals;
const numDecayingArgonCrystalsToRemove = Math.ceil(numDecayingArgonCrystals / 2);
logger.debug(`ticking argon crystals for day ${i + 1} of ${daysPassed}`, {
numArgonCrystals,
numStableArgonCrystals,
numDecayingArgonCrystals,
numDecayingArgonCrystalsToRemove
});
// Remove half of owned decaying argon crystals
addMiscItems(inventory, [
{
ItemType: "/Lotus/Types/Items/MiscItems/ArgonCrystal",
ItemCount: numDecayingArgonCrystalsToRemove * -1
}
]);
// All stable argon crystals are now decaying
inventory.FoundToday = undefined;
}
}
}
inventory.NextRefill = new Date((Math.trunc(Date.now() / 86400000) + 1) * 86400000);
await inventory.save();
}
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) {
inventoryResponse.RegularCredits = 999999999; inventoryResponse.RegularCredits = 999999999;
}
if (config.infinitePlatinum) {
inventoryResponse.PremiumCreditsFree = 0;
inventoryResponse.PremiumCredits = 999999999; inventoryResponse.PremiumCredits = 999999999;
} }
if (config.infiniteEndo) {
inventoryResponse.FusionPoints = 999999999;
}
if (config.infiniteRegalAya) {
inventoryResponse.PrimeTokens = 999999999;
}
if (config.skipAllDialogue) { // if (config.unlockAllMissions) {
inventoryResponse.TauntHistory = [ // //inventoryResponse.Missions = allMissions;
{ // //inventoryResponse.NodeIntrosCompleted.push("TeshinHardModeUnlocked");
node: "TreasureTutorial", // }
state: "TS_COMPLETED"
}
];
for (const str of allDialogue) {
addString(inventoryResponse.NodeIntrosCompleted, str);
}
}
if (config.unlockAllMissions) { // if (config.unlockAllMissions) {
inventoryResponse.Missions = []; // //inventoryResponse.Missions = allMissions;
for (const tag of Object.keys(ExportRegions)) { // //addString(inventoryResponse.NodeIntrosCompleted, "TeshinHardModeUnlocked");
inventoryResponse.Missions.push({ // }
Completes: 1,
Tier: 1,
Tag: tag
});
}
addString(inventoryResponse.NodeIntrosCompleted, "TeshinHardModeUnlocked");
}
if (config.unlockAllShipDecorations) { // if (config.unlockAllFlavourItems) {
inventoryResponse.ShipDecorations = []; // inventoryResponse.FlavourItems = [];
for (const [uniqueName, item] of Object.entries(ExportResources)) { // for (const uniqueName in ExportFlavour) {
if (item.productCategory == "ShipDecorations") { // inventoryResponse.FlavourItems.push({ ItemType: uniqueName });
inventoryResponse.ShipDecorations.push({ ItemType: uniqueName, ItemCount: 1 }); // }
} // }
}
}
if (config.unlockAllFlavourItems) { // if (config.unlockAllSkins) {
inventoryResponse.FlavourItems = []; // inventoryResponse.WeaponSkins = [];
for (const uniqueName in ExportFlavour) { // for (const uniqueName in ExportCustoms) {
inventoryResponse.FlavourItems.push({ ItemType: uniqueName }); // inventoryResponse.WeaponSkins.push({
} // ItemId: {
} // $id: "000000000000000000000000"
// },
if (config.unlockAllSkins) { // ItemType: uniqueName
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 (typeof config.spoofMasteryRank === "number" && config.spoofMasteryRank >= 0) { if (typeof config.spoofMasteryRank === "number" && config.spoofMasteryRank >= 0) {
inventoryResponse.PlayerLevel = config.spoofMasteryRank; 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. // This client has not been patched to accept any mastery rank, need to fake the XP.
inventoryResponse.XPInfo = []; inventoryResponse.XPInfo = [];
let numFrames = getExpRequiredForMr(Math.min(config.spoofMasteryRank, 5030)) / 6000; let numFrames = getExpRequiredForMr(Math.min(config.spoofMasteryRank, 5030)) / 6000;
@ -195,81 +78,18 @@ export const getInventoryResponse = async (
} }
} }
if (config.universalPolarityEverywhere) { // Fix for #380
const Polarity: IPolarity[] = []; inventoryResponse.NextRefill = { $date: { $numberLong: "9999999999999" } };
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) { response.json(inventoryResponse);
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) {
const spoofedDailyAffiliation = Math.max(999_999, 16000 + inventoryResponse.PlayerLevel * 500);
for (const key of allDailyAffiliationKeys) {
inventoryResponse[key] = spoofedDailyAffiliation;
}
}
if (inventoryResponse.InfestedFoundry) {
applyCheatsToInfestedFoundry(inventoryResponse.InfestedFoundry);
}
// Omitting this field so opening the navigation resyncs the inventory which is more desirable for typical usage.
//inventoryResponse.LastInventorySync = toOid(new Types.ObjectId());
// Set 2FA enabled so trading post can be used
inventoryResponse.HWIDProtectEnabled = true;
return inventoryResponse;
}; };
export const addString = (arr: string[], str: string): void => { /*
const addString = (arr: string[], str: string): void => {
if (!arr.find(x => x == str)) { if (!arr.find(x => x == str)) {
arr.push(str); arr.push(str);
} }
}; };*/
const getExpRequiredForMr = (rank: number): number => { const getExpRequiredForMr = (rank: number): number => {
if (rank <= 30) { if (rank <= 30) {
@ -278,30 +98,4 @@ const getExpRequiredForMr = (rank: number): number => {
return 2_250_000 + 147_500 * (rank - 30); return 2_250_000 + 147_500 * (rank - 30);
}; };
const resourceInheritsFrom = (resourceName: string, targetName: string): boolean => { export { inventoryController };
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;
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
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.
export 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;
};

View File

@ -1,9 +1,8 @@
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory, updateCurrency } from "@/src/services/inventoryService"; import { updateCurrency } from "@/src/services/inventoryService";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { updateSlots } from "@/src/services/inventoryService"; import { updateSlots } from "@/src/services/inventoryService";
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes"; import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
import { logger } from "@/src/utils/logger";
/* /*
loadout slots are additionally purchased slots only loadout slots are additionally purchased slots only
@ -19,22 +18,19 @@ import { logger } from "@/src/utils/logger";
number of frames = extra - slots + 2 number of frames = extra - slots + 2
*/ */
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const inventorySlotsController: RequestHandler = async (req, res) => { export const inventorySlotsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const body = JSON.parse(req.body as string) as IInventorySlotsRequest; //const body = JSON.parse(req.body as string) as IInventorySlotsRequest;
if (body.Bin != InventorySlot.SUITS && body.Bin != InventorySlot.PVE_LOADOUTS) { //console.log(body);
logger.warn(`unexpected slot purchase of type ${body.Bin}, account may be overcharged`);
}
const inventory = await getInventory(accountId); //TODO: check which slot was purchased because pvpBonus is also possible
const currencyChanges = updateCurrency(inventory, 20, true);
updateSlots(inventory, body.Bin, 1, 1); const currencyChanges = await updateCurrency(20, true, accountId);
await inventory.save(); await updateSlots(accountId, InventorySlot.PVE_LOADOUTS, 1, 1);
//console.log({ InventoryChanges: currencyChanges }, " added loadout changes:");
res.json({ InventoryChanges: currencyChanges }); res.json({ InventoryChanges: currencyChanges });
}; };
interface IInventorySlotsRequest {
Bin: InventorySlot;
}

View File

@ -2,13 +2,12 @@ import { RequestHandler } from "express";
import { getSessionByID } from "@/src/managers/sessionManager"; import { getSessionByID } from "@/src/managers/sessionManager";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
export const joinSessionController: RequestHandler = (req, res) => { const joinSessionController: RequestHandler = (_req, res) => {
const reqBody = JSON.parse(String(req.body)) as IJoinSessionRequest; const reqBody = JSON.parse(String(_req.body));
logger.debug(`JoinSession Request`, { reqBody }); logger.debug(`JoinSession Request`, { reqBody });
const session = getSessionByID(reqBody.sessionIds[0]); const req = JSON.parse(String(_req.body));
res.json({ rewardSeed: session?.rewardSeed, sessionId: { $oid: session?.sessionId } }); const session = getSessionByID(req.sessionIds[0] as string);
res.json({ rewardSeed: session?.rewardSeed, sessionId: { $id: session?.sessionId } });
}; };
interface IJoinSessionRequest { export { joinSessionController };
sessionIds: string[];
}

View File

@ -1,51 +1,43 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { config } from "@/src/services/configService"; 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 { Account } from "@/src/models/loginModel";
import { createAccount, isCorrectPassword, isNameTaken } from "@/src/services/loginService"; import { createAccount, isCorrectPassword } from "@/src/services/loginService";
import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes"; import { ILoginResponse } from "@/src/types/loginTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
export const loginController: RequestHandler = async (request, response) => { // eslint-disable-next-line @typescript-eslint/no-misused-promises
const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object 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 account = await Account.findOne({ email: loginRequest.email });
const nonce = Math.round(Math.random() * Number.MAX_SAFE_INTEGER); 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;
const myAddress = request.host.indexOf("warframe.com") == -1 ? request.host : config.myAddress;
if (!account && config.autoCreateAccount && loginRequest.ClientType != "webui") { if (!account && config.autoCreateAccount && loginRequest.ClientType != "webui") {
try { 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({ const newAccount = await createAccount({
email: loginRequest.email, email: loginRequest.email,
password: loginRequest.password, password: loginRequest.password,
DisplayName: name, DisplayName: loginRequest.email.substring(0, loginRequest.email.indexOf("@")),
CountryCode: loginRequest.lang.toUpperCase(),
ClientType: loginRequest.ClientType,
CrossPlatformAllowed: true,
ForceLogoutVersion: 0,
ConsentNeeded: false,
TrackedSettings: [],
Nonce: nonce Nonce: nonce
}); });
logger.debug("created new account"); logger.debug("created new account");
response.json(createLoginResponse(myAddress, 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; return;
} catch (error: unknown) { } catch (error: unknown) {
if (error instanceof Error) { if (error instanceof Error) {
@ -60,46 +52,21 @@ export const loginController: RequestHandler = async (request, response) => {
return; return;
} }
if (loginRequest.ClientType == "webui") { if (account.Nonce == 0 || loginRequest.ClientType != "webui") {
if (!account.Nonce) {
account.ClientType = "webui";
account.Nonce = nonce; account.Nonce = nonce;
} }
} else {
if (account.Nonce && account.ClientType != "webui" && !account.Dropped && !loginRequest.kick) {
response.status(400).json({ error: "nonce still set" });
return;
}
account.ClientType = loginRequest.ClientType;
account.Nonce = nonce;
account.CountryCode = loginRequest.lang.toUpperCase();
}
await account.save(); await account.save();
response.json(createLoginResponse(myAddress, account.toJSON(), buildLabel)); const { email, password, ...databaseAccount } = account.toJSON();
const newLoginResponse: ILoginResponse = {
...databaseAccount,
BuildLabel: buildConfig.buildLabel,
NatHash: "0",
SteamId: "0"
}; };
const createLoginResponse = (myAddress: string, account: IDatabaseAccountJson, buildLabel: string): ILoginResponse => { response.json(newLoginResponse);
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: [],
IRC: config.myIrcAddresses ?? [myAddress],
platformCDNs: [`https://${myAddress}/`],
HUB: `https://${myAddress}/api/`,
NRS: config.NRS,
DTLS: 99,
BuildLabel: buildLabel,
MatchmakingBuildId: buildConfig.matchmakingBuildId
};
}; };
export { loginController };

View File

@ -1,40 +1,8 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountForRequest } from "@/src/services/loginService"; import loginRewards from "@/static/fixed_responses/loginRewards.json";
import { claimLoginReward, getRandomLoginRewards, ILoginRewardsReponse } from "@/src/services/loginRewardService";
import { getInventory } from "@/src/services/inventoryService";
export const loginRewardsController: RequestHandler = async (req, res) => { const loginRewardsController: RequestHandler = (_req, res) => {
const account = await getAccountForRequest(req); res.json(loginRewards);
const today = Math.trunc(Date.now() / 86400000) * 86400;
if (today == account.LastLoginRewardDate) {
res.end();
return;
}
account.LoginDays += 1;
account.LastLoginRewardDate = today;
await account.save();
const inventory = await getInventory(account._id.toString());
const randomRewards = getRandomLoginRewards(account, inventory);
const isMilestoneDay = account.LoginDays == 5 || account.LoginDays % 50 == 0;
const response: ILoginRewardsReponse = {
DailyTributeInfo: {
Rewards: randomRewards,
IsMilestoneDay: isMilestoneDay,
IsChooseRewardSet: randomRewards.length != 1,
LoginDays: account.LoginDays,
//NextMilestoneReward: "",
NextMilestoneDay: account.LoginDays < 5 ? 5 : (Math.trunc(account.LoginDays / 50) + 1) * 50,
HasChosenReward: false
},
LastLoginRewardDate: today
};
if (!isMilestoneDay && randomRewards.length == 1) {
response.DailyTributeInfo.HasChosenReward = true;
response.DailyTributeInfo.ChosenReward = randomRewards[0];
response.DailyTributeInfo.NewInventory = await claimLoginReward(inventory, randomRewards[0]);
await inventory.save();
}
res.json(response);
}; };
export { loginRewardsController };

View File

@ -1,57 +0,0 @@
import { getInventory } from "@/src/services/inventoryService";
import { claimLoginReward, getRandomLoginRewards } from "@/src/services/loginRewardService";
import { getAccountForRequest } from "@/src/services/loginService";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express";
export const loginRewardsSelectionController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
const inventory = await getInventory(account._id.toString());
const body = JSON.parse(String(req.body)) as ILoginRewardsSelectionRequest;
const isMilestoneDay = account.LoginDays == 5 || account.LoginDays % 50 == 0;
if (body.IsMilestoneReward != isMilestoneDay) {
logger.warn(`Client disagrees on login milestone (got ${body.IsMilestoneReward}, expected ${isMilestoneDay})`);
}
let chosenReward;
let inventoryChanges: IInventoryChanges;
if (body.IsMilestoneReward) {
chosenReward = {
RewardType: "RT_STORE_ITEM",
StoreItemType: body.ChosenReward
};
inventoryChanges = (await handleStoreItemAcquisition(body.ChosenReward, inventory)).InventoryChanges;
if (!evergreenRewards.find(x => x == body.ChosenReward)) {
inventory.LoginMilestoneRewards.push(body.ChosenReward);
}
} else {
const randomRewards = getRandomLoginRewards(account, inventory);
chosenReward = randomRewards.find(x => x.StoreItemType == body.ChosenReward)!;
inventoryChanges = await claimLoginReward(inventory, chosenReward);
}
await inventory.save();
res.json({
DailyTributeInfo: {
NewInventory: inventoryChanges,
ChosenReward: chosenReward
}
});
};
interface ILoginRewardsSelectionRequest {
ChosenReward: string;
IsMilestoneReward: boolean;
}
const evergreenRewards = [
"/Lotus/Types/StoreItems/Packages/EvergreenTripleForma",
"/Lotus/Types/StoreItems/Packages/EvergreenTripleRifleRiven",
"/Lotus/Types/StoreItems/Packages/EvergreenTripleMeleeRiven",
"/Lotus/Types/StoreItems/Packages/EvergreenTripleSecondaryRiven",
"/Lotus/Types/StoreItems/Packages/EvergreenWeaponSlots",
"/Lotus/Types/StoreItems/Packages/EvergreenKuva",
"/Lotus/Types/StoreItems/Packages/EvergreenBoosters",
"/Lotus/Types/StoreItems/Packages/EvergreenEndo",
"/Lotus/Types/StoreItems/Packages/EvergreenExilus"
];

View File

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

View File

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

View File

@ -1,44 +1,48 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
getInventory, import { getInventory, updateCurrency, addEquipment, addMiscItems } from "@/src/services/inventoryService";
updateCurrency,
addEquipment, const modularWeaponTypes: Record<string, TEquipmentKey> = {
addMiscItems, "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam": "LongGuns",
applyDefaultUpgrades, "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary": "Pistols",
occupySlot, "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam": "Pistols",
productCategoryToInventoryBin "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun": "Pistols",
} from "@/src/services/inventoryService"; "/Lotus/Weapons/Ostron/Melee/LotusModularWeapon": "Melee",
import { IInventoryChanges } from "@/src/types/purchaseTypes"; "/Lotus/Weapons/Sentients/OperatorAmplifiers/OperatorAmpWeapon": "OperatorAmps",
import { getDefaultUpgrades } from "@/src/services/itemDataService"; "/Lotus/Types/Vehicles/Hoverboard/HoverboardSuit": "Hoverboards",
import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper"; "/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit": "MoaPets",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetAPowerSuit": "MoaPets",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetBPowerSuit": "MoaPets",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetCPowerSuit": "MoaPets"
};
interface IModularCraftRequest { interface IModularCraftRequest {
WeaponType: string; WeaponType: string;
Parts: string[]; Parts: string[];
} }
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const modularWeaponCraftingController: RequestHandler = async (req, res) => { export const modularWeaponCraftingController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); 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)) { if (!(data.WeaponType in modularWeaponTypes)) {
throw new Error(`unknown modular weapon type: ${data.WeaponType}`); throw new Error(`unknown modular weapon type: ${data.WeaponType}`);
} }
const category = modularWeaponTypes[data.WeaponType]; const category = modularWeaponTypes[data.WeaponType];
const inventory = await getInventory(accountId);
const defaultUpgrades = getDefaultUpgrades(data.Parts); // Give weapon
const configs = applyDefaultUpgrades(inventory, defaultUpgrades); const weapon = await addEquipment(category, data.WeaponType, accountId, data.Parts);
const inventoryChanges: IInventoryChanges = {
...addEquipment(inventory, category, data.WeaponType, data.Parts, {}, { Configs: configs }),
...occupySlot(inventory, productCategoryToInventoryBin(category)!, false)
};
if (defaultUpgrades) {
inventoryChanges.RawUpgrades = defaultUpgrades.map(x => ({ ItemType: x.ItemType, ItemCount: 1 }));
}
// Remove credits & parts // Remove credits
const currencyChanges = await updateCurrency(
category == "Hoverboards" || category == "MoaPets" ? 5000 : 4000,
false,
accountId
);
// Remove parts
const miscItemChanges = []; const miscItemChanges = [];
for (const part of data.Parts) { for (const part of data.Parts) {
miscItemChanges.push({ miscItemChanges.push({
@ -46,21 +50,15 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
ItemCount: -1 ItemCount: -1
}); });
} }
const currencyChanges = updateCurrency( const inventory = await getInventory(accountId);
inventory,
category == "Hoverboards" || category == "MoaPets" || category == "LongGuns" || category == "Pistols"
? 5000
: 4000, // Definitely correct for Melee & OperatorAmps
false
);
addMiscItems(inventory, miscItemChanges); addMiscItems(inventory, miscItemChanges);
await inventory.save(); await inventory.save();
// Tell client what we did // Tell client what we did
res.json({ res.json({
InventoryChanges: { InventoryChanges: {
...inventoryChanges,
...currencyChanges, ...currencyChanges,
[category]: [weapon],
MiscItems: miscItemChanges MiscItems: miscItemChanges
} }
}); });

View File

@ -1,190 +1,8 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { ExportWeapons } from "warframe-public-export-plus"; import modularWeaponSale from "@/static/fixed_responses/modularWeaponSale.json";
import { IMongoDate } from "@/src/types/commonTypes";
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
import { CRng } from "@/src/services/rngService";
import { ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import {
addEquipment,
applyDefaultUpgrades,
getInventory,
occupySlot,
productCategoryToInventoryBin,
updateCurrency
} from "@/src/services/inventoryService";
import { getDefaultUpgrades } from "@/src/services/itemDataService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
export const modularWeaponSaleController: RequestHandler = async (req, res) => { const modularWeaponSaleController: RequestHandler = (_req, res) => {
const partTypeToParts: Record<string, string[]> = {}; res.json(modularWeaponSale);
for (const [uniqueName, data] of Object.entries(ExportWeapons)) {
if (data.partType) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
partTypeToParts[data.partType] ??= [];
partTypeToParts[data.partType].push(uniqueName);
}
}
if (req.query.op == "SyncAll") {
res.json({
SaleInfos: getSaleInfos(partTypeToParts, Math.trunc(Date.now() / 86400000))
});
} else if (req.query.op == "Purchase") {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const payload = getJSONfromString<IModularWeaponPurchaseRequest>(String(req.body));
const weaponInfo = getSaleInfos(partTypeToParts, payload.Revision).find(x => x.Name == payload.SaleName)!
.Weapons[payload.ItemIndex];
const category = modularWeaponTypes[weaponInfo.ItemType];
const defaultUpgrades = getDefaultUpgrades(weaponInfo.ModularParts);
const configs = applyDefaultUpgrades(inventory, defaultUpgrades);
const inventoryChanges: IInventoryChanges = {
...addEquipment(
inventory,
category,
weaponInfo.ItemType,
weaponInfo.ModularParts,
{},
{
Features: EquipmentFeatures.DOUBLE_CAPACITY | EquipmentFeatures.GILDED,
ItemName: payload.ItemName,
Configs: configs,
Polarity: [
{
Slot: payload.PolarizeSlot,
Value: payload.PolarizeValue
}
]
}
),
...occupySlot(inventory, productCategoryToInventoryBin(category)!, true),
...updateCurrency(inventory, weaponInfo.PremiumPrice, true)
};
if (defaultUpgrades) {
inventoryChanges.RawUpgrades = defaultUpgrades.map(x => ({ ItemType: x.ItemType, ItemCount: 1 }));
}
await inventory.save();
res.json({
InventoryChanges: inventoryChanges
});
} else {
throw new Error(`unknown modularWeaponSale op: ${String(req.query.op)}`);
}
}; };
const getSaleInfos = (partTypeToParts: Record<string, string[]>, day: number): IModularWeaponSaleInfo[] => { export { modularWeaponSaleController };
const kitgunIsPrimary: boolean = (day & 1) != 0;
return [
getModularWeaponSale(
partTypeToParts,
day,
"Ostron",
["LWPT_HILT", "LWPT_BLADE", "LWPT_HILT_WEIGHT"],
() => "/Lotus/Weapons/Ostron/Melee/LotusModularWeapon"
),
getModularWeaponSale(
partTypeToParts,
day,
"SolarisUnitedHoverboard",
["LWPT_HB_DECK", "LWPT_HB_ENGINE", "LWPT_HB_FRONT", "LWPT_HB_JET"],
() => "/Lotus/Types/Vehicles/Hoverboard/HoverboardSuit"
),
getModularWeaponSale(
partTypeToParts,
day,
"SolarisUnitedMoaPet",
["LWPT_MOA_LEG", "LWPT_MOA_HEAD", "LWPT_MOA_ENGINE", "LWPT_MOA_PAYLOAD"],
() => "/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit"
),
getModularWeaponSale(
partTypeToParts,
day,
"SolarisUnitedKitGun",
[
kitgunIsPrimary ? "LWPT_GUN_PRIMARY_HANDLE" : "LWPT_GUN_SECONDARY_HANDLE",
"LWPT_GUN_BARREL",
"LWPT_GUN_CLIP"
],
(parts: string[]) => {
const barrel = parts[1];
const gunType = ExportWeapons[barrel].gunType!;
if (kitgunIsPrimary) {
return {
GT_RIFLE: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
GT_SHOTGUN: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun",
GT_BEAM: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam"
}[gunType];
} else {
return {
GT_RIFLE: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary",
GT_SHOTGUN: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun",
GT_BEAM: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam"
}[gunType];
}
}
)
];
};
const priceFactor: Record<string, number> = {
Ostron: 0.9,
SolarisUnitedHoverboard: 0.85,
SolarisUnitedMoaPet: 0.95,
SolarisUnitedKitGun: 0.9
};
const getModularWeaponSale = (
partTypeToParts: Record<string, string[]>,
day: number,
name: string,
partTypes: string[],
getItemType: (parts: string[]) => string
): IModularWeaponSaleInfo => {
const rng = new CRng(day);
const parts = partTypes.map(partType => rng.randomElement(partTypeToParts[partType]));
let partsCost = 0;
for (const part of parts) {
const meta = ExportWeapons[part];
if (!meta.premiumPrice) {
throw new Error(`no premium price for ${part}`);
}
partsCost += meta.premiumPrice;
}
return {
Name: name,
Expiry: toMongoDate(new Date((day + 1) * 86400000)),
Revision: day,
Weapons: [
{
ItemType: getItemType(parts),
PremiumPrice: Math.trunc(partsCost * priceFactor[name]),
ModularParts: parts
}
]
};
};
interface IModularWeaponSaleInfo {
Name: string;
Expiry: IMongoDate;
Revision: number;
Weapons: IModularWeaponSaleItem[];
}
interface IModularWeaponSaleItem {
ItemType: string;
PremiumPrice: number;
ModularParts: string[];
}
interface IModularWeaponPurchaseRequest {
SaleName: string;
ItemIndex: number;
Revision: number;
ItemName: string;
PolarizeSlot: number;
PolarizeValue: ArtifactPolarity;
}

View File

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

View File

@ -1,209 +0,0 @@
import { getInfNodes, getNemesisPasscode } from "@/src/helpers/nemesisHelpers";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { freeUpSlot, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { SRng } from "@/src/services/rngService";
import { IMongoDate, IOid } from "@/src/types/commonTypes";
import { IInnateDamageFingerprint, InventorySlot, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express";
export const nemesisController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
if ((req.query.mode as string) == "f") {
const body = getJSONfromString<IValenceFusionRequest>(String(req.body));
const inventory = await getInventory(accountId, body.Category + " WeaponBin");
const destWeapon = inventory[body.Category].id(body.DestWeapon.$oid)!;
const sourceWeapon = inventory[body.Category].id(body.SourceWeapon.$oid)!;
const destFingerprint = JSON.parse(destWeapon.UpgradeFingerprint!) as IInnateDamageFingerprint;
const sourceFingerprint = JSON.parse(sourceWeapon.UpgradeFingerprint!) as IInnateDamageFingerprint;
// Upgrade destination damage type if desireed
if (body.UseSourceDmgType) {
destFingerprint.buffs[0].Tag = sourceFingerprint.buffs[0].Tag;
}
// Upgrade destination damage value
const destDamage = 0.25 + (destFingerprint.buffs[0].Value / 0x3fffffff) * (0.6 - 0.25);
const sourceDamage = 0.25 + (sourceFingerprint.buffs[0].Value / 0x3fffffff) * (0.6 - 0.25);
let newDamage = Math.max(destDamage, sourceDamage) * 1.1;
if (newDamage >= 0.58) {
newDamage = 0.6;
}
destFingerprint.buffs[0].Value = Math.trunc(((newDamage - 0.25) / (0.6 - 0.25)) * 0x3fffffff);
// Commit fingerprint
destWeapon.UpgradeFingerprint = JSON.stringify(destFingerprint);
// Remove source weapon
inventory[body.Category].pull({ _id: body.SourceWeapon.$oid });
freeUpSlot(inventory, InventorySlot.WEAPONS);
await inventory.save();
res.json({
InventoryChanges: {
[body.Category]: [destWeapon.toJSON()]
}
});
} else if ((req.query.mode as string) == "p") {
const inventory = await getInventory(accountId, "Nemesis");
const body = getJSONfromString<INemesisPrespawnCheckRequest>(String(req.body));
const passcode = getNemesisPasscode(inventory.Nemesis!.fp, inventory.Nemesis!.Faction);
let guessResult = 0;
if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
for (let i = 0; i != 3; ++i) {
if (body.guess[i] == passcode[0]) {
guessResult = 1 + i;
break;
}
}
} else {
for (let i = 0; i != 3; ++i) {
if (body.guess[i] == passcode[i]) {
++guessResult;
}
}
}
res.json({ GuessResult: guessResult });
} else if ((req.query.mode as string) == "s") {
const inventory = await getInventory(accountId, "Nemesis");
const body = getJSONfromString<INemesisStartRequest>(String(req.body));
body.target.fp = BigInt(body.target.fp);
let weaponIdx = -1;
if (body.target.Faction != "FC_INFESTATION") {
let weapons: readonly string[];
if (body.target.manifest == "/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionSix") {
weapons = kuvaLichVersionSixWeapons;
} else if (
body.target.manifest == "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionFour" ||
body.target.manifest == "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionThree"
) {
weapons = corpusVersionThreeWeapons;
} else {
throw new Error(`unknown nemesis manifest: ${body.target.manifest}`);
}
const initialWeaponIdx = new SRng(body.target.fp).randomInt(0, weapons.length - 1);
weaponIdx = initialWeaponIdx;
do {
const weapon = weapons[weaponIdx];
if (!body.target.DisallowedWeapons.find(x => x == weapon)) {
break;
}
weaponIdx = (weaponIdx + 1) % weapons.length;
} while (weaponIdx != initialWeaponIdx);
}
inventory.Nemesis = {
fp: body.target.fp,
manifest: body.target.manifest,
KillingSuit: body.target.KillingSuit,
killingDamageType: body.target.killingDamageType,
ShoulderHelmet: body.target.ShoulderHelmet,
WeaponIdx: weaponIdx,
AgentIdx: body.target.AgentIdx,
BirthNode: body.target.BirthNode,
Faction: body.target.Faction,
Rank: 0,
k: false,
Traded: false,
d: new Date(),
InfNodes: getInfNodes(body.target.Faction, 0),
GuessHistory: [],
Hints: [],
HintProgress: 0,
Weakened: body.target.Weakened,
PrevOwners: 0,
HenchmenKilled: 0,
SecondInCommand: body.target.SecondInCommand,
MissionCount: 0,
LastEnc: 0
};
await inventory.save();
res.json({
target: inventory.toJSON().Nemesis
});
} else {
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
throw new Error(`unknown nemesis mode: ${String(req.query.mode)}`);
}
};
interface IValenceFusionRequest {
DestWeapon: IOid;
SourceWeapon: IOid;
Category: TEquipmentKey;
UseSourceDmgType: boolean;
}
interface INemesisStartRequest {
target: {
fp: number | bigint;
manifest: string;
KillingSuit: string;
killingDamageType: number;
ShoulderHelmet: string;
DisallowedWeapons: string[];
WeaponIdx: number;
AgentIdx: number;
BirthNode: string;
Faction: string;
Rank: number;
k: boolean;
Traded: boolean;
d: IMongoDate;
InfNodes: [];
GuessHistory: [];
Hints: [];
HintProgress: number;
Weakened: boolean;
PrevOwners: number;
HenchmenKilled: number;
MissionCount?: number; // Added in 38.5.0
LastEnc?: number; // Added in 38.5.0
SecondInCommand: boolean;
};
}
interface INemesisPrespawnCheckRequest {
guess: number[]; // .length == 3
potency?: number[];
}
const kuvaLichVersionSixWeapons = [
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Drakgoon/KuvaDrakgoon",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Karak/KuvaKarak",
"/Lotus/Weapons/Grineer/Melee/GrnKuvaLichScythe/GrnKuvaLichScytheWeapon",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Kohm/KuvaKohm",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Ogris/KuvaOgris",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Quartakk/KuvaQuartakk",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Tonkor/KuvaTonkor",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Brakk/KuvaBrakk",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Kraken/KuvaKraken",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Seer/KuvaSeer",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Stubba/KuvaStubba",
"/Lotus/Weapons/Grineer/HeavyWeapons/GrnHeavyGrenadeLauncher",
"/Lotus/Weapons/Grineer/LongGuns/GrnKuvaLichRifle/GrnKuvaLichRifleWeapon",
"/Lotus/Weapons/Grineer/Bows/GrnBow/GrnBowWeapon",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hind/KuvaHind",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Nukor/KuvaNukor",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hek/KuvaHekWeapon",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Zarr/KuvaZarr",
"/Lotus/Weapons/Grineer/KuvaLich/HeavyWeapons/Grattler/KuvaGrattler",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Sobek/KuvaSobek"
];
const corpusVersionThreeWeapons = [
"/Lotus/Weapons/Corpus/LongGuns/CrpBriefcaseLauncher/CrpBriefcaseLauncher",
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEArcaPlasmor/CrpBEArcaPlasmor",
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEFluxRifle/CrpBEFluxRifle",
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBETetra/CrpBETetra",
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBECycron/CrpBECycron",
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEDetron/CrpBEDetron",
"/Lotus/Weapons/Corpus/Pistols/CrpIgniterPistol/CrpIgniterPistol",
"/Lotus/Weapons/Corpus/Pistols/CrpBriefcaseAkimbo/CrpBriefcaseAkimboPistol",
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEPlinx/CrpBEPlinxWeapon",
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEGlaxion/CrpBEGlaxion"
];

View File

@ -1,51 +0,0 @@
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes";
import { RequestHandler } from "express";
import { Types } from "mongoose";
import { ExportDojoRecipes } from "warframe-public-export-plus";
export const placeDecoInComponentController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "GuildId LevelKeys");
const guild = await getGuildForRequestEx(req, inventory);
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Decorator))) {
res.json({ DojoRequestStatus: -1 });
return;
}
const request = JSON.parse(String(req.body)) as IPlaceDecoInComponentRequest;
const component = guild.DojoComponents.id(request.ComponentId)!;
if (component.DecoCapacity === undefined) {
component.DecoCapacity = Object.values(ExportDojoRecipes.rooms).find(
x => x.resultType == component.pf
)!.decoCapacity;
}
component.Decos ??= [];
component.Decos.push({
_id: new Types.ObjectId(),
Type: request.Type,
Pos: request.Pos,
Rot: request.Rot,
Name: request.Name
});
const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == request.Type);
if (meta && meta.capacityCost) {
component.DecoCapacity -= meta.capacityCost;
}
await guild.save();
res.json(await getDojoClient(guild, 0, component._id));
};
interface IPlaceDecoInComponentRequest {
ComponentId: string;
Revision: number;
Type: string;
Pos: number[];
Rot: number[];
Name?: string;
}

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, "PlayerSkills");
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 { addMiscItems, getInventory } from "@/src/services/inventoryService";
import { ExportRelics, IRelic } from "warframe-public-export-plus"; import { ExportRelics, IRelic } from "warframe-public-export-plus";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const projectionManagerController: RequestHandler = async (req, res) => { export const projectionManagerController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
@ -50,7 +51,6 @@ const qualityKeywordToNumber: Record<VoidProjectionQuality, number> = {
// e.g. "/Lotus/Types/Game/Projections/T2VoidProjectionProteaPrimeDBronze" -> ["Lith", "W5", "VPQ_BRONZE"] // e.g. "/Lotus/Types/Game/Projections/T2VoidProjectionProteaPrimeDBronze" -> ["Lith", "W5", "VPQ_BRONZE"]
const parseProjection = (typeName: string): [string, string, VoidProjectionQuality] => { const parseProjection = (typeName: string): [string, string, VoidProjectionQuality] => {
const relic: IRelic | undefined = ExportRelics[typeName]; const relic: IRelic | undefined = ExportRelics[typeName];
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!relic) { if (!relic) {
throw new Error(`Unknown projection ${typeName}`); throw new Error(`Unknown projection ${typeName}`);
} }

View File

@ -1,14 +1,12 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService"; 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 { 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) => { 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 accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId); const response = await handlePurchase(purchaseRequest, accountId);
const response = await handlePurchase(purchaseRequest, inventory);
await inventory.save();
res.json(response); res.json(response);
}; };

View File

@ -1,24 +1,16 @@
import { config } from "@/src/services/configService"; import { getGuildForRequest } from "@/src/services/guildService";
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const queueDojoComponentDestructionController: RequestHandler = async (req, res) => { export const queueDojoComponentDestructionController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const guild = await getGuildForRequest(req);
const inventory = await getInventory(accountId, "GuildId LevelKeys");
const guild = await getGuildForRequestEx(req, inventory);
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Architect))) {
res.json({ DojoRequestStatus: -1 });
return;
}
const componentId = req.query.componentId as string; const componentId = req.query.componentId as string;
guild.DojoComponents!.splice(
guild.DojoComponents.id(componentId)!.DestructionTime = new Date( guild.DojoComponents!.findIndex(x => x._id.toString() === componentId),
Date.now() + (config.fastDojoRoomDestruction ? 5_000 : 2 * 3600_000) 1
); );
await guild.save(); await guild.save();
res.json(await getDojoClient(guild, 0, componentId)); res.json({
DojoRequestStatus: 1
});
}; };

View File

@ -1,70 +0,0 @@
import { GuildMember } from "@/src/models/guildModel";
import { Account } from "@/src/models/loginModel";
import { getGuildForRequest, hasGuildPermission } from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes";
import { RequestHandler } from "express";
export const removeFromGuildController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
const guild = await getGuildForRequest(req);
const payload = JSON.parse(String(req.body)) as IRemoveFromGuildRequest;
const isKick = !account._id.equals(payload.userId);
if (isKick && !(await hasGuildPermission(guild, account._id, GuildPermission.Regulator))) {
res.status(400).json("Invalid permission");
return;
}
const guildMember = (await GuildMember.findOne({ accountId: payload.userId, guildId: guild._id }))!;
if (guildMember.status == 0) {
const inventory = await getInventory(payload.userId);
inventory.GuildId = undefined;
// Remove clan key or blueprint from kicked member
const itemIndex = inventory.LevelKeys.findIndex(x => x.ItemType == "/Lotus/Types/Keys/DojoKey");
if (itemIndex != -1) {
inventory.LevelKeys.splice(itemIndex, 1);
} else {
const recipeIndex = inventory.Recipes.findIndex(x => x.ItemType == "/Lotus/Types/Keys/DojoKeyBlueprint");
if (recipeIndex != -1) {
inventory.Recipes.splice(itemIndex, 1);
}
}
await inventory.save();
// TODO: Handle clan leader kicking themselves (guild should be deleted in this case, I think)
} else if (guildMember.status == 2) {
// TODO: Maybe the inbox message for the sent invite should be deleted?
}
await GuildMember.deleteOne({ _id: guildMember._id });
guild.RosterActivity ??= [];
if (isKick) {
const kickee = (await Account.findOne({ _id: payload.userId }))!;
guild.RosterActivity.push({
dateTime: new Date(),
entryType: 12,
details: getSuffixedName(kickee) + "," + getSuffixedName(account)
});
} else {
guild.RosterActivity.push({
dateTime: new Date(),
entryType: 7,
details: getSuffixedName(account)
});
}
await guild.save();
res.json({
_id: payload.userId,
ItemToRemove: "/Lotus/Types/Keys/DojoKey",
RecipeToRemove: "/Lotus/Types/Keys/DojoKeyBlueprint"
});
};
interface IRemoveFromGuildRequest {
userId: string;
kicker?: string;
}

View File

@ -1,83 +1,9 @@
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { createUnveiledRivenFingerprint, randomiseRivenStats, RivenFingerprint } from "@/src/helpers/rivenHelper";
import { ExportUpgrades } from "warframe-public-export-plus";
import { IOid } from "@/src/types/commonTypes";
export const rerollRandomModController: RequestHandler = async (req, res) => { const rerollRandomModController: RequestHandler = (_req, res) => {
const accountId = await getAccountIdForRequest(req); logger.debug("RerollRandomMod Request", { info: _req.body.toString("hex").replace(/(.)(.)/g, "$1$2 ") });
const request = getJSONfromString<RerollRandomModRequest>(String(req.body)); res.json({});
if ("ItemIds" in request) {
const inventory = await getInventory(accountId, "Upgrades MiscItems");
const changes: IChange[] = [];
let totalKuvaCost = 0;
request.ItemIds.forEach(itemId => {
const upgrade = inventory.Upgrades.id(itemId)!;
const fingerprint = JSON.parse(upgrade.UpgradeFingerprint!) as RivenFingerprint;
if ("challenge" in fingerprint) {
upgrade.UpgradeFingerprint = JSON.stringify(
createUnveiledRivenFingerprint(ExportUpgrades[upgrade.ItemType])
);
} else {
fingerprint.rerolls ??= 0;
const kuvaCost = fingerprint.rerolls < rerollCosts.length ? rerollCosts[fingerprint.rerolls] : 3500;
totalKuvaCost += kuvaCost;
addMiscItems(inventory, [
{
ItemType: "/Lotus/Types/Items/MiscItems/Kuva",
ItemCount: kuvaCost * -1
}
]);
fingerprint.rerolls++;
upgrade.UpgradeFingerprint = JSON.stringify(fingerprint);
randomiseRivenStats(ExportUpgrades[upgrade.ItemType], fingerprint);
upgrade.PendingRerollFingerprint = JSON.stringify(fingerprint);
}
changes.push({
ItemId: { $oid: request.ItemIds[0] },
UpgradeFingerprint: upgrade.UpgradeFingerprint,
PendingRerollFingerprint: upgrade.PendingRerollFingerprint
});
});
await inventory.save();
res.json({
changes: changes,
cost: totalKuvaCost
});
} 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);
}
}; };
type RerollRandomModRequest = LetsGoGamblingRequest | AwDangitRequest; export { rerollRandomModController };
interface LetsGoGamblingRequest {
ItemIds: string[];
}
interface AwDangitRequest {
ItemId: string;
CommitReroll: boolean;
}
interface IChange {
ItemId: IOid;
UpgradeFingerprint?: string;
PendingRerollFingerprint?: string;
}
const rerollCosts = [900, 1000, 1200, 1400, 1700, 2000, 2350, 2750, 3150];

View File

@ -1,91 +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: IOtherDialogueInfo[]; // unsure
}
interface IOtherDialogueInfo {
Dialogue: string;
Tag: string;
Value: number;
}

View File

@ -4,6 +4,7 @@ import { handleInventoryItemConfigChange } from "@/src/services/saveLoadoutServi
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const saveLoadoutController: RequestHandler = async (req, res) => { export const saveLoadoutController: RequestHandler = async (req, res) => {
//validate here //validate here
const accountId = await getAccountIdForRequest(req); 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,15 +1,9 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { ISellRequest } from "@/src/types/sellTypes";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { import { getInventory, addMods, addRecipes } from "@/src/services/inventoryService";
getInventory,
addMods,
addRecipes,
addMiscItems,
addConsumables,
freeUpSlot
} from "@/src/services/inventoryService";
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const sellController: RequestHandler = async (req, res) => { export const sellController: RequestHandler = async (req, res) => {
const payload = JSON.parse(String(req.body)) as ISellRequest; const payload = JSON.parse(String(req.body)) as ISellRequest;
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -20,20 +14,6 @@ export const sellController: RequestHandler = async (req, res) => {
inventory.RegularCredits += payload.SellPrice; inventory.RegularCredits += payload.SellPrice;
} else if (payload.SellCurrency == "SC_FusionPoints") { } else if (payload.SellCurrency == "SC_FusionPoints") {
inventory.FusionPoints += payload.SellPrice; 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 { } else {
throw new Error("Unknown SellCurrency: " + payload.SellCurrency); throw new Error("Unknown SellCurrency: " + payload.SellCurrency);
} }
@ -42,84 +22,23 @@ export const sellController: RequestHandler = async (req, res) => {
if (payload.Items.Suits) { if (payload.Items.Suits) {
payload.Items.Suits.forEach(sellItem => { payload.Items.Suits.forEach(sellItem => {
inventory.Suits.pull({ _id: sellItem.String }); inventory.Suits.pull({ _id: sellItem.String });
freeUpSlot(inventory, InventorySlot.SUITS);
}); });
} }
if (payload.Items.LongGuns) { if (payload.Items.LongGuns) {
payload.Items.LongGuns.forEach(sellItem => { payload.Items.LongGuns.forEach(sellItem => {
inventory.LongGuns.pull({ _id: sellItem.String }); inventory.LongGuns.pull({ _id: sellItem.String });
freeUpSlot(inventory, InventorySlot.WEAPONS);
}); });
} }
if (payload.Items.Pistols) { if (payload.Items.Pistols) {
payload.Items.Pistols.forEach(sellItem => { payload.Items.Pistols.forEach(sellItem => {
inventory.Pistols.pull({ _id: sellItem.String }); inventory.Pistols.pull({ _id: sellItem.String });
freeUpSlot(inventory, InventorySlot.WEAPONS);
}); });
} }
if (payload.Items.Melee) { if (payload.Items.Melee) {
payload.Items.Melee.forEach(sellItem => { payload.Items.Melee.forEach(sellItem => {
inventory.Melee.pull({ _id: sellItem.String }); inventory.Melee.pull({ _id: sellItem.String });
freeUpSlot(inventory, InventorySlot.WEAPONS);
}); });
} }
if (payload.Items.SpaceSuits) {
payload.Items.SpaceSuits.forEach(sellItem => {
inventory.SpaceSuits.pull({ _id: sellItem.String });
freeUpSlot(inventory, InventorySlot.SPACESUITS);
});
}
if (payload.Items.SpaceGuns) {
payload.Items.SpaceGuns.forEach(sellItem => {
inventory.SpaceGuns.pull({ _id: sellItem.String });
freeUpSlot(inventory, InventorySlot.SPACEWEAPONS);
});
}
if (payload.Items.SpaceMelee) {
payload.Items.SpaceMelee.forEach(sellItem => {
inventory.SpaceMelee.pull({ _id: sellItem.String });
freeUpSlot(inventory, InventorySlot.SPACEWEAPONS);
});
}
if (payload.Items.Sentinels) {
payload.Items.Sentinels.forEach(sellItem => {
inventory.Sentinels.pull({ _id: sellItem.String });
freeUpSlot(inventory, InventorySlot.SENTINELS);
});
}
if (payload.Items.SentinelWeapons) {
payload.Items.SentinelWeapons.forEach(sellItem => {
inventory.SentinelWeapons.pull({ _id: sellItem.String });
freeUpSlot(inventory, InventorySlot.SENTINELS);
});
}
if (payload.Items.OperatorAmps) {
payload.Items.OperatorAmps.forEach(sellItem => {
inventory.OperatorAmps.pull({ _id: sellItem.String });
freeUpSlot(inventory, InventorySlot.AMPS);
});
}
if (payload.Items.Hoverboards) {
payload.Items.Hoverboards.forEach(sellItem => {
inventory.Hoverboards.pull({ _id: sellItem.String });
freeUpSlot(inventory, InventorySlot.SPACESUITS);
});
}
if (payload.Items.Drones) {
payload.Items.Drones.forEach(sellItem => {
inventory.Drones.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) { if (payload.Items.Recipes) {
const recipeChanges = []; const recipeChanges = [];
for (const sellItem of payload.Items.Recipes) { for (const sellItem of payload.Items.Recipes) {
@ -144,52 +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(); await inventory.save();
res.json({}); 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[];
Drones?: 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"; import { RequestHandler } from "express";
export const setActiveQuestController: RequestHandler< const setActiveQuestController: RequestHandler = (_req, res) => {
Record<string, never>, res.sendStatus(200);
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();
}; };
export { setActiveQuestController };

View File

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

View File

@ -2,23 +2,12 @@ import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { getPersonalRooms } from "@/src/services/personalRoomsService"; import { getPersonalRooms } from "@/src/services/personalRoomsService";
import { TBootLocation } from "@/src/types/shipTypes"; 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) => { export const setBootLocationController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const personalRooms = await getPersonalRooms(accountId); const personalRooms = await getPersonalRooms(accountId);
personalRooms.Ship.BootLocation = req.query.bootLocation as string as TBootLocation; personalRooms.Ship.BootLocation = req.query.bootLocation as string as TBootLocation;
await personalRooms.save(); 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(); 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.id(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(await getDojoClient(guild, 0, component._id));
};
type SetDojoComponentMessageRequest = { Name: string } | { Message: string };

View File

@ -1,24 +0,0 @@
import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
export const setEquippedInstrumentController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const body = getJSONfromString<ISetEquippedInstrumentRequest>(String(req.body));
await Inventory.updateOne(
{
accountOwnerId: accountId
},
{
EquippedInstrument: body.Instrument
}
);
res.end();
};
interface ISetEquippedInstrumentRequest {
Instrument: string;
}

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