Merge branch 'main' of https://onlyg.it/VampireKitten/SpaceNinjaServerOnlyGit
This commit is contained in:
commit
5aedb579aa
19
.coderabbit.yaml
Normal file
19
.coderabbit.yaml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# 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
|
@ -15,17 +15,16 @@
|
|||||||
"@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",
|
||||||
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
|
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "caughtErrors": "none" }],
|
||||||
"@typescript-eslint/no-misused-promises": "warn",
|
|
||||||
"@typescript-eslint/no-unsafe-argument": "error",
|
"@typescript-eslint/no-unsafe-argument": "error",
|
||||||
"@typescript-eslint/no-unsafe-call": "warn",
|
"@typescript-eslint/no-unsafe-call": "warn",
|
||||||
"@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",
|
"no-loss-of-precision": "warn",
|
||||||
"@typescript-eslint/no-unnecessary-condition": "warn",
|
"@typescript-eslint/no-unnecessary-condition": "warn",
|
||||||
|
"@typescript-eslint/no-base-to-string": "off",
|
||||||
"no-case-declarations": "error",
|
"no-case-declarations": "error",
|
||||||
"prettier/prettier": "error",
|
"prettier/prettier": "error",
|
||||||
"@typescript-eslint/semi": "error",
|
|
||||||
"no-mixed-spaces-and-tabs": "error",
|
"no-mixed-spaces-and-tabs": "error",
|
||||||
"require-await": "off",
|
"require-await": "off",
|
||||||
"@typescript-eslint/require-await": "error"
|
"@typescript-eslint/require-await": "error"
|
||||||
|
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -17,5 +17,5 @@ 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: npm run build
|
- run: npm run verify
|
||||||
- run: npm run lint
|
- run: npm run lint
|
||||||
|
1
.github/workflows/docker.yml
vendored
1
.github/workflows/docker.yml
vendored
@ -5,6 +5,7 @@ on:
|
|||||||
- main
|
- main
|
||||||
jobs:
|
jobs:
|
||||||
docker:
|
docker:
|
||||||
|
if: github.repository == 'OpenWF/SpaceNinjaServer'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Docker buildx
|
- name: Set up Docker buildx
|
||||||
|
@ -1,2 +1,4 @@
|
|||||||
|
src/routes/api.ts
|
||||||
static/webui/libs/
|
static/webui/libs/
|
||||||
*.html
|
*.html
|
||||||
|
*.md
|
||||||
|
5
.vscode/extensions.json
vendored
Normal file
5
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"dbaeumer.vscode-eslint"
|
||||||
|
]
|
||||||
|
}
|
@ -10,8 +10,6 @@ ENV APP_SKIP_TUTORIAL=true
|
|||||||
ENV APP_SKIP_ALL_DIALOGUE=true
|
ENV APP_SKIP_ALL_DIALOGUE=true
|
||||||
ENV APP_UNLOCK_ALL_SCANS=true
|
ENV APP_UNLOCK_ALL_SCANS=true
|
||||||
ENV APP_UNLOCK_ALL_MISSIONS=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_INFINITE_RESOURCES=true
|
||||||
ENV APP_UNLOCK_ALL_SHIP_FEATURES=true
|
ENV APP_UNLOCK_ALL_SHIP_FEATURES=true
|
||||||
ENV APP_UNLOCK_ALL_SHIP_DECORATIONS=true
|
ENV APP_UNLOCK_ALL_SHIP_DECORATIONS=true
|
||||||
|
@ -2,7 +2,14 @@
|
|||||||
|
|
||||||
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
|
## config.json
|
||||||
|
|
||||||
- `logger.level` can be `fatal`, `error`, `warn`, `info`, `http`, `debug`, or `trace`.
|
- `logger.level` can be `fatal`, `error`, `warn`, `info`, `http`, `debug`, or `trace`.
|
||||||
- `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`.
|
- `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`.
|
||||||
|
- `worldState.lockTime` will lock the time provided in worldState if nonzero, e.g. `1743202800` for night in POE.
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
echo Updating SpaceNinjaServer...
|
echo Updating SpaceNinjaServer...
|
||||||
git config remote.origin.url https://openwf.io/SpaceNinjaServer.git
|
git config remote.origin.url https://openwf.io/SpaceNinjaServer.git
|
||||||
git fetch --prune
|
git fetch --prune
|
||||||
|
git stash
|
||||||
git reset --hard origin/main
|
git reset --hard origin/main
|
||||||
|
|
||||||
if exist static\data\0\ (
|
if exist static\data\0\ (
|
||||||
@ -13,12 +14,13 @@ if exist static\data\0\ (
|
|||||||
)
|
)
|
||||||
|
|
||||||
echo Updating dependencies...
|
echo Updating dependencies...
|
||||||
call npm i
|
call npm i --omit=dev
|
||||||
|
|
||||||
call npm run build
|
call npm run build
|
||||||
|
if %errorlevel% == 0 (
|
||||||
call npm run start
|
call npm run start
|
||||||
|
|
||||||
echo SpaceNinjaServer seems to have crashed.
|
echo SpaceNinjaServer seems to have crashed.
|
||||||
|
)
|
||||||
:a
|
:a
|
||||||
pause > nul
|
pause > nul
|
||||||
goto a
|
goto a
|
||||||
|
@ -5,31 +5,45 @@
|
|||||||
"level": "trace"
|
"level": "trace"
|
||||||
},
|
},
|
||||||
"myAddress": "localhost",
|
"myAddress": "localhost",
|
||||||
"hubAddress": "https://localhost/api/",
|
|
||||||
"platformCDNs": ["https://localhost/"],
|
|
||||||
"NRS": ["localhost"],
|
|
||||||
"httpPort": 80,
|
"httpPort": 80,
|
||||||
"httpsPort": 443,
|
"httpsPort": 443,
|
||||||
|
"NRS": ["localhost"],
|
||||||
"administratorNames": [],
|
"administratorNames": [],
|
||||||
"autoCreateAccount": true,
|
"autoCreateAccount": true,
|
||||||
"skipTutorial": true,
|
"skipTutorial": false,
|
||||||
"skipAllDialogue": true,
|
"skipAllDialogue": false,
|
||||||
"unlockAllScans": true,
|
"unlockAllScans": false,
|
||||||
"unlockAllMissions": true,
|
"unlockAllMissions": false,
|
||||||
"infiniteCredits": true,
|
"infiniteCredits": false,
|
||||||
"infinitePlatinum": true,
|
"infinitePlatinum": false,
|
||||||
"infiniteEndo": true,
|
"infiniteEndo": false,
|
||||||
"infiniteRegalAya": true,
|
"infiniteRegalAya": false,
|
||||||
"infiniteHelminthMaterials": false,
|
"infiniteHelminthMaterials": false,
|
||||||
"unlockAllShipFeatures": true,
|
"unlockAllShipFeatures": false,
|
||||||
"unlockAllShipDecorations": true,
|
"unlockAllShipDecorations": false,
|
||||||
"unlockAllFlavourItems": true,
|
"unlockAllFlavourItems": false,
|
||||||
"unlockAllSkins": true,
|
"unlockAllSkins": false,
|
||||||
"unlockAllCapturaScenes": true,
|
"unlockAllCapturaScenes": false,
|
||||||
"universalPolarityEverywhere": true,
|
"universalPolarityEverywhere": false,
|
||||||
"unlockDoubleCapacityPotatoesEverywhere": true,
|
"unlockDoubleCapacityPotatoesEverywhere": false,
|
||||||
"unlockExilusEverywhere": true,
|
"unlockExilusEverywhere": false,
|
||||||
"unlockArcanesEverywhere": true,
|
"unlockArcanesEverywhere": false,
|
||||||
"noDailyStandingLimits": true,
|
"noDailyStandingLimits": false,
|
||||||
"spoofMasteryRank": -1
|
"noArgonCrystalDecay": false,
|
||||||
|
"noMasteryRankUpCooldown": false,
|
||||||
|
"noVendorPurchaseLimits": true,
|
||||||
|
"instantResourceExtractorDrones": false,
|
||||||
|
"noDojoRoomBuildStage": false,
|
||||||
|
"fastDojoRoomDestruction": false,
|
||||||
|
"noDojoResearchCosts": false,
|
||||||
|
"noDojoResearchTime": false,
|
||||||
|
"fastClanAscension": false,
|
||||||
|
"spoofMasteryRank": -1,
|
||||||
|
"worldState": {
|
||||||
|
"creditBoost": false,
|
||||||
|
"affinityBoost": false,
|
||||||
|
"resourceBoost": false,
|
||||||
|
"starDays": true,
|
||||||
|
"lockTime": 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
960
package-lock.json
generated
960
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
33
package.json
33
package.json
@ -6,7 +6,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node --import ./build/src/pathman.js build/src/index.js",
|
"start": "node --import ./build/src/pathman.js build/src/index.js",
|
||||||
"dev": "ts-node-dev --openssl-legacy-provider -r tsconfig-paths/register src/index.ts ",
|
"dev": "ts-node-dev --openssl-legacy-provider -r tsconfig-paths/register src/index.ts ",
|
||||||
"build": "tsc && copyfiles static/webui/** build",
|
"build": "tsc --incremental --sourceMap && ncp static/webui build/static/webui",
|
||||||
|
"verify": "tsgo --noEmit",
|
||||||
"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 .",
|
||||||
@ -14,27 +15,29 @@
|
|||||||
},
|
},
|
||||||
"license": "GNU",
|
"license": "GNU",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"copyfiles": "^2.4.1",
|
"@types/express": "^5",
|
||||||
|
"@types/morgan": "^1.9.9",
|
||||||
|
"crc-32": "^1.2.2",
|
||||||
"express": "^5",
|
"express": "^5",
|
||||||
"mongoose": "^8.9.4",
|
"json-with-bigint": "^3.2.2",
|
||||||
"warframe-public-export-plus": "^0.5.36",
|
"mongoose": "^8.11.0",
|
||||||
|
"morgan": "^1.10.0",
|
||||||
|
"ncp": "^2.0.0",
|
||||||
|
"typescript": ">=5.5 <5.6.0",
|
||||||
|
"warframe-public-export-plus": "^0.5.52",
|
||||||
"warframe-riven-info": "^0.1.2",
|
"warframe-riven-info": "^0.1.2",
|
||||||
"winston": "^3.17.0",
|
"winston": "^3.17.0",
|
||||||
"winston-daily-rotate-file": "^5.0.0"
|
"winston-daily-rotate-file": "^5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/express": "^5",
|
"@rxliuli/tsgo": "^2025.3.31",
|
||||||
"@types/morgan": "^1.9.9",
|
"@typescript-eslint/eslint-plugin": "^8.28.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.18",
|
"@typescript-eslint/parser": "^8.28.0",
|
||||||
"@typescript-eslint/parser": "^7.18",
|
"eslint": "^8",
|
||||||
"eslint": "^8.56.0",
|
"eslint-plugin-prettier": "^5.2.5",
|
||||||
"eslint-plugin-prettier": "^5.2.3",
|
"prettier": "^3.5.3",
|
||||||
"morgan": "^1.10.0",
|
|
||||||
"prettier": "^3.4.2",
|
|
||||||
"ts-node": "^10.9.2",
|
|
||||||
"ts-node-dev": "^2.0.0",
|
"ts-node-dev": "^2.0.0",
|
||||||
"tsconfig-paths": "^4.2.0",
|
"tsconfig-paths": "^4.2.0"
|
||||||
"typescript": ">=4.7.4 <5.6.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.15.0",
|
"node": ">=18.15.0",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Based on http://209.141.38.3/OpenWF/Translations/src/branch/main/update.php
|
// Based on https://onlyg.it/OpenWF/Translations/src/branch/main/update.php
|
||||||
// Converted via ChatGPT-4o
|
// Converted via ChatGPT-4o
|
||||||
|
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
10
src/app.ts
10
src/app.ts
@ -15,6 +15,15 @@ import { webuiRouter } from "@/src/routes/webui";
|
|||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
|
app.use((req, _res, next) => {
|
||||||
|
// 38.5.0 introduced "ezip" for encrypted body blobs.
|
||||||
|
// The bootstrapper decrypts it for us but having an unsupported Content-Encoding here would still be an issue for Express, so removing it.
|
||||||
|
if (req.headers["content-encoding"] == "ezip") {
|
||||||
|
req.headers["content-encoding"] = undefined;
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
app.use(bodyParser.raw());
|
app.use(bodyParser.raw());
|
||||||
app.use(express.json({ limit: "4mb" }));
|
app.use(express.json({ limit: "4mb" }));
|
||||||
app.use(bodyParser.text());
|
app.use(bodyParser.text());
|
||||||
@ -23,6 +32,7 @@ app.use(requestLogger);
|
|||||||
app.use("/api", apiRouter);
|
app.use("/api", apiRouter);
|
||||||
app.use("/", cacheRouter);
|
app.use("/", cacheRouter);
|
||||||
app.use("/custom", customRouter);
|
app.use("/custom", customRouter);
|
||||||
|
app.use("/dynamic", dynamicController);
|
||||||
app.use("/:id/dynamic", dynamicController);
|
app.use("/:id/dynamic", dynamicController);
|
||||||
app.use("/pay", payRouter);
|
app.use("/pay", payRouter);
|
||||||
app.use("/stats", statsRouter);
|
app.use("/stats", statsRouter);
|
||||||
|
11
src/controllers/api/abandonLibraryDailyTaskController.ts
Normal file
11
src/controllers/api/abandonLibraryDailyTaskController.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
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();
|
||||||
|
};
|
46
src/controllers/api/abortDojoComponentController.ts
Normal file
46
src/controllers/api/abortDojoComponentController.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
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 {
|
||||||
|
await removeDojoRoom(guild, request.ComponentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
await guild.save();
|
||||||
|
res.json(await getDojoClient(guild, 0, request.ComponentId));
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IAbortDojoComponentRequest {
|
||||||
|
DecoType?: string;
|
||||||
|
ComponentId: string;
|
||||||
|
DecoId?: string;
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
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));
|
||||||
|
};
|
@ -1,8 +1,9 @@
|
|||||||
|
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
|
import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "@/src/helpers/rivenHelper";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { addMods, getInventory } from "@/src/services/inventoryService";
|
import { addMods, getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getRandomElement, getRandomInt, getRandomReward, IRngResult } from "@/src/services/rngService";
|
import { getRandomElement } from "@/src/services/rngService";
|
||||||
import { logger } from "@/src/utils/logger";
|
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { ExportUpgrades } from "warframe-public-export-plus";
|
import { ExportUpgrades } from "warframe-public-export-plus";
|
||||||
|
|
||||||
@ -17,82 +18,23 @@ export const activateRandomModController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType]);
|
const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType]);
|
||||||
const challenge = getRandomElement(ExportUpgrades[rivenType].availableChallenges!);
|
const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
|
||||||
const fingerprintChallenge: IRandomModChallenge = {
|
|
||||||
Type: challenge.fullName,
|
|
||||||
Progress: 0,
|
|
||||||
Required: getRandomInt(challenge.countRange[0], challenge.countRange[1])
|
|
||||||
};
|
|
||||||
if (Math.random() < challenge.complicationChance) {
|
|
||||||
const complicationsAsRngResults: IRngResult[] = [];
|
|
||||||
for (const complication of challenge.complications) {
|
|
||||||
complicationsAsRngResults.push({
|
|
||||||
type: complication.fullName,
|
|
||||||
itemCount: 1,
|
|
||||||
probability: complication.weight
|
|
||||||
});
|
|
||||||
}
|
|
||||||
fingerprintChallenge.Complication = getRandomReward(complicationsAsRngResults)!.type;
|
|
||||||
logger.debug(
|
|
||||||
`riven rolled challenge ${fingerprintChallenge.Type} with complication ${fingerprintChallenge.Complication}`
|
|
||||||
);
|
|
||||||
const complication = challenge.complications.find(x => x.fullName == fingerprintChallenge.Complication)!;
|
|
||||||
fingerprintChallenge.Required *= complication.countMultiplier;
|
|
||||||
} else {
|
|
||||||
logger.debug(`riven rolled challenge ${fingerprintChallenge.Type}`);
|
|
||||||
}
|
|
||||||
const upgradeIndex =
|
const upgradeIndex =
|
||||||
inventory.Upgrades.push({
|
inventory.Upgrades.push({
|
||||||
ItemType: rivenType,
|
ItemType: rivenType,
|
||||||
UpgradeFingerprint: JSON.stringify({ challenge: fingerprintChallenge })
|
UpgradeFingerprint: JSON.stringify(fingerprint)
|
||||||
}) - 1;
|
}) - 1;
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
|
// For some reason, in this response, the UpgradeFingerprint is simply a nested object and not a string
|
||||||
res.json({
|
res.json({
|
||||||
NewMod: inventory.Upgrades[upgradeIndex].toJSON()
|
NewMod: {
|
||||||
|
UpgradeFingerprint: fingerprint,
|
||||||
|
ItemType: rivenType,
|
||||||
|
ItemId: toOid(inventory.Upgrades[upgradeIndex]._id)
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IActiveRandomModRequest {
|
interface IActiveRandomModRequest {
|
||||||
ItemType: string;
|
ItemType: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IRandomModChallenge {
|
|
||||||
Type: string;
|
|
||||||
Progress: number;
|
|
||||||
Required: number;
|
|
||||||
Complication?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rivenRawToRealWeighted: Record<string, string[]> = {
|
|
||||||
"/Lotus/Upgrades/Mods/Randomized/RawArchgunRandomMod": [
|
|
||||||
"/Lotus/Upgrades/Mods/Randomized/LotusArchgunRandomModRare"
|
|
||||||
],
|
|
||||||
"/Lotus/Upgrades/Mods/Randomized/RawMeleeRandomMod": [
|
|
||||||
"/Lotus/Upgrades/Mods/Randomized/PlayerMeleeWeaponRandomModRare"
|
|
||||||
],
|
|
||||||
"/Lotus/Upgrades/Mods/Randomized/RawModularMeleeRandomMod": [
|
|
||||||
"/Lotus/Upgrades/Mods/Randomized/LotusModularMeleeRandomModRare"
|
|
||||||
],
|
|
||||||
"/Lotus/Upgrades/Mods/Randomized/RawModularPistolRandomMod": [
|
|
||||||
"/Lotus/Upgrades/Mods/Randomized/LotusModularPistolRandomModRare"
|
|
||||||
],
|
|
||||||
"/Lotus/Upgrades/Mods/Randomized/RawPistolRandomMod": ["/Lotus/Upgrades/Mods/Randomized/LotusPistolRandomModRare"],
|
|
||||||
"/Lotus/Upgrades/Mods/Randomized/RawRifleRandomMod": ["/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare"],
|
|
||||||
"/Lotus/Upgrades/Mods/Randomized/RawShotgunRandomMod": [
|
|
||||||
"/Lotus/Upgrades/Mods/Randomized/LotusShotgunRandomModRare"
|
|
||||||
],
|
|
||||||
"/Lotus/Upgrades/Mods/Randomized/RawSentinelWeaponRandomMod": [
|
|
||||||
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
|
|
||||||
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
|
|
||||||
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
|
|
||||||
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
|
|
||||||
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
|
|
||||||
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
|
|
||||||
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
|
|
||||||
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
|
|
||||||
"/Lotus/Upgrades/Mods/Randomized/LotusRifleRandomModRare",
|
|
||||||
"/Lotus/Upgrades/Mods/Randomized/LotusShotgunRandomModRare",
|
|
||||||
"/Lotus/Upgrades/Mods/Randomized/LotusPistolRandomModRare",
|
|
||||||
"/Lotus/Upgrades/Mods/Randomized/PlayerMeleeWeaponRandomModRare"
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
@ -1,16 +1,25 @@
|
|||||||
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 { getInventory } from "@/src/services/inventoryService";
|
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
|
|
||||||
const addFriendImageController: RequestHandler = async (req, res) => {
|
export 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<IUpdateGlyphRequest>(String(req.body));
|
||||||
const inventory = await getInventory(accountId);
|
|
||||||
inventory.ActiveAvatarImageType = json.AvatarImageType;
|
await Inventory.updateOne(
|
||||||
await inventory.save();
|
{
|
||||||
|
accountOwnerId: accountId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ActiveAvatarImageType: json.AvatarImageType
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
res.json({});
|
res.json({});
|
||||||
};
|
};
|
||||||
|
|
||||||
export { addFriendImageController };
|
interface IUpdateGlyphRequest {
|
||||||
|
AvatarImageType: string;
|
||||||
|
AvatarImage: string;
|
||||||
|
}
|
||||||
|
117
src/controllers/api/addToAllianceController.ts
Normal file
117
src/controllers/api/addToAllianceController.ts
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import { getJSONfromString, regexEscape } from "@/src/helpers/stringHelpers";
|
||||||
|
import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
|
import { createMessage } from "@/src/services/inboxService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||||
|
import { GuildPermission } from "@/src/types/guildTypes";
|
||||||
|
import { logger } from "@/src/utils/logger";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { ExportFlavour } from "warframe-public-export-plus";
|
||||||
|
|
||||||
|
export const addToAllianceController: RequestHandler = async (req, res) => {
|
||||||
|
// Check requester is a warlord in their guild
|
||||||
|
const account = await getAccountForRequest(req);
|
||||||
|
const guildMember = (await GuildMember.findOne({ accountId: account._id, status: 0 }))!;
|
||||||
|
if (guildMember.rank > 1) {
|
||||||
|
res.status(400).json({ Error: 104 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check guild has invite permissions in the alliance
|
||||||
|
const allianceMember = (await AllianceMember.findOne({
|
||||||
|
allianceId: req.query.allianceId,
|
||||||
|
guildId: guildMember.guildId
|
||||||
|
}))!;
|
||||||
|
if (!(allianceMember.Permissions & GuildPermission.Recruiter)) {
|
||||||
|
res.status(400).json({ Error: 104 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find clan to invite
|
||||||
|
const payload = getJSONfromString<IAddToAllianceRequest>(String(req.body));
|
||||||
|
const guilds = await Guild.find(
|
||||||
|
{
|
||||||
|
Name:
|
||||||
|
payload.clanName.indexOf("#") == -1
|
||||||
|
? new RegExp("^" + regexEscape(payload.clanName) + "#...$")
|
||||||
|
: payload.clanName
|
||||||
|
},
|
||||||
|
"Name"
|
||||||
|
);
|
||||||
|
if (guilds.length == 0) {
|
||||||
|
res.status(400).json({ Error: 101 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (guilds.length > 1) {
|
||||||
|
const choices: IGuildChoice[] = [];
|
||||||
|
for (const guild of guilds) {
|
||||||
|
choices.push({
|
||||||
|
OriginalPlatform: 0,
|
||||||
|
Name: guild.Name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
res.json(choices);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add clan as a pending alliance member
|
||||||
|
try {
|
||||||
|
await AllianceMember.insertOne({
|
||||||
|
allianceId: req.query.allianceId,
|
||||||
|
guildId: guilds[0]._id,
|
||||||
|
Pending: true,
|
||||||
|
Permissions: 0
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug(`alliance invite failed due to ${String(e)}`);
|
||||||
|
res.status(400).json({ Error: 102 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send inbox message to founding warlord
|
||||||
|
// TOVERIFY: Should other warlords get this as well?
|
||||||
|
// TOVERIFY: Who/what should the sender be?
|
||||||
|
// TOVERIFY: Should this message be highPriority?
|
||||||
|
const invitedClanOwnerMember = (await GuildMember.findOne({ guildId: guilds[0]._id, rank: 0 }))!;
|
||||||
|
const senderInventory = await getInventory(account._id.toString(), "ActiveAvatarImageType");
|
||||||
|
const senderGuild = (await Guild.findById(allianceMember.guildId, "Name"))!;
|
||||||
|
const alliance = (await Alliance.findById(req.query.allianceId, "Name"))!;
|
||||||
|
await createMessage(invitedClanOwnerMember.accountId, [
|
||||||
|
{
|
||||||
|
sndr: getSuffixedName(account),
|
||||||
|
msg: "/Lotus/Language/Menu/Mailbox_AllianceInvite_Body",
|
||||||
|
arg: [
|
||||||
|
{
|
||||||
|
Key: "THEIR_CLAN",
|
||||||
|
Tag: senderGuild.Name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "CLAN",
|
||||||
|
Tag: guilds[0].Name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "ALLIANCE",
|
||||||
|
Tag: alliance.Name
|
||||||
|
}
|
||||||
|
],
|
||||||
|
sub: "/Lotus/Language/Menu/Mailbox_AllianceInvite_Title",
|
||||||
|
icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
|
||||||
|
contextInfo: alliance._id.toString(),
|
||||||
|
highPriority: true,
|
||||||
|
acceptAction: "ALLIANCE_INVITE",
|
||||||
|
declineAction: "ALLIANCE_INVITE",
|
||||||
|
hasAccountAction: true
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IAddToAllianceRequest {
|
||||||
|
clanName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IGuildChoice {
|
||||||
|
OriginalPlatform: number;
|
||||||
|
Name: string;
|
||||||
|
}
|
105
src/controllers/api/addToGuildController.ts
Normal file
105
src/controllers/api/addToGuildController.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
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, getAccountIdForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||||
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
|
import { GuildPermission, IGuildMemberClient } from "@/src/types/guildTypes";
|
||||||
|
import { logger } from "@/src/utils/logger";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { ExportFlavour } from "warframe-public-export-plus";
|
||||||
|
|
||||||
|
export const addToGuildController: RequestHandler = async (req, res) => {
|
||||||
|
const payload = JSON.parse(String(req.body)) as IAddToGuildRequest;
|
||||||
|
|
||||||
|
if ("UserName" in payload) {
|
||||||
|
// Clan recruiter sending an invite
|
||||||
|
|
||||||
|
const account = await Account.findOne({ DisplayName: payload.UserName });
|
||||||
|
if (!account) {
|
||||||
|
res.status(400).json("Username does not exist");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const inventory = await getInventory(account._id.toString(), "Settings");
|
||||||
|
// TODO: Also consider GIFT_MODE_FRIENDS once friends are implemented
|
||||||
|
if (inventory.Settings?.GuildInvRestriction == "GIFT_MODE_NONE") {
|
||||||
|
res.status(400).json("Invite restricted");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const guild = (await Guild.findById(payload.GuildId.$oid, "Name Ranks"))!;
|
||||||
|
const senderAccount = await getAccountForRequest(req);
|
||||||
|
if (!(await hasGuildPermission(guild, senderAccount._id.toString(), GuildPermission.Recruiter))) {
|
||||||
|
res.status(400).json("Invalid permission");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await GuildMember.insertOne({
|
||||||
|
accountId: account._id,
|
||||||
|
guildId: payload.GuildId.$oid,
|
||||||
|
status: 2 // outgoing invite
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug(`guild invite failed due to ${String(e)}`);
|
||||||
|
res.status(400).json("User already invited to clan");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const senderInventory = await getInventory(senderAccount._id.toString(), "ActiveAvatarImageType");
|
||||||
|
await createMessage(account._id, [
|
||||||
|
{
|
||||||
|
sndr: getSuffixedName(senderAccount),
|
||||||
|
msg: "/Lotus/Language/Menu/Mailbox_ClanInvite_Body",
|
||||||
|
arg: [
|
||||||
|
{
|
||||||
|
Key: "clan",
|
||||||
|
Tag: guild.Name
|
||||||
|
}
|
||||||
|
],
|
||||||
|
sub: "/Lotus/Language/Menu/Mailbox_ClanInvite_Title",
|
||||||
|
icon: ExportFlavour[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 });
|
||||||
|
} else if ("RequestMsg" in payload) {
|
||||||
|
// Player applying to join a clan
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
try {
|
||||||
|
await GuildMember.insertOne({
|
||||||
|
accountId,
|
||||||
|
guildId: payload.GuildId.$oid,
|
||||||
|
status: 1, // incoming invite
|
||||||
|
RequestMsg: payload.RequestMsg,
|
||||||
|
RequestExpiry: new Date(Date.now() + 14 * 86400 * 1000) // TOVERIFY: I can't find any good information about this with regards to live, but 2 weeks seem reasonable.
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug(`guild invite failed due to ${String(e)}`);
|
||||||
|
res.status(400).send("Already requested");
|
||||||
|
}
|
||||||
|
res.end();
|
||||||
|
} else {
|
||||||
|
logger.error(`data provided to ${req.path}: ${String(req.body)}`);
|
||||||
|
res.status(400).end();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IAddToGuildRequest {
|
||||||
|
UserName?: string;
|
||||||
|
GuildId: IOid;
|
||||||
|
RequestMsg?: string;
|
||||||
|
}
|
132
src/controllers/api/artifactTransmutationController.ts
Normal file
132
src/controllers/api/artifactTransmutationController.ts
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
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
|
||||||
|
};
|
||||||
|
let forcedPolarity: string | undefined;
|
||||||
|
payload.Consumed.forEach(upgrade => {
|
||||||
|
const meta = ExportUpgrades[upgrade.ItemType];
|
||||||
|
counts[meta.rarity] += upgrade.ItemCount;
|
||||||
|
addMods(inventory, [
|
||||||
|
{
|
||||||
|
ItemType: upgrade.ItemType,
|
||||||
|
ItemCount: upgrade.ItemCount * -1
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/AttackTransmuteCore") {
|
||||||
|
forcedPolarity = "AP_ATTACK";
|
||||||
|
} else if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/DefenseTransmuteCore") {
|
||||||
|
forcedPolarity = "AP_DEFENSE";
|
||||||
|
} else if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/TacticTransmuteCore") {
|
||||||
|
forcedPolarity = "AP_TACTIC";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Based on the table on https://wiki.warframe.com/w/Transmutation
|
||||||
|
const weights: Record<TRarity, number> = {
|
||||||
|
COMMON: counts.COMMON * 95 + counts.UNCOMMON * 15 + counts.RARE * 4,
|
||||||
|
UNCOMMON: counts.COMMON * 4 + counts.UNCOMMON * 80 + counts.RARE * 10,
|
||||||
|
RARE: counts.COMMON * 1 + counts.UNCOMMON * 5 + counts.RARE * 50,
|
||||||
|
LEGENDARY: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const options: { uniqueName: string; rarity: TRarity }[] = [];
|
||||||
|
Object.entries(ExportUpgrades).forEach(([uniqueName, upgrade]) => {
|
||||||
|
if (upgrade.canBeTransmutation && (!forcedPolarity || upgrade.polarity == forcedPolarity)) {
|
||||||
|
options.push({ uniqueName, rarity: upgrade.rarity });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
20
src/controllers/api/cancelGuildAdvertisementController.ts
Normal file
20
src/controllers/api/cancelGuildAdvertisementController.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { GuildAd } from "@/src/models/guildModel";
|
||||||
|
import { getGuildForRequestEx, hasGuildPermission } from "@/src/services/guildService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { GuildPermission } from "@/src/types/guildTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const cancelGuildAdvertisementController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "GuildId");
|
||||||
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
|
if (!(await hasGuildPermission(guild, accountId, GuildPermission.Advertiser))) {
|
||||||
|
res.status(400).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await GuildAd.deleteOne({ GuildId: guild._id });
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
};
|
@ -1,15 +1,22 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getDojoClient, getGuildForRequest } from "@/src/services/guildService";
|
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { IDojoComponentDatabase } from "@/src/types/guildTypes";
|
import { GuildPermission, IDojoComponentDatabase } from "@/src/types/guildTypes";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
|
||||||
export const changeDojoRootController: RequestHandler = async (req, res) => {
|
export const changeDojoRootController: RequestHandler = async (req, res) => {
|
||||||
const guild = await getGuildForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
// At this point, we know that a member of the guild is making this request. Assuming they are allowed to change the root.
|
const 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> = {};
|
const idToNode: Record<string, INode> = {};
|
||||||
guild.DojoComponents!.forEach(x => {
|
guild.DojoComponents.forEach(x => {
|
||||||
idToNode[x._id.toString()] = {
|
idToNode[x._id.toString()] = {
|
||||||
component: x,
|
component: x,
|
||||||
parent: undefined,
|
parent: undefined,
|
||||||
@ -18,7 +25,7 @@ export const changeDojoRootController: RequestHandler = async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let oldRoot: INode | undefined;
|
let oldRoot: INode | undefined;
|
||||||
guild.DojoComponents!.forEach(x => {
|
guild.DojoComponents.forEach(x => {
|
||||||
const node = idToNode[x._id.toString()];
|
const node = idToNode[x._id.toString()];
|
||||||
if (x.pi) {
|
if (x.pi) {
|
||||||
idToNode[x.pi.toString()].children.push(node);
|
idToNode[x.pi.toString()].children.push(node);
|
||||||
@ -47,7 +54,7 @@ export const changeDojoRootController: RequestHandler = async (req, res) => {
|
|||||||
);
|
);
|
||||||
top.children.forEach(x => stack.push(x));
|
top.children.forEach(x => stack.push(x));
|
||||||
}
|
}
|
||||||
guild.DojoComponents!.forEach(x => {
|
guild.DojoComponents.forEach(x => {
|
||||||
x._id = idMap[x._id.toString()];
|
x._id = idMap[x._id.toString()];
|
||||||
if (x.pi) {
|
if (x.pi) {
|
||||||
x.pi = idMap[x.pi.toString()];
|
x.pi = idMap[x.pi.toString()];
|
||||||
@ -58,7 +65,7 @@ export const changeDojoRootController: RequestHandler = async (req, res) => {
|
|||||||
|
|
||||||
await guild.save();
|
await guild.save();
|
||||||
|
|
||||||
res.json(getDojoClient(guild, 0));
|
res.json(await getDojoClient(guild, 0));
|
||||||
};
|
};
|
||||||
|
|
||||||
interface INode {
|
interface INode {
|
||||||
|
38
src/controllers/api/changeGuildRankController.ts
Normal file
38
src/controllers/api/changeGuildRankController.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
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
|
||||||
|
});
|
||||||
|
};
|
@ -7,9 +7,19 @@ 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 { getInventory, updateCurrency, addItem, addMiscItems, addRecipes } from "@/src/services/inventoryService";
|
import {
|
||||||
|
getInventory,
|
||||||
|
updateCurrency,
|
||||||
|
addItem,
|
||||||
|
addRecipes,
|
||||||
|
occupySlot,
|
||||||
|
combineInventoryChanges
|
||||||
|
} from "@/src/services/inventoryService";
|
||||||
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
|
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
|
||||||
export interface IClaimCompletedRecipeRequest {
|
interface IClaimCompletedRecipeRequest {
|
||||||
RecipeIds: IOid[];
|
RecipeIds: IOid[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,15 +47,36 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (req.query.cancel) {
|
if (req.query.cancel) {
|
||||||
const currencyChanges = updateCurrency(inventory, recipe.buildPrice * -1, false);
|
const inventoryChanges: IInventoryChanges = {
|
||||||
addMiscItems(inventory, recipe.ingredients);
|
...updateCurrency(inventory, recipe.buildPrice * -1, false)
|
||||||
|
};
|
||||||
|
|
||||||
|
const equipmentIngredients = new Set();
|
||||||
|
for (const category of ["LongGuns", "Pistols", "Melee"] as const) {
|
||||||
|
if (pendingRecipe[category]) {
|
||||||
|
pendingRecipe[category].forEach(item => {
|
||||||
|
const index = inventory[category].push(item) - 1;
|
||||||
|
inventoryChanges[category] ??= [];
|
||||||
|
inventoryChanges[category].push(inventory[category][index].toJSON<IEquipmentClient>());
|
||||||
|
equipmentIngredients.add(item.ItemType);
|
||||||
|
|
||||||
|
occupySlot(inventory, InventorySlot.WEAPONS, false);
|
||||||
|
inventoryChanges.WeaponBin ??= { Slots: 0 };
|
||||||
|
inventoryChanges.WeaponBin.Slots -= 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const ingredient of recipe.ingredients) {
|
||||||
|
if (!equipmentIngredients.has(ingredient.ItemType)) {
|
||||||
|
combineInventoryChanges(
|
||||||
|
inventoryChanges,
|
||||||
|
await addItem(inventory, ingredient.ItemType, ingredient.ItemCount)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
// Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
|
res.json(inventoryChanges); // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
|
||||||
res.json({
|
|
||||||
...currencyChanges,
|
|
||||||
MiscItems: recipe.ingredients
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
logger.debug("Claiming Recipe", { recipe, pendingRecipe });
|
logger.debug("Claiming Recipe", { recipe, pendingRecipe });
|
||||||
|
|
||||||
@ -68,31 +99,40 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
|
|||||||
inventory.SpectreLoadouts.push(inventory.PendingSpectreLoadouts[pendingLoadoutIndex]);
|
inventory.SpectreLoadouts.push(inventory.PendingSpectreLoadouts[pendingLoadoutIndex]);
|
||||||
inventory.PendingSpectreLoadouts.splice(pendingLoadoutIndex, 1);
|
inventory.PendingSpectreLoadouts.splice(pendingLoadoutIndex, 1);
|
||||||
}
|
}
|
||||||
|
} else if (recipe.secretIngredientAction == "SIA_UNBRAND") {
|
||||||
|
inventory.BrandedSuits!.splice(
|
||||||
|
inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)),
|
||||||
|
1
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let InventoryChanges = {};
|
let InventoryChanges = {};
|
||||||
if (recipe.consumeOnUse) {
|
if (recipe.consumeOnUse) {
|
||||||
const recipeChanges = [
|
addRecipes(inventory, [
|
||||||
{
|
{
|
||||||
ItemType: pendingRecipe.ItemType,
|
ItemType: pendingRecipe.ItemType,
|
||||||
ItemCount: -1
|
ItemCount: -1
|
||||||
}
|
}
|
||||||
];
|
]);
|
||||||
|
|
||||||
InventoryChanges = { ...InventoryChanges, Recipes: recipeChanges };
|
|
||||||
|
|
||||||
addRecipes(inventory, recipeChanges);
|
|
||||||
}
|
}
|
||||||
if (req.query.rush) {
|
if (req.query.rush) {
|
||||||
|
const end = Math.trunc(pendingRecipe.CompletionDate.getTime() / 1000);
|
||||||
|
const start = end - recipe.buildTime;
|
||||||
|
const secondsElapsed = Math.trunc(Date.now() / 1000) - start;
|
||||||
|
const progress = secondsElapsed / recipe.buildTime;
|
||||||
|
logger.debug(`rushing recipe at ${Math.trunc(progress * 100)}% completion`);
|
||||||
|
const cost = Math.round(recipe.skipBuildTimePrice * (1 - (progress - 0.5)));
|
||||||
InventoryChanges = {
|
InventoryChanges = {
|
||||||
...InventoryChanges,
|
...InventoryChanges,
|
||||||
...updateCurrency(inventory, recipe.skipBuildTimePrice, true)
|
...updateCurrency(inventory, cost, true)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (recipe.secretIngredientAction != "SIA_UNBRAND") {
|
||||||
InventoryChanges = {
|
InventoryChanges = {
|
||||||
...InventoryChanges,
|
...InventoryChanges,
|
||||||
...(await addItem(inventory, recipe.resultType, recipe.num)).InventoryChanges
|
...(await addItem(inventory, recipe.resultType, recipe.num, false))
|
||||||
};
|
};
|
||||||
|
}
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({ InventoryChanges });
|
res.json({ InventoryChanges });
|
||||||
}
|
}
|
||||||
|
31
src/controllers/api/claimLibraryDailyTaskRewardController.ts
Normal file
31
src/controllers/api/claimLibraryDailyTaskRewardController.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
6
src/controllers/api/clearNewEpisodeRewardController.ts
Normal file
6
src/controllers/api/clearNewEpisodeRewardController.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
// example req.body: {"NewEpisodeReward":true,"crossPlaySetting":"ENABLED"}
|
||||||
|
export const clearNewEpisodeRewardController: RequestHandler = (_req, res) => {
|
||||||
|
res.status(200).end();
|
||||||
|
};
|
@ -4,8 +4,7 @@ import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inven
|
|||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { IUnveiledRivenFingerprint, randomiseRivenStats } from "@/src/helpers/rivenFingerprintHelper";
|
import { createUnveiledRivenFingerprint } from "@/src/helpers/rivenHelper";
|
||||||
import { getRandomElement, getRandomInt } from "@/src/services/rngService";
|
|
||||||
import { ExportUpgrades } from "warframe-public-export-plus";
|
import { ExportUpgrades } from "warframe-public-export-plus";
|
||||||
|
|
||||||
export const completeRandomModChallengeController: RequestHandler = async (req, res) => {
|
export const completeRandomModChallengeController: RequestHandler = async (req, res) => {
|
||||||
@ -31,17 +30,7 @@ export const completeRandomModChallengeController: RequestHandler = async (req,
|
|||||||
// Update riven fingerprint to a randomised unveiled state
|
// Update riven fingerprint to a randomised unveiled state
|
||||||
const upgrade = inventory.Upgrades.id(request.ItemId)!;
|
const upgrade = inventory.Upgrades.id(request.ItemId)!;
|
||||||
const meta = ExportUpgrades[upgrade.ItemType];
|
const meta = ExportUpgrades[upgrade.ItemType];
|
||||||
const fingerprint: IUnveiledRivenFingerprint = {
|
upgrade.UpgradeFingerprint = JSON.stringify(createUnveiledRivenFingerprint(meta));
|
||||||
compat: getRandomElement(meta.compatibleItems!),
|
|
||||||
lim: 0,
|
|
||||||
lvl: 0,
|
|
||||||
lvlReq: getRandomInt(8, 16),
|
|
||||||
pol: getRandomElement(["AP_ATTACK", "AP_DEFENSE", "AP_TACTIC"]),
|
|
||||||
buffs: [],
|
|
||||||
curses: []
|
|
||||||
};
|
|
||||||
randomiseRivenStats(meta, fingerprint);
|
|
||||||
upgrade.UpgradeFingerprint = JSON.stringify(fingerprint);
|
|
||||||
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
|
|
||||||
|
37
src/controllers/api/confirmAllianceInvitationController.ts
Normal file
37
src/controllers/api/confirmAllianceInvitationController.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
|
import { getAllianceClient } from "@/src/services/guildService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const confirmAllianceInvitationController: RequestHandler = async (req, res) => {
|
||||||
|
// Check requester is a warlord in their guild
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const guildMember = (await GuildMember.findOne({ accountId, status: 0 }))!;
|
||||||
|
if (guildMember.rank > 1) {
|
||||||
|
res.status(400).json({ Error: 104 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allianceMember = await AllianceMember.findOne({
|
||||||
|
allianceId: req.query.allianceId,
|
||||||
|
guildId: guildMember.guildId
|
||||||
|
});
|
||||||
|
if (!allianceMember || !allianceMember.Pending) {
|
||||||
|
res.status(400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
allianceMember.Pending = false;
|
||||||
|
|
||||||
|
const guild = (await Guild.findById(guildMember.guildId))!;
|
||||||
|
guild.AllianceId = allianceMember.allianceId;
|
||||||
|
|
||||||
|
await Promise.all([allianceMember.save(), guild.save()]);
|
||||||
|
|
||||||
|
// Give client the new alliance data which uses "AllianceId" instead of "_id" in this response
|
||||||
|
const alliance = (await Alliance.findById(allianceMember.allianceId))!;
|
||||||
|
const { _id, ...rest } = await getAllianceClient(alliance, guild);
|
||||||
|
res.json({
|
||||||
|
AllianceId: _id,
|
||||||
|
...rest
|
||||||
|
});
|
||||||
|
};
|
124
src/controllers/api/confirmGuildInvitationController.ts
Normal file
124
src/controllers/api/confirmGuildInvitationController.ts
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
|
import { Account } from "@/src/models/loginModel";
|
||||||
|
import { deleteGuild, getGuildClient, hasGuildPermission, removeDojoKeyItems } from "@/src/services/guildService";
|
||||||
|
import { addRecipes, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountForRequest, getAccountIdForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||||
|
import { GuildPermission } from "@/src/types/guildTypes";
|
||||||
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { Types } from "mongoose";
|
||||||
|
|
||||||
|
// GET request: A player accepting an invite they got in their inbox.
|
||||||
|
export const confirmGuildInvitationGetController: RequestHandler = async (req, res) => {
|
||||||
|
const account = await getAccountForRequest(req);
|
||||||
|
const invitedGuildMember = await GuildMember.findOne({
|
||||||
|
accountId: account._id,
|
||||||
|
guildId: req.query.clanId as string
|
||||||
|
});
|
||||||
|
if (invitedGuildMember && invitedGuildMember.status == 2) {
|
||||||
|
let inventoryChanges: IInventoryChanges = {};
|
||||||
|
|
||||||
|
// If this account is already in a guild, we need to do cleanup first.
|
||||||
|
const guildMember = await GuildMember.findOneAndDelete({ accountId: account._id, status: 0 });
|
||||||
|
if (guildMember) {
|
||||||
|
const inventory = await getInventory(account._id.toString(), "LevelKeys Recipes");
|
||||||
|
inventoryChanges = removeDojoKeyItems(inventory);
|
||||||
|
await inventory.save();
|
||||||
|
|
||||||
|
if (guildMember.rank == 0) {
|
||||||
|
await deleteGuild(guildMember.guildId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that we're sure this account is not in a guild right now, we can just proceed with the normal updates.
|
||||||
|
invitedGuildMember.status = 0;
|
||||||
|
await invitedGuildMember.save();
|
||||||
|
|
||||||
|
// Remove pending applications for this account
|
||||||
|
await GuildMember.deleteMany({ accountId: account._id, status: 1 });
|
||||||
|
|
||||||
|
// Update inventory of new member
|
||||||
|
const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes");
|
||||||
|
inventory.GuildId = new Types.ObjectId(req.query.clanId as string);
|
||||||
|
const recipeChanges = [
|
||||||
|
{
|
||||||
|
ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
|
||||||
|
ItemCount: 1
|
||||||
|
}
|
||||||
|
];
|
||||||
|
addRecipes(inventory, recipeChanges);
|
||||||
|
combineInventoryChanges(inventoryChanges, { Recipes: recipeChanges });
|
||||||
|
await inventory.save();
|
||||||
|
|
||||||
|
const guild = (await Guild.findById(req.query.clanId as string))!;
|
||||||
|
|
||||||
|
// Add join to clan log
|
||||||
|
guild.RosterActivity ??= [];
|
||||||
|
guild.RosterActivity.push({
|
||||||
|
dateTime: new Date(),
|
||||||
|
entryType: 6,
|
||||||
|
details: getSuffixedName(account)
|
||||||
|
});
|
||||||
|
await guild.save();
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
...(await getGuildClient(guild, account._id.toString())),
|
||||||
|
InventoryChanges: inventoryChanges
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.end();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// POST request: Clan representative accepting invite(s).
|
||||||
|
export const confirmGuildInvitationPostController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const guild = (await Guild.findById(req.query.clanId as string, "Ranks RosterActivity"))!;
|
||||||
|
if (!(await hasGuildPermission(guild, accountId, GuildPermission.Recruiter))) {
|
||||||
|
res.status(400).json("Invalid permission");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const payload = getJSONfromString<{ userId: string }>(String(req.body));
|
||||||
|
const filter: { accountId?: string; status: number } = { status: 1 };
|
||||||
|
if (payload.userId != "all") {
|
||||||
|
filter.accountId = payload.userId;
|
||||||
|
}
|
||||||
|
const guildMembers = await GuildMember.find(filter);
|
||||||
|
const newMembers: string[] = [];
|
||||||
|
for (const guildMember of guildMembers) {
|
||||||
|
guildMember.status = 0;
|
||||||
|
guildMember.RequestMsg = undefined;
|
||||||
|
guildMember.RequestExpiry = undefined;
|
||||||
|
await guildMember.save();
|
||||||
|
|
||||||
|
// Remove other pending applications for this account
|
||||||
|
await GuildMember.deleteMany({ accountId: guildMember.accountId, status: 1 });
|
||||||
|
|
||||||
|
// Update inventory of new member
|
||||||
|
const inventory = await getInventory(guildMember.accountId.toString(), "GuildId Recipes");
|
||||||
|
inventory.GuildId = new Types.ObjectId(req.query.clanId as string);
|
||||||
|
addRecipes(inventory, [
|
||||||
|
{
|
||||||
|
ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
|
||||||
|
ItemCount: 1
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
await inventory.save();
|
||||||
|
|
||||||
|
// Add join to clan log
|
||||||
|
const account = (await Account.findOne({ _id: guildMember.accountId }))!;
|
||||||
|
guild.RosterActivity ??= [];
|
||||||
|
guild.RosterActivity.push({
|
||||||
|
dateTime: new Date(),
|
||||||
|
entryType: 6,
|
||||||
|
details: getSuffixedName(account)
|
||||||
|
});
|
||||||
|
|
||||||
|
newMembers.push(account._id.toString());
|
||||||
|
}
|
||||||
|
await guild.save();
|
||||||
|
res.json({
|
||||||
|
NewMembers: newMembers
|
||||||
|
});
|
||||||
|
};
|
104
src/controllers/api/contributeGuildClassController.ts
Normal file
104
src/controllers/api/contributeGuildClassController.ts
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
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.findById(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, [
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
};
|
168
src/controllers/api/contributeToDojoComponentController.ts
Normal file
168
src/controllers/api/contributeToDojoComponentController.ts
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
import { GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
|
||||||
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
|
import {
|
||||||
|
addGuildMemberMiscItemContribution,
|
||||||
|
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, IGuildMemberDatabase } 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 guildMember = (await GuildMember.findOne(
|
||||||
|
{ accountId, guildId: guild._id },
|
||||||
|
"RegularCreditsContributed MiscItemsContributed"
|
||||||
|
))!;
|
||||||
|
const request = JSON.parse(String(req.body)) as IContributeToDojoComponentRequest;
|
||||||
|
const component = guild.DojoComponents.id(request.ComponentId)!;
|
||||||
|
|
||||||
|
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, guildMember, 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, guildMember, request, inventory, inventoryChanges, meta, deco);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
|
||||||
|
res.json({
|
||||||
|
...(await getDojoClient(guild, 0, component._id)),
|
||||||
|
InventoryChanges: inventoryChanges
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const processContribution = (
|
||||||
|
guild: TGuildDatabaseDocument,
|
||||||
|
guildMember: IGuildMemberDatabase,
|
||||||
|
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);
|
||||||
|
|
||||||
|
guildMember.RegularCreditsContributed ??= 0;
|
||||||
|
guildMember.RegularCreditsContributed += request.RegularCredits;
|
||||||
|
}
|
||||||
|
if (request.VaultCredits) {
|
||||||
|
component.RegularCredits += request.VaultCredits;
|
||||||
|
guild.VaultRegularCredits! -= request.VaultCredits;
|
||||||
|
}
|
||||||
|
if (component.RegularCredits > scaleRequiredCount(guild.Tier, meta.price)) {
|
||||||
|
guild.VaultRegularCredits ??= 0;
|
||||||
|
guild.VaultRegularCredits += component.RegularCredits - scaleRequiredCount(guild.Tier, meta.price);
|
||||||
|
component.RegularCredits = scaleRequiredCount(guild.Tier, 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(guild.Tier, ingredientMeta.ItemCount)
|
||||||
|
) {
|
||||||
|
ingredientContribution.ItemCount =
|
||||||
|
scaleRequiredCount(guild.Tier, 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(guild.Tier, ingredientMeta.ItemCount)
|
||||||
|
) {
|
||||||
|
ingredientContribution.ItemCount =
|
||||||
|
scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
|
||||||
|
}
|
||||||
|
componentMiscItem.ItemCount += ingredientContribution.ItemCount;
|
||||||
|
} else {
|
||||||
|
component.MiscItems.push(ingredientContribution);
|
||||||
|
}
|
||||||
|
miscItemChanges.push({
|
||||||
|
ItemType: ingredientContribution.ItemType,
|
||||||
|
ItemCount: ingredientContribution.ItemCount * -1
|
||||||
|
});
|
||||||
|
|
||||||
|
addGuildMemberMiscItemContribution(guildMember, ingredientContribution);
|
||||||
|
}
|
||||||
|
addMiscItems(inventory, miscItemChanges);
|
||||||
|
inventoryChanges.MiscItems = miscItemChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component.RegularCredits >= scaleRequiredCount(guild.Tier, meta.price)) {
|
||||||
|
let fullyFunded = true;
|
||||||
|
for (const ingredient of meta.ingredients) {
|
||||||
|
const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredient.ItemType);
|
||||||
|
if (
|
||||||
|
!componentMiscItem ||
|
||||||
|
componentMiscItem.ItemCount < scaleRequiredCount(guild.Tier, ingredient.ItemCount)
|
||||||
|
) {
|
||||||
|
fullyFunded = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fullyFunded) {
|
||||||
|
component.CompletionTime = new Date(Date.now() + meta.time * 1000);
|
||||||
|
processDojoBuildMaterialsGathered(guild, meta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
113
src/controllers/api/contributeToVaultController.ts
Normal file
113
src/controllers/api/contributeToVaultController.ts
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import {
|
||||||
|
Alliance,
|
||||||
|
Guild,
|
||||||
|
GuildMember,
|
||||||
|
TGuildDatabaseDocument,
|
||||||
|
TGuildMemberDatabaseDocument
|
||||||
|
} from "@/src/models/guildModel";
|
||||||
|
import {
|
||||||
|
addGuildMemberMiscItemContribution,
|
||||||
|
addGuildMemberShipDecoContribution,
|
||||||
|
addVaultFusionTreasures,
|
||||||
|
addVaultMiscItems,
|
||||||
|
addVaultShipDecos,
|
||||||
|
getGuildForRequestEx
|
||||||
|
} from "@/src/services/guildService";
|
||||||
|
import {
|
||||||
|
addFusionTreasures,
|
||||||
|
addMiscItems,
|
||||||
|
addShipDecorations,
|
||||||
|
getInventory,
|
||||||
|
updateCurrency
|
||||||
|
} from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { IFusionTreasure, IMiscItem, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const contributeToVaultController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "GuildId RegularCredits MiscItems ShipDecorations FusionTreasures");
|
||||||
|
const request = JSON.parse(String(req.body)) as IContributeToVaultRequest;
|
||||||
|
|
||||||
|
if (request.Alliance) {
|
||||||
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
|
const alliance = (await Alliance.findById(guild.AllianceId!))!;
|
||||||
|
alliance.VaultRegularCredits ??= 0;
|
||||||
|
alliance.VaultRegularCredits += request.RegularCredits;
|
||||||
|
if (request.FromVault) {
|
||||||
|
guild.VaultRegularCredits! -= request.RegularCredits;
|
||||||
|
await Promise.all([guild.save(), alliance.save()]);
|
||||||
|
} else {
|
||||||
|
updateCurrency(inventory, request.RegularCredits, false);
|
||||||
|
await Promise.all([inventory.save(), alliance.save()]);
|
||||||
|
}
|
||||||
|
res.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let guild: TGuildDatabaseDocument;
|
||||||
|
let guildMember: TGuildMemberDatabaseDocument | undefined;
|
||||||
|
if (request.GuildVault) {
|
||||||
|
guild = (await Guild.findById(request.GuildVault))!;
|
||||||
|
} else {
|
||||||
|
guild = await getGuildForRequestEx(req, inventory);
|
||||||
|
guildMember = (await GuildMember.findOne(
|
||||||
|
{ accountId, guildId: guild._id },
|
||||||
|
"RegularCreditsContributed MiscItemsContributed ShipDecorationsContributed"
|
||||||
|
))!;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.RegularCredits) {
|
||||||
|
updateCurrency(inventory, request.RegularCredits, false);
|
||||||
|
|
||||||
|
guild.VaultRegularCredits ??= 0;
|
||||||
|
guild.VaultRegularCredits += request.RegularCredits;
|
||||||
|
|
||||||
|
if (guildMember) {
|
||||||
|
guildMember.RegularCreditsContributed ??= 0;
|
||||||
|
guildMember.RegularCreditsContributed += request.RegularCredits;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (request.MiscItems.length) {
|
||||||
|
addVaultMiscItems(guild, request.MiscItems);
|
||||||
|
for (const item of request.MiscItems) {
|
||||||
|
if (guildMember) {
|
||||||
|
addGuildMemberMiscItemContribution(guildMember, item);
|
||||||
|
}
|
||||||
|
addMiscItems(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (request.ShipDecorations.length) {
|
||||||
|
addVaultShipDecos(guild, request.ShipDecorations);
|
||||||
|
for (const item of request.ShipDecorations) {
|
||||||
|
if (guildMember) {
|
||||||
|
addGuildMemberShipDecoContribution(guildMember, item);
|
||||||
|
}
|
||||||
|
addShipDecorations(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (request.FusionTreasures.length) {
|
||||||
|
addVaultFusionTreasures(guild, request.FusionTreasures);
|
||||||
|
for (const item of request.FusionTreasures) {
|
||||||
|
addFusionTreasures(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const promises: Promise<unknown>[] = [guild.save(), inventory.save()];
|
||||||
|
if (guildMember) {
|
||||||
|
promises.push(guildMember.save());
|
||||||
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IContributeToVaultRequest {
|
||||||
|
RegularCredits: number;
|
||||||
|
MiscItems: IMiscItem[];
|
||||||
|
ShipDecorations: ITypeCount[];
|
||||||
|
FusionTreasures: IFusionTreasure[];
|
||||||
|
Alliance?: boolean;
|
||||||
|
FromVault?: boolean;
|
||||||
|
GuildVault?: string;
|
||||||
|
}
|
50
src/controllers/api/createAllianceController.ts
Normal file
50
src/controllers/api/createAllianceController.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
|
import { getAllianceClient } from "@/src/services/guildService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { GuildPermission } from "@/src/types/guildTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const createAllianceController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "GuildId");
|
||||||
|
const guild = (await Guild.findById(inventory.GuildId!, "Name Tier AllianceId"))!;
|
||||||
|
if (guild.AllianceId) {
|
||||||
|
res.status(400).send("Guild is already in an alliance").end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const guildMember = (await GuildMember.findOne({ guildId: guild._id, accountId }, "rank"))!;
|
||||||
|
if (guildMember.rank > 1) {
|
||||||
|
res.status(400).send("Invalid permission").end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = getJSONfromString<ICreateAllianceRequest>(String(req.body));
|
||||||
|
const alliance = new Alliance({ Name: data.allianceName });
|
||||||
|
try {
|
||||||
|
await alliance.save();
|
||||||
|
} catch (e) {
|
||||||
|
res.status(400).send("Alliance name already in use").end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
guild.AllianceId = alliance._id;
|
||||||
|
await Promise.all([
|
||||||
|
guild.save(),
|
||||||
|
AllianceMember.insertOne({
|
||||||
|
allianceId: alliance._id,
|
||||||
|
guildId: guild._id,
|
||||||
|
Pending: false,
|
||||||
|
Permissions:
|
||||||
|
GuildPermission.Ruler |
|
||||||
|
GuildPermission.Promoter |
|
||||||
|
GuildPermission.Recruiter |
|
||||||
|
GuildPermission.Treasurer |
|
||||||
|
GuildPermission.ChatModerator
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
res.json(await getAllianceClient(alliance, guild));
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ICreateAllianceRequest {
|
||||||
|
allianceName: string;
|
||||||
|
}
|
@ -1,35 +1,52 @@
|
|||||||
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 { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
import { Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
import { Guild } from "@/src/models/guildModel";
|
import { createUniqueClanName, getGuildClient } from "@/src/services/guildService";
|
||||||
|
import { addRecipes, getInventory } from "@/src/services/inventoryService";
|
||||||
|
|
||||||
export const createGuildController: RequestHandler = async (req, res) => {
|
export 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<ICreateGuildRequest>(String(req.body));
|
||||||
|
|
||||||
|
// Remove pending applications for this account
|
||||||
|
await GuildMember.deleteMany({ accountId, status: 1 });
|
||||||
|
|
||||||
// Create guild on database
|
// Create guild on database
|
||||||
const guild = new Guild({
|
const guild = new Guild({
|
||||||
Name: payload.guildName
|
Name: await createUniqueClanName(payload.guildName)
|
||||||
});
|
});
|
||||||
await guild.save();
|
await guild.save();
|
||||||
|
|
||||||
// Update inventory
|
// Create guild member on database
|
||||||
const inventory = await Inventory.findOne({ accountOwnerId: accountId });
|
await GuildMember.insertOne({
|
||||||
if (inventory) {
|
accountId: accountId,
|
||||||
// Set GuildId
|
guildId: guild._id,
|
||||||
inventory.GuildId = guild._id;
|
status: 0,
|
||||||
|
rank: 0
|
||||||
// Give clan key (TODO: This should only be a blueprint)
|
|
||||||
inventory.LevelKeys.push({
|
|
||||||
ItemType: "/Lotus/Types/Keys/DojoKey",
|
|
||||||
ItemCount: 1
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await inventory.save();
|
const inventory = await getInventory(accountId, "GuildId Recipes");
|
||||||
|
inventory.GuildId = guild._id;
|
||||||
|
addRecipes(inventory, [
|
||||||
|
{
|
||||||
|
ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
|
||||||
|
ItemCount: 1
|
||||||
}
|
}
|
||||||
|
]);
|
||||||
|
await inventory.save();
|
||||||
|
|
||||||
res.json(guild);
|
res.json({
|
||||||
|
...(await getGuildClient(guild, accountId)),
|
||||||
|
InventoryChanges: {
|
||||||
|
Recipes: [
|
||||||
|
{
|
||||||
|
ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
|
||||||
|
ItemCount: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ICreateGuildRequest {
|
interface ICreateGuildRequest {
|
||||||
|
@ -19,7 +19,7 @@ export const creditsController: RequestHandler = async (req, res) => {
|
|||||||
response.RegularCredits = 999999999;
|
response.RegularCredits = 999999999;
|
||||||
}
|
}
|
||||||
if (config.infinitePlatinum) {
|
if (config.infinitePlatinum) {
|
||||||
response.PremiumCreditsFree = 999999999;
|
response.PremiumCreditsFree = 0;
|
||||||
response.PremiumCredits = 999999999;
|
response.PremiumCredits = 999999999;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { Guild } from "@/src/models/guildModel";
|
||||||
|
import { getAccountForRequest } from "@/src/services/loginService";
|
||||||
|
import { logger } from "@/src/utils/logger";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const customObstacleCourseLeaderboardController: RequestHandler = async (req, res) => {
|
||||||
|
const data = getJSONfromString<ICustomObstacleCourseLeaderboardRequest>(String(req.body));
|
||||||
|
const guild = (await Guild.findById(data.g, "DojoComponents"))!;
|
||||||
|
const component = guild.DojoComponents.id(data.c)!;
|
||||||
|
if (req.query.act == "f") {
|
||||||
|
res.json({
|
||||||
|
results: component.Leaderboard ?? []
|
||||||
|
});
|
||||||
|
} else if (req.query.act == "p") {
|
||||||
|
const account = await getAccountForRequest(req);
|
||||||
|
component.Leaderboard ??= [];
|
||||||
|
const entry = component.Leaderboard.find(x => x.n == account.DisplayName);
|
||||||
|
if (entry) {
|
||||||
|
entry.s = data.s!;
|
||||||
|
} else {
|
||||||
|
component.Leaderboard.push({
|
||||||
|
s: data.s!,
|
||||||
|
n: account.DisplayName,
|
||||||
|
r: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
component.Leaderboard.sort((a, b) => a.s - b.s); // In this case, the score is the time in milliseconds, so smaller is better.
|
||||||
|
if (component.Leaderboard.length > 10) {
|
||||||
|
component.Leaderboard.shift();
|
||||||
|
}
|
||||||
|
let r = 0;
|
||||||
|
for (const entry of component.Leaderboard) {
|
||||||
|
entry.r = ++r;
|
||||||
|
}
|
||||||
|
await guild.save();
|
||||||
|
res.status(200).end();
|
||||||
|
} else {
|
||||||
|
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
||||||
|
throw new Error(`unknown customObstacleCourseLeaderboard act: ${String(req.query.act)}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ICustomObstacleCourseLeaderboardRequest {
|
||||||
|
g: string;
|
||||||
|
c: string;
|
||||||
|
s?: number; // act=p
|
||||||
|
}
|
21
src/controllers/api/customizeGuildRanksController.ts
Normal file
21
src/controllers/api/customizeGuildRanksController.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
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[];
|
||||||
|
}
|
17
src/controllers/api/declineAllianceInviteController.ts
Normal file
17
src/controllers/api/declineAllianceInviteController.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { AllianceMember, GuildMember } from "@/src/models/guildModel";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const declineAllianceInviteController: RequestHandler = async (req, res) => {
|
||||||
|
// Check requester is a warlord in their guild
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const guildMember = (await GuildMember.findOne({ accountId, status: 0 }))!;
|
||||||
|
if (guildMember.rank > 1) {
|
||||||
|
res.status(400).json({ Error: 104 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await AllianceMember.deleteOne({ allianceId: req.query.allianceId, guildId: guildMember.guildId });
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
};
|
14
src/controllers/api/declineGuildInviteController.ts
Normal file
14
src/controllers/api/declineGuildInviteController.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
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();
|
||||||
|
};
|
33
src/controllers/api/destroyDojoDecoController.ts
Normal file
33
src/controllers/api/destroyDojoDecoController.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
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;
|
||||||
|
}
|
67
src/controllers/api/divvyAllianceVaultController.ts
Normal file
67
src/controllers/api/divvyAllianceVaultController.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
|
import { getAccountForRequest } from "@/src/services/loginService";
|
||||||
|
import { GuildPermission } from "@/src/types/guildTypes";
|
||||||
|
import { parallelForeach } from "@/src/utils/async-utils";
|
||||||
|
import { logger } from "@/src/utils/logger";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const divvyAllianceVaultController: RequestHandler = async (req, res) => {
|
||||||
|
// Afaict, there's no way to put anything other than credits in the alliance vault (anymore?), so just no-op if this is not a request to divvy credits.
|
||||||
|
if (req.query.credits == "1") {
|
||||||
|
// Check requester is a warlord in their guild
|
||||||
|
const account = await getAccountForRequest(req);
|
||||||
|
const guildMember = (await GuildMember.findOne({ accountId: account._id, status: 0 }))!;
|
||||||
|
if (guildMember.rank > 1) {
|
||||||
|
res.status(400).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check guild has treasurer permissions in the alliance
|
||||||
|
const allianceMember = (await AllianceMember.findOne({
|
||||||
|
allianceId: req.query.allianceId,
|
||||||
|
guildId: guildMember.guildId
|
||||||
|
}))!;
|
||||||
|
if (!(allianceMember.Permissions & GuildPermission.Treasurer)) {
|
||||||
|
res.status(400).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allianceMembers = await AllianceMember.find({ allianceId: req.query.allianceId });
|
||||||
|
const memberCounts: Record<string, number> = {};
|
||||||
|
let totalMembers = 0;
|
||||||
|
await parallelForeach(allianceMembers, async allianceMember => {
|
||||||
|
const memberCount = await GuildMember.countDocuments({
|
||||||
|
guildId: allianceMember.guildId
|
||||||
|
});
|
||||||
|
memberCounts[allianceMember.guildId.toString()] = memberCount;
|
||||||
|
totalMembers += memberCount;
|
||||||
|
});
|
||||||
|
logger.debug(`alliance has ${totalMembers} members between all its clans`);
|
||||||
|
|
||||||
|
const alliance = (await Alliance.findById(allianceMember.allianceId, "VaultRegularCredits"))!;
|
||||||
|
if (alliance.VaultRegularCredits) {
|
||||||
|
let creditsHandedOutInTotal = 0;
|
||||||
|
await parallelForeach(allianceMembers, async allianceMember => {
|
||||||
|
const memberCount = memberCounts[allianceMember.guildId.toString()];
|
||||||
|
const cutPercentage = memberCount / totalMembers;
|
||||||
|
const creditsToHandOut = Math.trunc(alliance.VaultRegularCredits! * cutPercentage);
|
||||||
|
logger.debug(
|
||||||
|
`${allianceMember.guildId.toString()} has ${memberCount} member(s) = ${Math.trunc(cutPercentage * 100)}% of alliance; giving ${creditsToHandOut} credit(s)`
|
||||||
|
);
|
||||||
|
if (creditsToHandOut != 0) {
|
||||||
|
await Guild.updateOne(
|
||||||
|
{ _id: allianceMember.guildId },
|
||||||
|
{ $inc: { VaultRegularCredits: creditsToHandOut } }
|
||||||
|
);
|
||||||
|
creditsHandedOutInTotal += creditsToHandOut;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
alliance.VaultRegularCredits -= creditsHandedOutInTotal;
|
||||||
|
logger.debug(
|
||||||
|
`handed out ${creditsHandedOutInTotal} credits; alliance vault now has ${alliance.VaultRegularCredits} credit(s)`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await alliance.save();
|
||||||
|
}
|
||||||
|
res.end();
|
||||||
|
};
|
76
src/controllers/api/dojoComponentRushController.ts
Normal file
76
src/controllers/api/dojoComponentRushController.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import { GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
|
||||||
|
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(guild, deco, meta, platinumDonated);
|
||||||
|
} else {
|
||||||
|
const meta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!;
|
||||||
|
processContribution(guild, component, meta, platinumDonated);
|
||||||
|
|
||||||
|
const entry = guild.RoomChanges?.find(x => x.componentId.equals(component._id));
|
||||||
|
if (entry) {
|
||||||
|
entry.dateTime = component.CompletionTime!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const guildMember = (await GuildMember.findOne({ accountId, guildId: guild._id }, "PremiumCreditsContributed"))!;
|
||||||
|
guildMember.PremiumCreditsContributed ??= 0;
|
||||||
|
guildMember.PremiumCreditsContributed += request.Amount;
|
||||||
|
|
||||||
|
await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
...(await getDojoClient(guild, 0, component._id)),
|
||||||
|
InventoryChanges: inventoryChanges
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const processContribution = (
|
||||||
|
guild: TGuildDatabaseDocument,
|
||||||
|
component: IDojoContributable,
|
||||||
|
meta: IDojoBuild,
|
||||||
|
platinumDonated: number
|
||||||
|
): void => {
|
||||||
|
const fullPlatinumCost = scaleRequiredCount(guild.Tier, meta.skipTimePrice);
|
||||||
|
const fullDurationSeconds = meta.time;
|
||||||
|
const secondsPerPlatinum = fullDurationSeconds / fullPlatinumCost;
|
||||||
|
component.CompletionTime = new Date(
|
||||||
|
component.CompletionTime!.getTime() - secondsPerPlatinum * platinumDonated * 1000
|
||||||
|
);
|
||||||
|
component.RushPlatinum ??= 0;
|
||||||
|
component.RushPlatinum += platinumDonated;
|
||||||
|
};
|
@ -1,7 +1,143 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
const dronesController: RequestHandler = (_req, res) => {
|
export const dronesController: RequestHandler = async (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`);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export { dronesController };
|
interface IActiveDrone {
|
||||||
|
DeployTime: IMongoDate;
|
||||||
|
System: number;
|
||||||
|
ItemId: IOid;
|
||||||
|
ItemType: string;
|
||||||
|
CurrentHP: number;
|
||||||
|
DamageTime: IMongoDate;
|
||||||
|
PendingDamage: number;
|
||||||
|
Resources: {
|
||||||
|
ItemType: string;
|
||||||
|
BinTotal: number;
|
||||||
|
StartTime: IMongoDate;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
69
src/controllers/api/entratiLabConquestModeController.ts
Normal file
69
src/controllers/api/entratiLabConquestModeController.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
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[];
|
||||||
|
}
|
@ -17,7 +17,7 @@ export const evolveWeaponController: RequestHandler = async (req, res) => {
|
|||||||
recipe.ingredients.map(x => ({ ItemType: x.ItemType, ItemCount: x.ItemCount * -1 }))
|
recipe.ingredients.map(x => ({ ItemType: x.ItemType, ItemCount: x.ItemCount * -1 }))
|
||||||
);
|
);
|
||||||
|
|
||||||
const item = inventory[payload.Category].find(item => item._id.toString() == (req.query.ItemId as string))!;
|
const item = inventory[payload.Category].id(req.query.ItemId as string)!;
|
||||||
item.Features ??= 0;
|
item.Features ??= 0;
|
||||||
item.Features |= EquipmentFeatures.INCARNON_GENESIS;
|
item.Features |= EquipmentFeatures.INCARNON_GENESIS;
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ export const evolveWeaponController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const item = inventory[payload.Category].find(item => item._id.toString() == (req.query.ItemId as string))!;
|
const item = inventory[payload.Category].id(req.query.ItemId as string)!;
|
||||||
item.Features! &= ~EquipmentFeatures.INCARNON_GENESIS;
|
item.Features! &= ~EquipmentFeatures.INCARNON_GENESIS;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`unexpected evolve weapon action: ${payload.Action}`);
|
throw new Error(`unexpected evolve weapon action: ${payload.Action}`);
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getInventory, addMiscItems, addEquipment } from "@/src/services/inventoryService";
|
import { getInventory, addMiscItems, addEquipment, occupySlot } from "@/src/services/inventoryService";
|
||||||
import { IMiscItem, TFocusPolarity, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IMiscItem, TFocusPolarity, TEquipmentKey, InventorySlot } 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 { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
|
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
|
|
||||||
export const focusController: RequestHandler = async (req, res) => {
|
export const focusController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
@ -17,8 +18,8 @@ export const focusController: RequestHandler = async (req, res) => {
|
|||||||
case FocusOperation.InstallLens: {
|
case FocusOperation.InstallLens: {
|
||||||
const request = JSON.parse(String(req.body)) as ILensInstallRequest;
|
const request = JSON.parse(String(req.body)) as ILensInstallRequest;
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
for (const item of inventory[request.Category]) {
|
const item = inventory[request.Category].id(request.WeaponId);
|
||||||
if (item._id.toString() == request.WeaponId) {
|
if (item) {
|
||||||
item.FocusLens = request.LensType;
|
item.FocusLens = request.LensType;
|
||||||
addMiscItems(inventory, [
|
addMiscItems(inventory, [
|
||||||
{
|
{
|
||||||
@ -26,8 +27,6 @@ export const focusController: RequestHandler = async (req, res) => {
|
|||||||
ItemCount: -1
|
ItemCount: -1
|
||||||
} satisfies IMiscItem
|
} satisfies IMiscItem
|
||||||
]);
|
]);
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({
|
res.json({
|
||||||
@ -55,9 +54,16 @@ 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);
|
|
||||||
inventory.FocusAbility = focusType;
|
await Inventory.updateOne(
|
||||||
await inventory.save();
|
{
|
||||||
|
accountOwnerId: accountId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
FocusAbility: focusType
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
res.end();
|
res.end();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -105,6 +111,7 @@ export const focusController: RequestHandler = async (req, res) => {
|
|||||||
];
|
];
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, parts);
|
const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, parts);
|
||||||
|
occupySlot(inventory, InventorySlot.AMPS, false);
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json((inventoryChanges.OperatorAmps as IEquipmentClient[])[0]);
|
res.json((inventoryChanges.OperatorAmps as IEquipmentClient[])[0]);
|
||||||
break;
|
break;
|
||||||
|
@ -23,12 +23,11 @@ export const fusionTreasuresController: RequestHandler = async (req, res) => {
|
|||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
const request = JSON.parse(String(req.body)) as IFusionTreasureRequest;
|
const request = JSON.parse(String(req.body)) as IFusionTreasureRequest;
|
||||||
|
|
||||||
|
// Swap treasures
|
||||||
const oldTreasure = parseFusionTreasure(request.oldTreasureName, -1);
|
const oldTreasure = parseFusionTreasure(request.oldTreasureName, -1);
|
||||||
const newTreasure = parseFusionTreasure(request.newTreasureName, 1);
|
const newTreasure = parseFusionTreasure(request.newTreasureName, 1);
|
||||||
|
const fusionTreasureChanges = [oldTreasure, newTreasure];
|
||||||
// Swap treasures
|
addFusionTreasures(inventory, fusionTreasureChanges);
|
||||||
addFusionTreasures(inventory, [oldTreasure]);
|
|
||||||
addFusionTreasures(inventory, [newTreasure]);
|
|
||||||
|
|
||||||
// Remove consumed stars
|
// Remove consumed stars
|
||||||
const miscItemChanges: IMiscItem[] = [];
|
const miscItemChanges: IMiscItem[] = [];
|
||||||
@ -45,5 +44,9 @@ export const fusionTreasuresController: RequestHandler = async (req, res) => {
|
|||||||
addMiscItems(inventory, miscItemChanges);
|
addMiscItems(inventory, miscItemChanges);
|
||||||
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.end();
|
// The response itself is the inventory changes for this endpoint.
|
||||||
|
res.json({
|
||||||
|
MiscItems: miscItemChanges,
|
||||||
|
FusionTreasures: fusionTreasureChanges
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -10,8 +10,7 @@ import { IGenericUpdate } from "@/src/types/genericUpdate";
|
|||||||
const genericUpdateController: RequestHandler = async (request, response) => {
|
const genericUpdateController: RequestHandler = async (request, response) => {
|
||||||
const accountId = await getAccountIdForRequest(request);
|
const accountId = await getAccountIdForRequest(request);
|
||||||
const update = getJSONfromString<IGenericUpdate>(String(request.body));
|
const update = getJSONfromString<IGenericUpdate>(String(request.body));
|
||||||
await updateGeneric(update, accountId);
|
response.json(await updateGeneric(update, accountId));
|
||||||
response.json(update);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export { genericUpdateController };
|
export { genericUpdateController };
|
||||||
|
@ -1,7 +1,25 @@
|
|||||||
|
import { Alliance, Guild } from "@/src/models/guildModel";
|
||||||
|
import { getAllianceClient } from "@/src/services/guildService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
const getAllianceController: RequestHandler = (_req, res) => {
|
export const getAllianceController: RequestHandler = async (req, res) => {
|
||||||
res.sendStatus(200);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "GuildId");
|
||||||
|
if (inventory.GuildId) {
|
||||||
|
const guild = (await Guild.findById(inventory.GuildId, "Name Tier AllianceId"))!;
|
||||||
|
if (guild.AllianceId) {
|
||||||
|
const alliance = (await Alliance.findById(guild.AllianceId))!;
|
||||||
|
res.json(await getAllianceClient(alliance, guild));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.end();
|
||||||
};
|
};
|
||||||
|
|
||||||
export { getAllianceController };
|
/*interface IGetAllianceRequest {
|
||||||
|
memberCount: number;
|
||||||
|
clanLeaderName: string;
|
||||||
|
clanLeaderId: string;
|
||||||
|
}*/
|
||||||
|
@ -1,5 +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): void => {
|
const getFriendsController = (_request: Request, response: Response): void => {
|
||||||
response.writeHead(200, {
|
response.writeHead(200, {
|
||||||
//Connection: "keep-alive",
|
//Connection: "keep-alive",
|
||||||
|
19
src/controllers/api/getGuildContributionsController.ts
Normal file
19
src/controllers/api/getGuildContributionsController.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { GuildMember } from "@/src/models/guildModel";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { IGuildMemberClient } from "@/src/types/guildTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const getGuildContributionsController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const guildId = (await getInventory(accountId, "GuildId")).GuildId;
|
||||||
|
const guildMember = (await GuildMember.findOne({ guildId, accountId: req.query.buddyId }))!;
|
||||||
|
res.json({
|
||||||
|
_id: { $oid: req.query.buddyId as string },
|
||||||
|
RegularCreditsContributed: guildMember.RegularCreditsContributed,
|
||||||
|
PremiumCreditsContributed: guildMember.PremiumCreditsContributed,
|
||||||
|
MiscItemsContributed: guildMember.MiscItemsContributed,
|
||||||
|
ConsumablesContributed: [], // ???
|
||||||
|
ShipDecorationsContributed: guildMember.ShipDecorationsContributed
|
||||||
|
} satisfies Partial<IGuildMemberClient>);
|
||||||
|
};
|
@ -1,73 +1,32 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
|
||||||
import { Guild } from "@/src/models/guildModel";
|
import { Guild } from "@/src/models/guildModel";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
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) => {
|
export const getGuildController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await Inventory.findOne({ accountOwnerId: accountId });
|
const inventory = await getInventory(accountId, "GuildId");
|
||||||
if (!inventory) {
|
|
||||||
res.status(400).json({ error: "inventory was undefined" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (inventory.GuildId) {
|
if (inventory.GuildId) {
|
||||||
const guild = await Guild.findOne({ _id: inventory.GuildId });
|
const guild = await Guild.findById(inventory.GuildId);
|
||||||
if (guild) {
|
if (guild) {
|
||||||
res.json({
|
// Handle guilds created before we added discriminators
|
||||||
_id: toOid(guild._id),
|
if (guild.Name.indexOf("#") == -1) {
|
||||||
Name: guild.Name,
|
guild.Name = await createUniqueClanName(guild.Name);
|
||||||
Members: [
|
await guild.save();
|
||||||
{
|
|
||||||
_id: { $oid: req.query.accountId },
|
|
||||||
Rank: 0,
|
|
||||||
Status: 0
|
|
||||||
}
|
}
|
||||||
],
|
|
||||||
Ranks: [
|
if (guild.CeremonyResetDate && Date.now() >= guild.CeremonyResetDate.getTime()) {
|
||||||
{
|
logger.debug(`ascension ceremony is over`);
|
||||||
Name: "/Lotus/Language/Game/Rank_Creator",
|
guild.CeremonyEndo = undefined;
|
||||||
Permissions: 16351
|
guild.CeremonyContributors = undefined;
|
||||||
},
|
guild.CeremonyResetDate = undefined;
|
||||||
{
|
await guild.save();
|
||||||
Name: "/Lotus/Language/Game/Rank_Warlord",
|
|
||||||
Permissions: 14303
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "/Lotus/Language/Game/Rank_General",
|
|
||||||
Permissions: 4318
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "/Lotus/Language/Game/Rank_Officer",
|
|
||||||
Permissions: 4314
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "/Lotus/Language/Game/Rank_Leader",
|
|
||||||
Permissions: 4106
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "/Lotus/Language/Game/Rank_Sage",
|
|
||||||
Permissions: 4304
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "/Lotus/Language/Game/Rank_Soldier",
|
|
||||||
Permissions: 4098
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "/Lotus/Language/Game/Rank_Initiate",
|
|
||||||
Permissions: 4096
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "/Lotus/Language/Game/Rank_Utility",
|
|
||||||
Permissions: 4096
|
|
||||||
}
|
}
|
||||||
],
|
res.json(await getGuildClient(guild, accountId));
|
||||||
Tier: 1
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res.json({});
|
res.end();
|
||||||
};
|
};
|
||||||
|
|
||||||
export { getGuildController };
|
|
||||||
|
@ -6,24 +6,28 @@ import { getDojoClient } from "@/src/services/guildService";
|
|||||||
export const getGuildDojoController: RequestHandler = async (req, res) => {
|
export const getGuildDojoController: RequestHandler = async (req, res) => {
|
||||||
const guildId = req.query.guildId as string;
|
const guildId = req.query.guildId as string;
|
||||||
|
|
||||||
const guild = await Guild.findOne({ _id: guildId });
|
const guild = await Guild.findById(guildId);
|
||||||
if (!guild) {
|
if (!guild) {
|
||||||
res.status(404).end();
|
res.status(404).end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate dojo info if not present
|
// Populate dojo info if not present
|
||||||
if (!guild.DojoComponents || guild.DojoComponents.length == 0) {
|
if (guild.DojoComponents.length == 0) {
|
||||||
guild.DojoComponents = [
|
guild.DojoComponents.push({
|
||||||
{
|
|
||||||
_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();
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json(getDojoClient(guild, 0));
|
const payload: IGetGuildDojoRequest = req.body ? (JSON.parse(String(req.body)) as IGetGuildDojoRequest) : {};
|
||||||
|
res.json(await getDojoClient(guild, 0, payload.ComponentId));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface IGetGuildDojoRequest {
|
||||||
|
ComponentId?: string;
|
||||||
|
}
|
||||||
|
@ -1,11 +1,60 @@
|
|||||||
|
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 = (_req, res) => {
|
export const getGuildLogController: RequestHandler = async (req, res) => {
|
||||||
res.json({
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "GuildId");
|
||||||
|
if (inventory.GuildId) {
|
||||||
|
const guild = await Guild.findById(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;
|
||||||
|
}
|
||||||
|
@ -1,11 +1,21 @@
|
|||||||
|
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
|
import { generateRewardSeed } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { logger } from "@/src/utils/logger";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
export const getNewRewardSeedController: RequestHandler = (_req, res) => {
|
export const getNewRewardSeedController: RequestHandler = async (req, res) => {
|
||||||
res.json({ rewardSeed: generateRewardSeed() });
|
const accountId = await getAccountIdForRequest(req);
|
||||||
};
|
|
||||||
|
|
||||||
export function generateRewardSeed(): number {
|
const rewardSeed = generateRewardSeed();
|
||||||
const min = -Number.MAX_SAFE_INTEGER;
|
logger.debug(`generated new reward seed: ${rewardSeed}`);
|
||||||
const max = Number.MAX_SAFE_INTEGER;
|
await Inventory.updateOne(
|
||||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
{
|
||||||
|
accountOwnerId: accountId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RewardSeed: rewardSeed
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
res.json({ rewardSeed: rewardSeed });
|
||||||
|
};
|
||||||
|
92
src/controllers/api/giftingController.ts
Normal file
92
src/controllers/api/giftingController.ts
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { Account } from "@/src/models/loginModel";
|
||||||
|
import { createMessage } from "@/src/services/inboxService";
|
||||||
|
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||||
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
|
import { IPurchaseParams } from "@/src/types/purchaseTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { ExportFlavour } from "warframe-public-export-plus";
|
||||||
|
|
||||||
|
export const giftingController: RequestHandler = async (req, res) => {
|
||||||
|
const data = getJSONfromString<IGiftingRequest>(String(req.body));
|
||||||
|
if (data.PurchaseParams.Source != 0 || !data.PurchaseParams.UsePremium) {
|
||||||
|
throw new Error(`unexpected purchase params in gifting request: ${String(req.body)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const account = await Account.findOne(
|
||||||
|
data.RecipientId ? { _id: data.RecipientId.$oid } : { DisplayName: data.Recipient }
|
||||||
|
);
|
||||||
|
if (!account) {
|
||||||
|
res.status(400).send("9").end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const inventory = await getInventory(account._id.toString(), "Suits Settings");
|
||||||
|
|
||||||
|
// Cannot gift items to players that have not completed the tutorial.
|
||||||
|
if (inventory.Suits.length == 0) {
|
||||||
|
res.status(400).send("14").end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cannot gift to players who have gifting disabled.
|
||||||
|
// TODO: Also consider GIFT_MODE_FRIENDS once friends are implemented
|
||||||
|
if (inventory.Settings?.GiftMode == "GIFT_MODE_NONE") {
|
||||||
|
res.status(400).send("17").end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Cannot gift items with mastery requirement to players who are too low level. (Code 2)
|
||||||
|
// TODO: Cannot gift archwing items to players that have not completed the archwing quest. (Code 7)
|
||||||
|
// TODO: Cannot gift necramechs to players that have not completed heart of deimos. (Code 20)
|
||||||
|
|
||||||
|
const senderAccount = await getAccountForRequest(req);
|
||||||
|
const senderInventory = await getInventory(
|
||||||
|
senderAccount._id.toString(),
|
||||||
|
"PremiumCredits PremiumCreditsFree ActiveAvatarImageType GiftsRemaining"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (senderInventory.GiftsRemaining == 0) {
|
||||||
|
res.status(400).send("10").end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
senderInventory.GiftsRemaining -= 1;
|
||||||
|
|
||||||
|
updateCurrency(senderInventory, data.PurchaseParams.ExpectedPrice, true);
|
||||||
|
await senderInventory.save();
|
||||||
|
|
||||||
|
const senderName = getSuffixedName(senderAccount);
|
||||||
|
await createMessage(account._id, [
|
||||||
|
{
|
||||||
|
sndr: senderName,
|
||||||
|
msg: data.Message || "/Lotus/Language/Menu/GiftReceivedBody_NoCustomMessage",
|
||||||
|
arg: [
|
||||||
|
{
|
||||||
|
Key: "GIFTER_NAME",
|
||||||
|
Tag: senderName
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "GIFT_QUANTITY",
|
||||||
|
Tag: data.PurchaseParams.Quantity
|
||||||
|
}
|
||||||
|
],
|
||||||
|
sub: "/Lotus/Language/Menu/GiftReceivedSubject",
|
||||||
|
icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
|
||||||
|
gifts: [
|
||||||
|
{
|
||||||
|
GiftType: data.PurchaseParams.StoreItem
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IGiftingRequest {
|
||||||
|
PurchaseParams: IPurchaseParams;
|
||||||
|
Message?: string;
|
||||||
|
Recipient?: string;
|
||||||
|
RecipientId?: IOid;
|
||||||
|
buildLabel: string;
|
||||||
|
}
|
@ -1,29 +1,29 @@
|
|||||||
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 } from "@/src/services/inventoryService";
|
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
||||||
import { WeaponTypeInternal } from "@/src/services/itemDataService";
|
import { WeaponTypeInternal } from "@/src/services/itemDataService";
|
||||||
import { ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { ArtifactPolarity, EquipmentFeatures, IEquipmentClient } 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" // Not sure about hoverboards just coppied from modual crafting
|
"Hoverboards"
|
||||||
];
|
];
|
||||||
|
|
||||||
interface IGildWeaponRequest {
|
interface IGildWeaponRequest {
|
||||||
ItemName: string;
|
ItemName: string;
|
||||||
Recipe: string; // /Lotus/Weapons/SolarisUnited/LotusGildKitgunBlueprint
|
Recipe: string; // e.g. /Lotus/Weapons/SolarisUnited/LotusGildKitgunBlueprint
|
||||||
PolarizeSlot?: number;
|
PolarizeSlot?: number;
|
||||||
PolarizeValue?: ArtifactPolarity;
|
PolarizeValue?: ArtifactPolarity;
|
||||||
ItemId: string;
|
ItemId: string;
|
||||||
Category: WeaponTypeInternal | "Hoverboards";
|
Category: WeaponTypeInternal | "Hoverboards";
|
||||||
}
|
}
|
||||||
|
|
||||||
// In export there no recipes for gild action, so reputation and ressources only consumed visually
|
|
||||||
|
|
||||||
export const gildWeaponController: RequestHandler = async (req, res) => {
|
export const gildWeaponController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const data = getJSONfromString<IGildWeaponRequest>(String(req.body));
|
const data = getJSONfromString<IGildWeaponRequest>(String(req.body));
|
||||||
@ -40,7 +40,8 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const weapon = inventory[data.Category][weaponIndex];
|
const weapon = inventory[data.Category][weaponIndex];
|
||||||
weapon.Features = EquipmentFeatures.GILDED; // maybe 9 idk if DOUBLE_CAPACITY is also given
|
weapon.Features ??= 0;
|
||||||
|
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) {
|
||||||
@ -52,11 +53,29 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
inventory[data.Category][weaponIndex] = weapon;
|
inventory[data.Category][weaponIndex] = weapon;
|
||||||
await inventory.save();
|
const inventoryChanges: IInventoryChanges = {};
|
||||||
|
inventoryChanges[data.Category] = [weapon.toJSON<IEquipmentClient>()];
|
||||||
|
|
||||||
res.json({
|
const recipe = ExportRecipes[data.Recipe];
|
||||||
InventoryChanges: {
|
inventoryChanges.MiscItems = recipe.secretIngredients!.map(ingredient => ({
|
||||||
[data.Category]: [weapon]
|
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();
|
||||||
|
res.json({
|
||||||
|
InventoryChanges: inventoryChanges,
|
||||||
|
AffiliationMods: affiliationMods
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -2,8 +2,8 @@ import { RequestHandler } from "express";
|
|||||||
import { parseString } from "@/src/helpers/general";
|
import { parseString } from "@/src/helpers/general";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { IGroup } from "@/src/types/loginTypes";
|
|
||||||
import { giveKeyChainItem } from "@/src/services/questService";
|
import { giveKeyChainItem } from "@/src/services/questService";
|
||||||
|
import { IKeyChainRequest } from "@/src/types/requestTypes";
|
||||||
|
|
||||||
export const giveKeyChainTriggeredItemsController: RequestHandler = async (req, res) => {
|
export const giveKeyChainTriggeredItemsController: RequestHandler = async (req, res) => {
|
||||||
const accountId = parseString(req.query.accountId);
|
const accountId = parseString(req.query.accountId);
|
||||||
@ -15,9 +15,3 @@ export const giveKeyChainTriggeredItemsController: RequestHandler = async (req,
|
|||||||
|
|
||||||
res.send(inventoryChanges);
|
res.send(inventoryChanges);
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IKeyChainRequest {
|
|
||||||
KeyChain: string;
|
|
||||||
ChainStage: number;
|
|
||||||
Groups?: IGroup[];
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { IKeyChainRequest } from "@/src/controllers/api/giveKeyChainTriggeredItemsController";
|
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { giveKeyChainMessage } from "@/src/services/questService";
|
import { giveKeyChainMessage } from "@/src/services/questService";
|
||||||
|
import { IKeyChainRequest } from "@/src/types/requestTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
export const giveKeyChainTriggeredMessageController: RequestHandler = async (req, res) => {
|
export const giveKeyChainTriggeredMessageController: RequestHandler = async (req, res) => {
|
||||||
|
@ -20,11 +20,11 @@ export const giveQuestKeyRewardController: RequestHandler = async (req, res) =>
|
|||||||
//TODO: consider whishlist changes
|
//TODO: consider whishlist changes
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IQuestKeyRewardRequest {
|
interface IQuestKeyRewardRequest {
|
||||||
reward: IQuestKeyReward;
|
reward: IQuestKeyReward;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IQuestKeyReward {
|
interface IQuestKeyReward {
|
||||||
RewardType: string;
|
RewardType: string;
|
||||||
CouponType: string;
|
CouponType: string;
|
||||||
Icon: string;
|
Icon: string;
|
||||||
@ -38,7 +38,7 @@ export interface IQuestKeyReward {
|
|||||||
Duration: number;
|
Duration: number;
|
||||||
CouponSku: number;
|
CouponSku: number;
|
||||||
Syndicate: string;
|
Syndicate: string;
|
||||||
Milestones: any[];
|
//Milestones: any[];
|
||||||
ChooseSetIndex: number;
|
ChooseSetIndex: number;
|
||||||
NewSystemReward: boolean;
|
NewSystemReward: boolean;
|
||||||
_id: IOid;
|
_id: IOid;
|
||||||
|
@ -1,19 +1,8 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { InventoryDocumentProps } from "@/src/models/inventoryModels/inventoryModel";
|
import { addStartingGear, getInventory } from "@/src/services/inventoryService";
|
||||||
import {
|
|
||||||
addEquipment,
|
|
||||||
addItem,
|
|
||||||
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 { TPartialStartingGear } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { HydratedDocument } from "mongoose";
|
|
||||||
|
|
||||||
type TPartialStartingGear = Pick<IInventoryClient, "LongGuns" | "Suits" | "Pistols" | "Melee">;
|
|
||||||
|
|
||||||
export const giveStartingGearController: RequestHandler = async (req, res) => {
|
export const giveStartingGearController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
@ -25,72 +14,3 @@ export const giveStartingGearController: RequestHandler = async (req, res) => {
|
|||||||
|
|
||||||
res.send(inventoryChanges);
|
res.send(inventoryChanges);
|
||||||
};
|
};
|
||||||
|
|
||||||
//TODO: RawUpgrades might need to return a LastAdded
|
|
||||||
const awakeningRewards = [
|
|
||||||
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem1",
|
|
||||||
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem2",
|
|
||||||
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem3",
|
|
||||||
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem4",
|
|
||||||
"/Lotus/Types/Restoratives/LisetAutoHack",
|
|
||||||
"/Lotus/Upgrades/Mods/Warframe/AvatarShieldMaxMod"
|
|
||||||
];
|
|
||||||
|
|
||||||
export const addStartingGear = async (
|
|
||||||
inventory: HydratedDocument<IInventoryDatabase, InventoryDocumentProps>,
|
|
||||||
startingGear: TPartialStartingGear | undefined = undefined
|
|
||||||
): Promise<IInventoryChanges> => {
|
|
||||||
const { LongGuns, Pistols, Suits, Melee } = startingGear || {
|
|
||||||
LongGuns: [{ ItemType: "/Lotus/Weapons/Tenno/Rifle/Rifle" }],
|
|
||||||
Pistols: [{ ItemType: "/Lotus/Weapons/Tenno/Pistol/Pistol" }],
|
|
||||||
Suits: [{ ItemType: "/Lotus/Powersuits/Excalibur/Excalibur" }],
|
|
||||||
Melee: [{ ItemType: "/Lotus/Weapons/Tenno/Melee/LongSword/LongSword" }]
|
|
||||||
};
|
|
||||||
|
|
||||||
//TODO: properly merge weapon bin changes it is currently static here
|
|
||||||
const inventoryChanges: IInventoryChanges = {};
|
|
||||||
addEquipment(inventory, "LongGuns", LongGuns[0].ItemType, undefined, inventoryChanges);
|
|
||||||
addEquipment(inventory, "Pistols", Pistols[0].ItemType, undefined, inventoryChanges);
|
|
||||||
addEquipment(inventory, "Melee", Melee[0].ItemType, undefined, inventoryChanges);
|
|
||||||
addEquipment(inventory, "Suits", Suits[0].ItemType, undefined, inventoryChanges, { Configs: Suits[0].Configs });
|
|
||||||
addEquipment(
|
|
||||||
inventory,
|
|
||||||
"DataKnives",
|
|
||||||
"/Lotus/Weapons/Tenno/HackingDevices/TnHackingDevice/TnHackingDeviceWeapon",
|
|
||||||
undefined,
|
|
||||||
inventoryChanges,
|
|
||||||
{ XP: 450_000 }
|
|
||||||
);
|
|
||||||
addEquipment(
|
|
||||||
inventory,
|
|
||||||
"Scoops",
|
|
||||||
"/Lotus/Weapons/Tenno/Speedball/SpeedballWeaponTest",
|
|
||||||
undefined,
|
|
||||||
inventoryChanges
|
|
||||||
);
|
|
||||||
|
|
||||||
updateSlots(inventory, InventorySlot.SUITS, 0, 1);
|
|
||||||
updateSlots(inventory, InventorySlot.WEAPONS, 0, 3);
|
|
||||||
inventoryChanges.SuitBin = { count: 1, platinum: 0, Slots: -1 };
|
|
||||||
inventoryChanges.WeaponBin = { count: 3, platinum: 0, Slots: -3 };
|
|
||||||
|
|
||||||
await addItem(inventory, "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain");
|
|
||||||
inventory.ActiveQuest = "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain";
|
|
||||||
|
|
||||||
inventory.PremiumCredits = 50;
|
|
||||||
inventory.PremiumCreditsFree = 50;
|
|
||||||
inventoryChanges.PremiumCredits = 50;
|
|
||||||
inventoryChanges.PremiumCreditsFree = 50;
|
|
||||||
inventory.RegularCredits = 3000;
|
|
||||||
inventoryChanges.RegularCredits = 3000;
|
|
||||||
|
|
||||||
for (const item of awakeningRewards) {
|
|
||||||
const inventoryDelta = await addItem(inventory, item);
|
|
||||||
combineInventoryChanges(inventoryChanges, inventoryDelta.InventoryChanges);
|
|
||||||
}
|
|
||||||
|
|
||||||
inventory.PlayedParkourTutorial = true;
|
|
||||||
inventory.ReceivedStartingGear = true;
|
|
||||||
|
|
||||||
return inventoryChanges;
|
|
||||||
};
|
|
||||||
|
@ -1,44 +1,139 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getGuildForRequestEx } from "@/src/services/guildService";
|
import {
|
||||||
|
addGuildMemberMiscItemContribution,
|
||||||
|
getGuildForRequestEx,
|
||||||
|
getGuildVault,
|
||||||
|
hasAccessToDojo,
|
||||||
|
hasGuildPermission,
|
||||||
|
processFundedGuildTechProject,
|
||||||
|
processGuildTechProjectContributionsUpdate,
|
||||||
|
removePigmentsFromGuildMembers,
|
||||||
|
scaleRequiredCount,
|
||||||
|
setGuildTechLogState
|
||||||
|
} from "@/src/services/guildService";
|
||||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
|
import { ExportDojoRecipes } from "warframe-public-export-plus";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { addMiscItems, addRecipes, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
import {
|
||||||
|
addItem,
|
||||||
|
addMiscItems,
|
||||||
|
addRecipes,
|
||||||
|
combineInventoryChanges,
|
||||||
|
getInventory,
|
||||||
|
updateCurrency
|
||||||
|
} from "@/src/services/inventoryService";
|
||||||
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
import { config } from "@/src/services/configService";
|
||||||
|
import { GuildPermission, ITechProjectClient } from "@/src/types/guildTypes";
|
||||||
|
import { GuildMember } 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 = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
const guild = await getGuildForRequestEx(req, inventory);
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
const data = JSON.parse(String(req.body)) as TGuildTechRequest;
|
const data = JSON.parse(String(req.body)) as TGuildTechRequest;
|
||||||
const action = data.Action.split(",")[0];
|
if (data.Action == "Sync") {
|
||||||
if (action == "Sync") {
|
let needSave = false;
|
||||||
res.json({
|
const techProjects: ITechProjectClient[] = [];
|
||||||
TechProjects: guild.toJSON().TechProjects
|
if (guild.TechProjects) {
|
||||||
});
|
for (const project of guild.TechProjects) {
|
||||||
} else if (action == "Start") {
|
const techProject: ITechProjectClient = {
|
||||||
const recipe = ExportDojoRecipes.research[data.RecipeType!];
|
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 ||= setGuildTechLogState(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 ??= [];
|
guild.TechProjects ??= [];
|
||||||
if (!guild.TechProjects.find(x => x.ItemType == data.RecipeType)) {
|
if (!guild.TechProjects.find(x => x.ItemType == data.RecipeType)) {
|
||||||
|
const techProject =
|
||||||
|
guild.TechProjects[
|
||||||
guild.TechProjects.push({
|
guild.TechProjects.push({
|
||||||
ItemType: data.RecipeType!,
|
ItemType: data.RecipeType,
|
||||||
ReqCredits: scaleRequiredCount(recipe.price),
|
ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, recipe.price),
|
||||||
ReqItems: recipe.ingredients.map(x => ({
|
ReqItems: recipe.ingredients.map(x => ({
|
||||||
ItemType: x.ItemType,
|
ItemType: x.ItemType,
|
||||||
ItemCount: scaleRequiredCount(x.ItemCount)
|
ItemCount: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, x.ItemCount)
|
||||||
})),
|
})),
|
||||||
State: 0
|
State: 0
|
||||||
});
|
}) - 1
|
||||||
|
];
|
||||||
|
setGuildTechLogState(guild, techProject.ItemType, 5);
|
||||||
|
if (config.noDojoResearchCosts) {
|
||||||
|
processFundedGuildTechProject(guild, techProject, recipe);
|
||||||
|
} else {
|
||||||
|
if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
|
||||||
|
guild.ActiveDojoColorResearch = data.RecipeType;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await guild.save();
|
await guild.save();
|
||||||
res.end();
|
res.end();
|
||||||
} else if (action == "Contribute") {
|
} else if (data.Action == "Contribute") {
|
||||||
const contributions = data as IGuildTechContributeFields;
|
if (!hasAccessToDojo(inventory)) {
|
||||||
|
res.status(400).send("-1").end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const guildMember = (await GuildMember.findOne(
|
||||||
|
{ accountId, guildId: guild._id },
|
||||||
|
"RegularCreditsContributed MiscItemsContributed"
|
||||||
|
))!;
|
||||||
|
|
||||||
|
const contributions = data;
|
||||||
const techProject = guild.TechProjects!.find(x => x.ItemType == contributions.RecipeType)!;
|
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) {
|
if (contributions.RegularCredits > techProject.ReqCredits) {
|
||||||
contributions.RegularCredits = techProject.ReqCredits;
|
contributions.RegularCredits = techProject.ReqCredits;
|
||||||
}
|
}
|
||||||
techProject.ReqCredits -= contributions.RegularCredits;
|
techProject.ReqCredits -= contributions.RegularCredits;
|
||||||
|
|
||||||
|
guildMember.RegularCreditsContributed ??= 0;
|
||||||
|
guildMember.RegularCreditsContributed += 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 = [];
|
const miscItemChanges = [];
|
||||||
for (const miscItem of contributions.MiscItems) {
|
for (const miscItem of contributions.MiscItems) {
|
||||||
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
|
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
|
||||||
@ -51,30 +146,29 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
|||||||
ItemType: miscItem.ItemType,
|
ItemType: miscItem.ItemType,
|
||||||
ItemCount: miscItem.ItemCount * -1
|
ItemCount: miscItem.ItemCount * -1
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addGuildMemberMiscItemContribution(guildMember, miscItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addMiscItems(inventory, miscItemChanges);
|
addMiscItems(inventory, miscItemChanges);
|
||||||
const inventoryChanges: IInventoryChanges = {
|
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, contributions.RegularCredits, false);
|
||||||
...updateCurrency(inventory, contributions.RegularCredits, false),
|
inventoryChanges.MiscItems = miscItemChanges;
|
||||||
MiscItems: miscItemChanges
|
|
||||||
};
|
|
||||||
|
|
||||||
if (techProject.ReqCredits == 0 && !techProject.ReqItems.find(x => x.ItemCount > 0)) {
|
// Check if research is fully funded now.
|
||||||
// This research is now fully funded.
|
await processGuildTechProjectContributionsUpdate(guild, techProject);
|
||||||
techProject.State = 1;
|
|
||||||
const recipe = ExportDojoRecipes.research[data.RecipeType!];
|
|
||||||
techProject.CompletionDate = new Date(new Date().getTime() + recipe.time * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
await guild.save();
|
await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
|
||||||
await inventory.save();
|
|
||||||
res.json({
|
res.json({
|
||||||
InventoryChanges: inventoryChanges
|
InventoryChanges: inventoryChanges,
|
||||||
|
Vault: getGuildVault(guild)
|
||||||
});
|
});
|
||||||
} else if (action == "Buy") {
|
} else if (data.Action.split(",")[0] == "Buy") {
|
||||||
const purchase = data as IGuildTechBuyFields;
|
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 quantity = parseInt(data.Action.split(",")[1]);
|
||||||
const inventory = await getInventory(accountId);
|
|
||||||
const recipeChanges = [
|
const recipeChanges = [
|
||||||
{
|
{
|
||||||
ItemType: purchase.RecipeType,
|
ItemType: purchase.RecipeType,
|
||||||
@ -95,24 +189,68 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
|||||||
Recipes: recipeChanges
|
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 {
|
} else {
|
||||||
|
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
||||||
throw new Error(`unknown guildTech action: ${data.Action}`);
|
throw new Error(`unknown guildTech action: ${data.Action}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
type TGuildTechRequest = {
|
type TGuildTechRequest =
|
||||||
Action: string;
|
| { Action: "Sync" | "SomethingElseThatWeMightNotKnowAbout" }
|
||||||
} & Partial<IGuildTechStartFields> &
|
| IGuildTechBasicRequest
|
||||||
Partial<IGuildTechContributeFields>;
|
| IGuildTechContributeRequest;
|
||||||
|
|
||||||
interface IGuildTechStartFields {
|
interface IGuildTechBasicRequest {
|
||||||
|
Action: "Start" | "Fabricate" | "Pause" | "Unpause";
|
||||||
Mode: "Guild";
|
Mode: "Guild";
|
||||||
RecipeType: string;
|
RecipeType: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type IGuildTechBuyFields = IGuildTechStartFields;
|
interface IGuildTechBuyRequest {
|
||||||
|
Action: string;
|
||||||
|
Mode: "Guild";
|
||||||
|
RecipeType: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface IGuildTechContributeFields {
|
interface IGuildTechContributeRequest {
|
||||||
|
Action: "Contribute";
|
||||||
ResearchId: "";
|
ResearchId: "";
|
||||||
RecipeType: string;
|
RecipeType: string;
|
||||||
RegularCredits: number;
|
RegularCredits: number;
|
||||||
@ -120,8 +258,3 @@ interface IGuildTechContributeFields {
|
|||||||
VaultCredits: number;
|
VaultCredits: number;
|
||||||
VaultMiscItems: IMiscItem[];
|
VaultMiscItems: IMiscItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const scaleRequiredCount = (count: number): number => {
|
|
||||||
// The recipes in the export are for Moon clans. For now we'll just assume we only have Ghost clans.
|
|
||||||
return Math.max(1, Math.trunc(count / 100));
|
|
||||||
};
|
|
||||||
|
45
src/controllers/api/hubBlessingController.ts
Normal file
45
src/controllers/api/hubBlessingController.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { addBooster, getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { getRandomInt } from "@/src/services/rngService";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { ExportBoosters } from "warframe-public-export-plus";
|
||||||
|
|
||||||
|
export const hubBlessingController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const data = getJSONfromString<IHubBlessingRequest>(String(req.body));
|
||||||
|
const boosterType = ExportBoosters[data.booster].typeName;
|
||||||
|
if (req.query.mode == "send") {
|
||||||
|
const inventory = await getInventory(accountId, "BlessingCooldown Boosters");
|
||||||
|
inventory.BlessingCooldown = new Date(Date.now() + 86400000);
|
||||||
|
addBooster(boosterType, 3 * 3600, inventory);
|
||||||
|
await inventory.save();
|
||||||
|
|
||||||
|
let token = "";
|
||||||
|
for (let i = 0; i != 32; ++i) {
|
||||||
|
token += getRandomInt(0, 15).toString(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
BlessingCooldown: inventory.BlessingCooldown,
|
||||||
|
SendTime: Math.trunc(Date.now() / 1000).toString(),
|
||||||
|
Token: token
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const inventory = await getInventory(accountId, "Boosters");
|
||||||
|
addBooster(boosterType, 3 * 3600, inventory);
|
||||||
|
await inventory.save();
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
BoosterType: data.booster,
|
||||||
|
Sender: data.senderId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IHubBlessingRequest {
|
||||||
|
booster: string;
|
||||||
|
senderId?: string; // mode=request
|
||||||
|
sendTime?: string; // mode=request
|
||||||
|
token?: string; // mode=request
|
||||||
|
}
|
@ -1,21 +1,24 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { Inbox } from "@/src/models/inboxModel";
|
import { Inbox } from "@/src/models/inboxModel";
|
||||||
import {
|
import {
|
||||||
|
createMessage,
|
||||||
createNewEventMessages,
|
createNewEventMessages,
|
||||||
deleteAllMessagesRead,
|
deleteAllMessagesRead,
|
||||||
deleteMessageRead,
|
deleteMessageRead,
|
||||||
getAllMessagesSorted,
|
getAllMessagesSorted,
|
||||||
getMessage
|
getMessage
|
||||||
} from "@/src/services/inboxService";
|
} from "@/src/services/inboxService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountForRequest, getAccountFromSuffixedName, getSuffixedName } from "@/src/services/loginService";
|
||||||
import { addItems, getInventory } from "@/src/services/inventoryService";
|
import { addItems, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { ExportGear } from "warframe-public-export-plus";
|
import { ExportFlavour, ExportGear } from "warframe-public-export-plus";
|
||||||
|
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||||
|
|
||||||
export const inboxController: RequestHandler = async (req, res) => {
|
export const inboxController: RequestHandler = async (req, res) => {
|
||||||
const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query;
|
const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query;
|
||||||
|
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const account = await getAccountForRequest(req);
|
||||||
|
const accountId = account._id.toString();
|
||||||
|
|
||||||
if (deleteId) {
|
if (deleteId) {
|
||||||
if (deleteId === "DeleteAllRead") {
|
if (deleteId === "DeleteAllRead") {
|
||||||
@ -29,12 +32,12 @@ export const inboxController: RequestHandler = async (req, res) => {
|
|||||||
} else if (messageId) {
|
} else if (messageId) {
|
||||||
const message = await getMessage(messageId as string);
|
const message = await getMessage(messageId as string);
|
||||||
message.r = true;
|
message.r = true;
|
||||||
|
await message.save();
|
||||||
|
|
||||||
const attachmentItems = message.att;
|
const attachmentItems = message.att;
|
||||||
const attachmentCountedItems = message.countedAtt;
|
const attachmentCountedItems = message.countedAtt;
|
||||||
|
|
||||||
if (!attachmentItems && !attachmentCountedItems) {
|
if (!attachmentItems && !attachmentCountedItems && !message.gifts) {
|
||||||
await message.save();
|
|
||||||
|
|
||||||
res.status(200).end();
|
res.status(200).end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -54,9 +57,43 @@ export const inboxController: RequestHandler = async (req, res) => {
|
|||||||
if (attachmentCountedItems) {
|
if (attachmentCountedItems) {
|
||||||
await addItems(inventory, attachmentCountedItems, inventoryChanges);
|
await addItems(inventory, attachmentCountedItems, inventoryChanges);
|
||||||
}
|
}
|
||||||
|
if (message.gifts) {
|
||||||
|
const sender = await getAccountFromSuffixedName(message.sndr);
|
||||||
|
const recipientName = getSuffixedName(account);
|
||||||
|
const giftQuantity = message.arg!.find(x => x.Key == "GIFT_QUANTITY")!.Tag as number;
|
||||||
|
for (const gift of message.gifts) {
|
||||||
|
combineInventoryChanges(
|
||||||
|
inventoryChanges,
|
||||||
|
(await handleStoreItemAcquisition(gift.GiftType, inventory, giftQuantity)).InventoryChanges
|
||||||
|
);
|
||||||
|
if (sender) {
|
||||||
|
await createMessage(sender._id, [
|
||||||
|
{
|
||||||
|
sndr: recipientName,
|
||||||
|
msg: "/Lotus/Language/Menu/GiftReceivedConfirmationBody",
|
||||||
|
arg: [
|
||||||
|
{
|
||||||
|
Key: "RECIPIENT_NAME",
|
||||||
|
Tag: recipientName
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "GIFT_TYPE",
|
||||||
|
Tag: gift.GiftType
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "GIFT_QUANTITY",
|
||||||
|
Tag: giftQuantity
|
||||||
|
}
|
||||||
|
],
|
||||||
|
sub: "/Lotus/Language/Menu/GiftReceivedConfirmationSubject",
|
||||||
|
icon: ExportFlavour[inventory.ActiveAvatarImageType].icon,
|
||||||
|
highPriority: true
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
await message.save();
|
|
||||||
|
|
||||||
res.json({ InventoryChanges: inventoryChanges });
|
res.json({ InventoryChanges: inventoryChanges });
|
||||||
} else if (latestClientMessageId) {
|
} else if (latestClientMessageId) {
|
||||||
await createNewEventMessages(req);
|
await createNewEventMessages(req);
|
||||||
|
@ -1,24 +1,26 @@
|
|||||||
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 } from "@/src/services/inventoryService";
|
import { getInventory, addMiscItems, updateCurrency, addRecipes, freeUpSlot } from "@/src/services/inventoryService";
|
||||||
import { IOid } from "@/src/types/commonTypes";
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
import {
|
import {
|
||||||
IConsumedSuit,
|
IConsumedSuit,
|
||||||
IHelminthFoodRecord,
|
IHelminthFoodRecord,
|
||||||
IInfestedFoundryClient,
|
|
||||||
IInfestedFoundryDatabase,
|
|
||||||
IInventoryClient,
|
IInventoryClient,
|
||||||
IMiscItem,
|
IMiscItem,
|
||||||
ITypeCount
|
InventorySlot
|
||||||
} from "@/src/types/inventoryTypes/inventoryTypes";
|
} from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { ExportMisc, ExportRecipes } from "warframe-public-export-plus";
|
import { ExportMisc } from "warframe-public-export-plus";
|
||||||
import { getRecipe } from "@/src/services/itemDataService";
|
import { getRecipe } from "@/src/services/itemDataService";
|
||||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
|
||||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { colorToShard } from "@/src/helpers/shardHelper";
|
import { colorToShard } from "@/src/helpers/shardHelper";
|
||||||
import { config } from "@/src/services/configService";
|
import { config } from "@/src/services/configService";
|
||||||
|
import {
|
||||||
|
addInfestedFoundryXP,
|
||||||
|
applyCheatsToInfestedFoundry,
|
||||||
|
handleSubsumeCompletion
|
||||||
|
} from "@/src/services/infestedFoundryService";
|
||||||
|
|
||||||
export const infestedFoundryController: RequestHandler = async (req, res) => {
|
export const infestedFoundryController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
@ -27,7 +29,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
// shard installation
|
// shard installation
|
||||||
const request = getJSONfromString<IShardInstallRequest>(String(req.body));
|
const request = getJSONfromString<IShardInstallRequest>(String(req.body));
|
||||||
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.id(request.SuitId.$oid)!;
|
||||||
if (!suit.ArchonCrystalUpgrades || suit.ArchonCrystalUpgrades.length != 5) {
|
if (!suit.ArchonCrystalUpgrades || suit.ArchonCrystalUpgrades.length != 5) {
|
||||||
suit.ArchonCrystalUpgrades = [{}, {}, {}, {}, {}];
|
suit.ArchonCrystalUpgrades = [{}, {}, {}, {}, {}];
|
||||||
}
|
}
|
||||||
@ -55,19 +57,20 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
// shard removal
|
// shard removal
|
||||||
const request = getJSONfromString<IShardUninstallRequest>(String(req.body));
|
const request = getJSONfromString<IShardUninstallRequest>(String(req.body));
|
||||||
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.id(request.SuitId.$oid)!;
|
||||||
|
|
||||||
|
const miscItemChanges: IMiscItem[] = [];
|
||||||
|
if (suit.ArchonCrystalUpgrades![request.Slot].Color) {
|
||||||
// refund shard
|
// refund shard
|
||||||
const shard = Object.entries(colorToShard).find(
|
const shard = Object.entries(colorToShard).find(
|
||||||
([color]) => color == suit.ArchonCrystalUpgrades![request.Slot].Color
|
([color]) => color == suit.ArchonCrystalUpgrades![request.Slot].Color
|
||||||
)![1];
|
)![1];
|
||||||
const miscItemChanges = [
|
miscItemChanges.push({
|
||||||
{
|
|
||||||
ItemType: shard,
|
ItemType: shard,
|
||||||
ItemCount: 1
|
ItemCount: 1
|
||||||
}
|
});
|
||||||
];
|
|
||||||
addMiscItems(inventory, miscItemChanges);
|
addMiscItems(inventory, miscItemChanges);
|
||||||
|
}
|
||||||
|
|
||||||
// remove from suit
|
// remove from suit
|
||||||
suit.ArchonCrystalUpgrades![request.Slot] = {};
|
suit.ArchonCrystalUpgrades![request.Slot] = {};
|
||||||
@ -126,7 +129,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
const miscItemChanges: IMiscItem[] = [];
|
const miscItemChanges: IMiscItem[] = [];
|
||||||
let totalPercentagePointsGained = 0;
|
let totalPercentagePointsGained = 0;
|
||||||
|
|
||||||
const currentUnixSeconds = Math.trunc(new Date().getTime() / 1000);
|
const currentUnixSeconds = Math.trunc(Date.now() / 1000);
|
||||||
|
|
||||||
for (const contribution of request.ResourceContributions) {
|
for (const contribution of request.ResourceContributions) {
|
||||||
const snack = ExportMisc.helminthSnacks[contribution.ItemType];
|
const snack = ExportMisc.helminthSnacks[contribution.ItemType];
|
||||||
@ -258,11 +261,10 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
inventory.InfestedFoundry!.ConsumedSuits ??= [];
|
inventory.InfestedFoundry!.ConsumedSuits ??= [];
|
||||||
inventory.InfestedFoundry!.ConsumedSuits.push(consumedSuit);
|
inventory.InfestedFoundry!.ConsumedSuits.push(consumedSuit);
|
||||||
inventory.InfestedFoundry!.LastConsumedSuit = suit;
|
inventory.InfestedFoundry!.LastConsumedSuit = suit;
|
||||||
inventory.InfestedFoundry!.AbilityOverrideUnlockCooldown = new Date(
|
inventory.InfestedFoundry!.AbilityOverrideUnlockCooldown = new Date(Date.now() + 24 * 60 * 60 * 1000);
|
||||||
new Date().getTime() + 24 * 60 * 60 * 1000
|
|
||||||
);
|
|
||||||
const recipeChanges = addInfestedFoundryXP(inventory.InfestedFoundry!, 1600_00);
|
const recipeChanges = addInfestedFoundryXP(inventory.InfestedFoundry!, 1600_00);
|
||||||
addRecipes(inventory, recipeChanges);
|
addRecipes(inventory, recipeChanges);
|
||||||
|
freeUpSlot(inventory, InventorySlot.SUITS);
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
const infestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry!;
|
const infestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry!;
|
||||||
applyCheatsToInfestedFoundry(infestedFoundry);
|
applyCheatsToInfestedFoundry(infestedFoundry);
|
||||||
@ -307,7 +309,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
const request = getJSONfromString<IHelminthInvigorationRequest>(String(req.body));
|
const request = getJSONfromString<IHelminthInvigorationRequest>(String(req.body));
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
const suit = inventory.Suits.id(request.SuitId.$oid)!;
|
const suit = inventory.Suits.id(request.SuitId.$oid)!;
|
||||||
const upgradesExpiry = new Date(new Date().getTime() + 7 * 24 * 60 * 60 * 1000);
|
const upgradesExpiry = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
||||||
suit.OffensiveUpgrade = request.OffensiveUpgradeType;
|
suit.OffensiveUpgrade = request.OffensiveUpgradeType;
|
||||||
suit.DefensiveUpgrade = request.DefensiveUpgradeType;
|
suit.DefensiveUpgrade = request.DefensiveUpgradeType;
|
||||||
suit.UpgradesExpiry = upgradesExpiry;
|
suit.UpgradesExpiry = upgradesExpiry;
|
||||||
@ -354,6 +356,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
||||||
throw new Error(`unhandled infestedFoundry mode: ${String(req.query.mode)}`);
|
throw new Error(`unhandled infestedFoundry mode: ${String(req.query.mode)}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -381,116 +384,11 @@ interface IHelminthFeedRequest {
|
|||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addInfestedFoundryXP = (infestedFoundry: IInfestedFoundryDatabase, delta: number): ITypeCount[] => {
|
|
||||||
const recipeChanges: ITypeCount[] = [];
|
|
||||||
infestedFoundry.XP ??= 0;
|
|
||||||
const prevXP = infestedFoundry.XP;
|
|
||||||
infestedFoundry.XP += delta;
|
|
||||||
if (prevXP < 2250_00 && infestedFoundry.XP >= 2250_00) {
|
|
||||||
infestedFoundry.Slots ??= 0;
|
|
||||||
infestedFoundry.Slots += 3;
|
|
||||||
}
|
|
||||||
if (prevXP < 5625_00 && infestedFoundry.XP >= 5625_00) {
|
|
||||||
recipeChanges.push({
|
|
||||||
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthShieldsBlueprint",
|
|
||||||
ItemCount: 1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (prevXP < 10125_00 && infestedFoundry.XP >= 10125_00) {
|
|
||||||
recipeChanges.push({ ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthHackBlueprint", ItemCount: 1 });
|
|
||||||
}
|
|
||||||
if (prevXP < 15750_00 && infestedFoundry.XP >= 15750_00) {
|
|
||||||
infestedFoundry.Slots ??= 0;
|
|
||||||
infestedFoundry.Slots += 10;
|
|
||||||
}
|
|
||||||
if (prevXP < 22500_00 && infestedFoundry.XP >= 22500_00) {
|
|
||||||
recipeChanges.push({
|
|
||||||
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthAmmoEfficiencyBlueprint",
|
|
||||||
ItemCount: 1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (prevXP < 30375_00 && infestedFoundry.XP >= 30375_00) {
|
|
||||||
recipeChanges.push({ ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthStunBlueprint", ItemCount: 1 });
|
|
||||||
}
|
|
||||||
if (prevXP < 39375_00 && infestedFoundry.XP >= 39375_00) {
|
|
||||||
infestedFoundry.Slots ??= 0;
|
|
||||||
infestedFoundry.Slots += 20;
|
|
||||||
}
|
|
||||||
if (prevXP < 60750_00 && infestedFoundry.XP >= 60750_00) {
|
|
||||||
recipeChanges.push({ ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthStatusBlueprint", ItemCount: 1 });
|
|
||||||
}
|
|
||||||
if (prevXP < 73125_00 && infestedFoundry.XP >= 73125_00) {
|
|
||||||
infestedFoundry.Slots = 1;
|
|
||||||
}
|
|
||||||
if (prevXP < 86625_00 && infestedFoundry.XP >= 86625_00) {
|
|
||||||
recipeChanges.push({
|
|
||||||
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthShieldArmorBlueprint",
|
|
||||||
ItemCount: 1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (prevXP < 101250_00 && infestedFoundry.XP >= 101250_00) {
|
|
||||||
recipeChanges.push({
|
|
||||||
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthProcBlockBlueprint",
|
|
||||||
ItemCount: 1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (prevXP < 117000_00 && infestedFoundry.XP >= 117000_00) {
|
|
||||||
recipeChanges.push({
|
|
||||||
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthEnergyShareBlueprint",
|
|
||||||
ItemCount: 1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (prevXP < 133875_00 && infestedFoundry.XP >= 133875_00) {
|
|
||||||
recipeChanges.push({
|
|
||||||
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthMaxStatusBlueprint",
|
|
||||||
ItemCount: 1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (prevXP < 151875_00 && infestedFoundry.XP >= 151875_00) {
|
|
||||||
recipeChanges.push({
|
|
||||||
ItemType: "/Lotus/Types/Recipes/AbilityOverrides/HelminthTreasureBlueprint",
|
|
||||||
ItemCount: 1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return recipeChanges;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IHelminthSubsumeRequest {
|
interface IHelminthSubsumeRequest {
|
||||||
SuitId: IOid;
|
SuitId: IOid;
|
||||||
Recipe: string;
|
Recipe: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const handleSubsumeCompletion = (inventory: TInventoryDatabaseDocument): ITypeCount[] => {
|
|
||||||
const [recipeType] = Object.entries(ExportRecipes).find(
|
|
||||||
([_recipeType, recipe]) =>
|
|
||||||
recipe.secretIngredientAction == "SIA_WARFRAME_ABILITY" &&
|
|
||||||
recipe.secretIngredients![0].ItemType == inventory.InfestedFoundry!.LastConsumedSuit!.ItemType
|
|
||||||
)!;
|
|
||||||
inventory.InfestedFoundry!.LastConsumedSuit = undefined;
|
|
||||||
inventory.InfestedFoundry!.AbilityOverrideUnlockCooldown = undefined;
|
|
||||||
const recipeChanges: ITypeCount[] = [
|
|
||||||
{
|
|
||||||
ItemType: recipeType,
|
|
||||||
ItemCount: 1
|
|
||||||
}
|
|
||||||
];
|
|
||||||
addRecipes(inventory, recipeChanges);
|
|
||||||
return recipeChanges;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const applyCheatsToInfestedFoundry = (infestedFoundry: IInfestedFoundryClient): void => {
|
|
||||||
if (config.infiniteHelminthMaterials) {
|
|
||||||
infestedFoundry.Resources = [
|
|
||||||
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthCalx", Count: 1000 },
|
|
||||||
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthBiotics", Count: 1000 },
|
|
||||||
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthSynthetics", Count: 1000 },
|
|
||||||
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthPheromones", Count: 1000 },
|
|
||||||
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthBile", Count: 1000 },
|
|
||||||
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthOxides", Count: 1000 }
|
|
||||||
];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IHelminthOfferingsUpdate {
|
interface IHelminthOfferingsUpdate {
|
||||||
OfferingsIndex: number;
|
OfferingsIndex: number;
|
||||||
SuitTypes: string[];
|
SuitTypes: string[];
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { Inventory, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
import { Inventory, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
import { config } from "@/src/services/configService";
|
import { config } from "@/src/services/configService";
|
||||||
import allDialogue from "@/static/fixed_responses/allDialogue.json";
|
import allDialogue from "@/static/fixed_responses/allDialogue.json";
|
||||||
@ -13,13 +13,15 @@ import {
|
|||||||
ExportResources,
|
ExportResources,
|
||||||
ExportVirtuals
|
ExportVirtuals
|
||||||
} from "warframe-public-export-plus";
|
} from "warframe-public-export-plus";
|
||||||
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "./infestedFoundryController";
|
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "@/src/services/infestedFoundryService";
|
||||||
import { allDailyAffiliationKeys } from "@/src/services/inventoryService";
|
import { addMiscItems, allDailyAffiliationKeys, createLibraryDailyTask } from "@/src/services/inventoryService";
|
||||||
|
import { logger } from "@/src/utils/logger";
|
||||||
|
import { catBreadHash } from "@/src/helpers/stringHelpers";
|
||||||
|
|
||||||
export const inventoryController: RequestHandler = async (request, response) => {
|
export const inventoryController: RequestHandler = async (request, response) => {
|
||||||
const account = await getAccountForRequest(request);
|
const accountId = await getAccountIdForRequest(request);
|
||||||
|
|
||||||
const inventory = await Inventory.findOne({ accountOwnerId: account._id.toString() });
|
const inventory = await Inventory.findOne({ accountOwnerId: accountId });
|
||||||
|
|
||||||
if (!inventory) {
|
if (!inventory) {
|
||||||
response.status(400).json({ error: "inventory was undefined" });
|
response.status(400).json({ error: "inventory was undefined" });
|
||||||
@ -27,15 +29,57 @@ export const inventoryController: RequestHandler = async (request, response) =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle daily reset
|
// Handle daily reset
|
||||||
const today: number = Math.trunc(new Date().getTime() / 86400000);
|
if (!inventory.NextRefill || Date.now() >= inventory.NextRefill.getTime()) {
|
||||||
if (account.LastLoginDay != today) {
|
|
||||||
account.LastLoginDay = today;
|
|
||||||
await account.save();
|
|
||||||
|
|
||||||
for (const key of allDailyAffiliationKeys) {
|
for (const key of allDailyAffiliationKeys) {
|
||||||
inventory[key] = 16000 + inventory.PlayerLevel * 500;
|
inventory[key] = 16000 + inventory.PlayerLevel * 500;
|
||||||
}
|
}
|
||||||
inventory.DailyFocus = 250000 + inventory.PlayerLevel * 5000;
|
inventory.DailyFocus = 250000 + inventory.PlayerLevel * 5000;
|
||||||
|
inventory.GiftsRemaining = Math.max(8, inventory.PlayerLevel);
|
||||||
|
inventory.TradesRemaining = inventory.PlayerLevel;
|
||||||
|
|
||||||
|
inventory.LibraryAvailableDailyTaskInfo = createLibraryDailyTask();
|
||||||
|
|
||||||
|
if (inventory.NextRefill) {
|
||||||
|
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 = Math.min(
|
||||||
|
numArgonCrystals,
|
||||||
|
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();
|
await inventory.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +111,7 @@ export const getInventoryResponse = async (
|
|||||||
inventoryResponse.RegularCredits = 999999999;
|
inventoryResponse.RegularCredits = 999999999;
|
||||||
}
|
}
|
||||||
if (config.infinitePlatinum) {
|
if (config.infinitePlatinum) {
|
||||||
inventoryResponse.PremiumCreditsFree = 999999999;
|
inventoryResponse.PremiumCreditsFree = 0;
|
||||||
inventoryResponse.PremiumCredits = 999999999;
|
inventoryResponse.PremiumCredits = 999999999;
|
||||||
}
|
}
|
||||||
if (config.infiniteEndo) {
|
if (config.infiniteEndo) {
|
||||||
@ -207,8 +251,9 @@ export const getInventoryResponse = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (config.noDailyStandingLimits) {
|
if (config.noDailyStandingLimits) {
|
||||||
|
const spoofedDailyAffiliation = Math.max(999_999, 16000 + inventoryResponse.PlayerLevel * 500);
|
||||||
for (const key of allDailyAffiliationKeys) {
|
for (const key of allDailyAffiliationKeys) {
|
||||||
inventoryResponse[key] = 999_999;
|
inventoryResponse[key] = spoofedDailyAffiliation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,19 +261,16 @@ export const getInventoryResponse = async (
|
|||||||
applyCheatsToInfestedFoundry(inventoryResponse.InfestedFoundry);
|
applyCheatsToInfestedFoundry(inventoryResponse.InfestedFoundry);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fix for #380
|
|
||||||
inventoryResponse.NextRefill = { $date: { $numberLong: "9999999999999" } };
|
|
||||||
|
|
||||||
// This determines if the "void fissures" tab is shown in navigation.
|
|
||||||
inventoryResponse.HasOwnedVoidProjectionsPreviously = true;
|
|
||||||
|
|
||||||
// Omitting this field so opening the navigation resyncs the inventory which is more desirable for typical usage.
|
// Omitting this field so opening the navigation resyncs the inventory which is more desirable for typical usage.
|
||||||
//inventoryResponse.LastInventorySync = toOid(new Types.ObjectId());
|
//inventoryResponse.LastInventorySync = toOid(new Types.ObjectId());
|
||||||
|
|
||||||
|
// Set 2FA enabled so trading post can be used
|
||||||
|
inventoryResponse.HWIDProtectEnabled = true;
|
||||||
|
|
||||||
return inventoryResponse;
|
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);
|
||||||
}
|
}
|
||||||
@ -255,15 +297,6 @@ const resourceGetParent = (resourceName: string): string | undefined => {
|
|||||||
if (resourceName in ExportResources) {
|
if (resourceName in ExportResources) {
|
||||||
return ExportResources[resourceName].parentName;
|
return ExportResources[resourceName].parentName;
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
return ExportVirtuals[resourceName]?.parentName;
|
return ExportVirtuals[resourceName]?.parentName;
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is FNV1a-32 except operating under modulus 2^31 because JavaScript is stinky and likes producing negative integers out of nowhere.
|
|
||||||
const catBreadHash = (name: string): number => {
|
|
||||||
let hash = 2166136261;
|
|
||||||
for (let i = 0; i != name.length; ++i) {
|
|
||||||
hash = (hash ^ name.charCodeAt(i)) & 0x7fffffff;
|
|
||||||
hash = (hash * 16777619) & 0x7fffffff;
|
|
||||||
}
|
|
||||||
return hash;
|
|
||||||
};
|
|
||||||
|
@ -3,6 +3,7 @@ import { getInventory, 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
|
||||||
@ -20,14 +21,20 @@ import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
|||||||
|
|
||||||
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;
|
||||||
|
|
||||||
//TODO: check which slot was purchased because pvpBonus is also possible
|
if (body.Bin != InventorySlot.SUITS && body.Bin != InventorySlot.PVE_LOADOUTS) {
|
||||||
|
logger.warn(`unexpected slot purchase of type ${body.Bin}, account may be overcharged`);
|
||||||
|
}
|
||||||
|
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
const currencyChanges = updateCurrency(inventory, 20, true);
|
const currencyChanges = updateCurrency(inventory, 20, true);
|
||||||
updateSlots(inventory, InventorySlot.PVE_LOADOUTS, 1, 1);
|
updateSlots(inventory, body.Bin, 1, 1);
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
|
|
||||||
res.json({ InventoryChanges: currencyChanges });
|
res.json({ InventoryChanges: currencyChanges });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface IInventorySlotsRequest {
|
||||||
|
Bin: InventorySlot;
|
||||||
|
}
|
||||||
|
@ -2,12 +2,13 @@ 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";
|
||||||
|
|
||||||
const joinSessionController: RequestHandler = (_req, res) => {
|
export const joinSessionController: RequestHandler = (req, res) => {
|
||||||
const reqBody = JSON.parse(String(_req.body));
|
const reqBody = JSON.parse(String(req.body)) as IJoinSessionRequest;
|
||||||
logger.debug(`JoinSession Request`, { reqBody });
|
logger.debug(`JoinSession Request`, { reqBody });
|
||||||
const req = JSON.parse(String(_req.body));
|
const session = getSessionByID(reqBody.sessionIds[0]);
|
||||||
const session = getSessionByID(req.sessionIds[0] as string);
|
|
||||||
res.json({ rewardSeed: session?.rewardSeed, sessionId: { $oid: session?.sessionId } });
|
res.json({ rewardSeed: session?.rewardSeed, sessionId: { $oid: session?.sessionId } });
|
||||||
};
|
};
|
||||||
|
|
||||||
export { joinSessionController };
|
interface IJoinSessionRequest {
|
||||||
|
sessionIds: string[];
|
||||||
|
}
|
||||||
|
@ -19,10 +19,12 @@ export const loginController: RequestHandler = async (request, response) => {
|
|||||||
? request.query.buildLabel.split(" ").join("+")
|
? request.query.buildLabel.split(" ").join("+")
|
||||||
: buildConfig.buildLabel;
|
: 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("@"));
|
const nameFromEmail = loginRequest.email.substring(0, loginRequest.email.indexOf("@"));
|
||||||
let name = nameFromEmail;
|
let name = nameFromEmail || loginRequest.email.substring(1) || "SpaceNinja";
|
||||||
if (await isNameTaken(name)) {
|
if (await isNameTaken(name)) {
|
||||||
let suffix = 0;
|
let suffix = 0;
|
||||||
do {
|
do {
|
||||||
@ -40,11 +42,10 @@ export const loginController: RequestHandler = async (request, response) => {
|
|||||||
ForceLogoutVersion: 0,
|
ForceLogoutVersion: 0,
|
||||||
ConsentNeeded: false,
|
ConsentNeeded: false,
|
||||||
TrackedSettings: [],
|
TrackedSettings: [],
|
||||||
Nonce: nonce,
|
Nonce: nonce
|
||||||
LatestEventMessageDate: new Date(0)
|
|
||||||
});
|
});
|
||||||
logger.debug("created new account");
|
logger.debug("created new account");
|
||||||
response.json(createLoginResponse(newAccount, buildLabel));
|
response.json(createLoginResponse(myAddress, newAccount, buildLabel));
|
||||||
return;
|
return;
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
@ -53,24 +54,37 @@ export const loginController: RequestHandler = async (request, response) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//email not found or incorrect password
|
if (!account) {
|
||||||
if (!account || !isCorrectPassword(loginRequest.password, account.password)) {
|
response.status(400).json({ error: "unknown user" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isCorrectPassword(loginRequest.password, account.password)) {
|
||||||
response.status(400).json({ error: "incorrect login data" });
|
response.status(400).json({ error: "incorrect login data" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (account.Nonce == 0 || loginRequest.ClientType != "webui") {
|
if (loginRequest.ClientType == "webui") {
|
||||||
|
if (!account.Nonce) {
|
||||||
|
account.ClientType = "webui";
|
||||||
account.Nonce = nonce;
|
account.Nonce = nonce;
|
||||||
}
|
}
|
||||||
if (loginRequest.ClientType != "webui") {
|
} 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();
|
account.CountryCode = loginRequest.lang.toUpperCase();
|
||||||
}
|
}
|
||||||
await account.save();
|
await account.save();
|
||||||
|
|
||||||
response.json(createLoginResponse(account.toJSON(), buildLabel));
|
response.json(createLoginResponse(myAddress, account.toJSON(), buildLabel));
|
||||||
};
|
};
|
||||||
|
|
||||||
const createLoginResponse = (account: IDatabaseAccountJson, buildLabel: string): ILoginResponse => {
|
const createLoginResponse = (myAddress: string, account: IDatabaseAccountJson, buildLabel: string): ILoginResponse => {
|
||||||
return {
|
return {
|
||||||
id: account.id,
|
id: account.id,
|
||||||
DisplayName: account.DisplayName,
|
DisplayName: account.DisplayName,
|
||||||
@ -84,9 +98,9 @@ const createLoginResponse = (account: IDatabaseAccountJson, buildLabel: string):
|
|||||||
TrackedSettings: account.TrackedSettings,
|
TrackedSettings: account.TrackedSettings,
|
||||||
Nonce: account.Nonce,
|
Nonce: account.Nonce,
|
||||||
Groups: [],
|
Groups: [],
|
||||||
IRC: config.myIrcAddresses ?? [config.myAddress],
|
IRC: config.myIrcAddresses ?? [myAddress],
|
||||||
platformCDNs: config.platformCDNs,
|
platformCDNs: [`https://${myAddress}/`],
|
||||||
HUB: config.hubAddress,
|
HUB: `https://${myAddress}/api/`,
|
||||||
NRS: config.NRS,
|
NRS: config.NRS,
|
||||||
DTLS: 99,
|
DTLS: 99,
|
||||||
BuildLabel: buildLabel,
|
BuildLabel: buildLabel,
|
||||||
|
@ -1,8 +1,55 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import loginRewards from "@/static/fixed_responses/loginRewards.json";
|
import { getAccountForRequest } from "@/src/services/loginService";
|
||||||
|
import {
|
||||||
|
claimLoginReward,
|
||||||
|
getRandomLoginRewards,
|
||||||
|
ILoginRewardsReponse,
|
||||||
|
isLoginRewardAChoice,
|
||||||
|
setAccountGotLoginRewardToday
|
||||||
|
} from "@/src/services/loginRewardService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
|
||||||
const loginRewardsController: RequestHandler = (_req, res) => {
|
export const loginRewardsController: RequestHandler = async (req, res) => {
|
||||||
res.json(loginRewards);
|
const account = await getAccountForRequest(req);
|
||||||
|
const today = Math.trunc(Date.now() / 86400000) * 86400;
|
||||||
|
const isMilestoneDay = account.LoginDays == 5 || account.LoginDays % 50 == 0;
|
||||||
|
const nextMilestoneDay = account.LoginDays < 5 ? 5 : (Math.trunc(account.LoginDays / 50) + 1) * 50;
|
||||||
|
|
||||||
|
if (today == account.LastLoginRewardDate) {
|
||||||
|
res.json({
|
||||||
|
DailyTributeInfo: {
|
||||||
|
IsMilestoneDay: isMilestoneDay,
|
||||||
|
IsChooseRewardSet: isLoginRewardAChoice(account),
|
||||||
|
LoginDays: account.LoginDays,
|
||||||
|
NextMilestoneReward: "",
|
||||||
|
NextMilestoneDay: nextMilestoneDay
|
||||||
|
}
|
||||||
|
} satisfies ILoginRewardsReponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const inventory = await getInventory(account._id.toString());
|
||||||
|
const randomRewards = getRandomLoginRewards(account, inventory);
|
||||||
|
const response: ILoginRewardsReponse = {
|
||||||
|
DailyTributeInfo: {
|
||||||
|
Rewards: randomRewards,
|
||||||
|
IsMilestoneDay: isMilestoneDay,
|
||||||
|
IsChooseRewardSet: randomRewards.length != 1,
|
||||||
|
LoginDays: account.LoginDays,
|
||||||
|
NextMilestoneReward: "",
|
||||||
|
NextMilestoneDay: nextMilestoneDay,
|
||||||
|
HasChosenReward: false
|
||||||
|
},
|
||||||
|
LastLoginRewardDate: today
|
||||||
};
|
};
|
||||||
|
if (!isMilestoneDay && randomRewards.length == 1) {
|
||||||
|
response.DailyTributeInfo.HasChosenReward = true;
|
||||||
|
response.DailyTributeInfo.ChosenReward = randomRewards[0];
|
||||||
|
response.DailyTributeInfo.NewInventory = await claimLoginReward(inventory, randomRewards[0]);
|
||||||
|
await inventory.save();
|
||||||
|
|
||||||
export { loginRewardsController };
|
setAccountGotLoginRewardToday(account);
|
||||||
|
await account.save();
|
||||||
|
}
|
||||||
|
res.json(response);
|
||||||
|
};
|
||||||
|
65
src/controllers/api/loginRewardsSelectionController.ts
Normal file
65
src/controllers/api/loginRewardsSelectionController.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import {
|
||||||
|
claimLoginReward,
|
||||||
|
getRandomLoginRewards,
|
||||||
|
setAccountGotLoginRewardToday
|
||||||
|
} from "@/src/services/loginRewardService";
|
||||||
|
import { getAccountForRequest } from "@/src/services/loginService";
|
||||||
|
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||||
|
import { 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();
|
||||||
|
|
||||||
|
setAccountGotLoginRewardToday(account);
|
||||||
|
await account.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"
|
||||||
|
];
|
@ -1,19 +1,28 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
|
||||||
import { Account } from "@/src/models/loginModel";
|
import { Account } from "@/src/models/loginModel";
|
||||||
|
|
||||||
const logoutController: RequestHandler = async (req, res) => {
|
export const logoutController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
if (!req.query.accountId) {
|
||||||
const account = await Account.findOne({ _id: accountId });
|
throw new Error("Request is missing accountId parameter");
|
||||||
if (account) {
|
|
||||||
account.Nonce = 0;
|
|
||||||
await account.save();
|
|
||||||
}
|
}
|
||||||
|
const nonce: number = parseInt(req.query.nonce as string);
|
||||||
|
if (!nonce) {
|
||||||
|
throw new Error("Request is missing nonce parameter");
|
||||||
|
}
|
||||||
|
|
||||||
|
await Account.updateOne(
|
||||||
|
{
|
||||||
|
_id: req.query.accountId,
|
||||||
|
Nonce: nonce
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Nonce: 0
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
res.writeHead(200, {
|
res.writeHead(200, {
|
||||||
"Content-Type": "text/html",
|
"Content-Type": "text/html",
|
||||||
"Content-Length": 1
|
"Content-Length": 1
|
||||||
});
|
});
|
||||||
res.end("1");
|
res.end("1");
|
||||||
};
|
};
|
||||||
|
|
||||||
export { logoutController };
|
|
||||||
|
27
src/controllers/api/maturePetController.ts
Normal file
27
src/controllers/api/maturePetController.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const maturePetController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "KubrowPets");
|
||||||
|
const data = getJSONfromString<IMaturePetRequest>(String(req.body));
|
||||||
|
const details = inventory.KubrowPets.id(data.petId)!.Details!;
|
||||||
|
details.IsPuppy = data.revert;
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
petId: data.petId,
|
||||||
|
updateCollar: true,
|
||||||
|
armorSkins: ["", "", ""],
|
||||||
|
furPatterns: data.revert
|
||||||
|
? ["", "", ""]
|
||||||
|
: [details.DominantTraits.FurPattern, details.DominantTraits.FurPattern, details.DominantTraits.FurPattern],
|
||||||
|
unmature: data.revert
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IMaturePetRequest {
|
||||||
|
petId: string;
|
||||||
|
revert: boolean;
|
||||||
|
}
|
@ -47,14 +47,13 @@ import { logger } from "@/src/utils/logger";
|
|||||||
- [ ] FpsSamples
|
- [ ] FpsSamples
|
||||||
*/
|
*/
|
||||||
//move credit calc in here, return MissionRewards: [] if no reward info
|
//move credit calc in here, return MissionRewards: [] if no reward info
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
||||||
export const missionInventoryUpdateController: RequestHandler = async (req, res): Promise<void> => {
|
export const missionInventoryUpdateController: RequestHandler = async (req, res): Promise<void> => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const missionReport = getJSONfromString<IMissionInventoryUpdateRequest>((req.body as string).toString());
|
const missionReport = getJSONfromString<IMissionInventoryUpdateRequest>((req.body as string).toString());
|
||||||
logger.debug("mission report:", missionReport);
|
logger.debug("mission report:", missionReport);
|
||||||
|
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
const inventoryUpdates = addMissionInventoryUpdates(inventory, missionReport);
|
const inventoryUpdates = await addMissionInventoryUpdates(inventory, missionReport);
|
||||||
|
|
||||||
if (missionReport.MissionStatus !== "GS_SUCCESS") {
|
if (missionReport.MissionStatus !== "GS_SUCCESS") {
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
|
@ -1,33 +1,24 @@
|
|||||||
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 { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
|
||||||
import {
|
import {
|
||||||
getInventory,
|
getInventory,
|
||||||
updateCurrency,
|
updateCurrency,
|
||||||
addEquipment,
|
addEquipment,
|
||||||
addMiscItems,
|
addMiscItems,
|
||||||
applyDefaultUpgrades
|
applyDefaultUpgrades,
|
||||||
|
occupySlot,
|
||||||
|
productCategoryToInventoryBin,
|
||||||
|
combineInventoryChanges,
|
||||||
|
addSpecialItem
|
||||||
} from "@/src/services/inventoryService";
|
} from "@/src/services/inventoryService";
|
||||||
import { ExportWeapons } from "warframe-public-export-plus";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
import { getDefaultUpgrades } from "@/src/services/itemDataService";
|
||||||
const modularWeaponTypes: Record<string, TEquipmentKey> = {
|
import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
|
||||||
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary": "LongGuns",
|
import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam": "LongGuns",
|
import { getRandomInt } from "@/src/services/rngService";
|
||||||
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryLauncher": "LongGuns",
|
import { ExportSentinels } from "warframe-public-export-plus";
|
||||||
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun": "LongGuns",
|
import { Status } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimarySniper": "LongGuns",
|
|
||||||
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary": "Pistols",
|
|
||||||
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam": "Pistols",
|
|
||||||
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun": "Pistols",
|
|
||||||
"/Lotus/Weapons/Ostron/Melee/LotusModularWeapon": "Melee",
|
|
||||||
"/Lotus/Weapons/Sentients/OperatorAmplifiers/OperatorAmpWeapon": "OperatorAmps",
|
|
||||||
"/Lotus/Types/Vehicles/Hoverboard/HoverboardSuit": "Hoverboards",
|
|
||||||
"/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;
|
||||||
@ -43,11 +34,110 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
|
|||||||
const category = modularWeaponTypes[data.WeaponType];
|
const category = modularWeaponTypes[data.WeaponType];
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
const defaultUpgrades = getDefaultUpgrades(data.Parts);
|
||||||
const configs = applyDefaultUpgrades(inventory, ExportWeapons[data.Parts[0]]?.defaultUpgrades);
|
const defaultOverwrites: Partial<IEquipmentDatabase> = {
|
||||||
|
Configs: applyDefaultUpgrades(inventory, defaultUpgrades)
|
||||||
|
};
|
||||||
|
const inventoryChanges: IInventoryChanges = {};
|
||||||
|
if (category == "KubrowPets") {
|
||||||
|
const traits = {
|
||||||
|
"/Lotus/Types/Friendly/Pets/CreaturePets/ArmoredInfestedCatbrowPetPowerSuit": {
|
||||||
|
BaseColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorRareBase",
|
||||||
|
SecondaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorRareSecondary",
|
||||||
|
TertiaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorRareTertiary",
|
||||||
|
AccentColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorRareAccent",
|
||||||
|
EyeColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorRareEyes",
|
||||||
|
FurPattern: "/Lotus/Types/Game/InfestedKavatPet/Patterns/InfestedCritterPatternDefault",
|
||||||
|
Personality: data.WeaponType,
|
||||||
|
BodyType: "/Lotus/Types/Game/CatbrowPet/BodyTypes/InfestedCatbrowPetRegularBodyType",
|
||||||
|
Head: "/Lotus/Types/Game/InfestedKavatPet/Heads/InfestedCritterHeadC"
|
||||||
|
},
|
||||||
|
"/Lotus/Types/Friendly/Pets/CreaturePets/HornedInfestedCatbrowPetPowerSuit": {
|
||||||
|
BaseColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorUncommonBase",
|
||||||
|
SecondaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorUncommonSecondary",
|
||||||
|
TertiaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorUncommonTertiary",
|
||||||
|
AccentColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorUncommonAccent",
|
||||||
|
EyeColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorUncommonEyes",
|
||||||
|
FurPattern: "/Lotus/Types/Game/InfestedKavatPet/Patterns/InfestedCritterPatternDefault",
|
||||||
|
Personality: data.WeaponType,
|
||||||
|
BodyType: "/Lotus/Types/Game/CatbrowPet/BodyTypes/InfestedCatbrowPetRegularBodyType",
|
||||||
|
Head: "/Lotus/Types/Game/InfestedKavatPet/Heads/InfestedCritterHeadB"
|
||||||
|
},
|
||||||
|
"/Lotus/Types/Friendly/Pets/CreaturePets/MedjayPredatorKubrowPetPowerSuit": {
|
||||||
|
BaseColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorRareBase",
|
||||||
|
SecondaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorRareSecondary",
|
||||||
|
TertiaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorRareTertiary",
|
||||||
|
AccentColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorRareAccent",
|
||||||
|
EyeColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorRareEyes",
|
||||||
|
FurPattern: "/Lotus/Types/Game/InfestedPredatorPet/Patterns/InfestedPredatorPatternDefault",
|
||||||
|
Personality: data.WeaponType,
|
||||||
|
BodyType: "/Lotus/Types/Game/KubrowPet/BodyTypes/InfestedKubrowPetBodyType",
|
||||||
|
Head: "/Lotus/Types/Game/InfestedPredatorPet/Heads/InfestedPredatorHeadA"
|
||||||
|
},
|
||||||
|
"/Lotus/Types/Friendly/Pets/CreaturePets/PharaohPredatorKubrowPetPowerSuit": {
|
||||||
|
BaseColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorUncommonBase",
|
||||||
|
SecondaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorUncommonSecondary",
|
||||||
|
TertiaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorUncommonTertiary",
|
||||||
|
AccentColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorUncommonAccent",
|
||||||
|
EyeColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorUncommonEyes",
|
||||||
|
FurPattern: "/Lotus/Types/Game/InfestedPredatorPet/Patterns/InfestedPredatorPatternDefault",
|
||||||
|
Personality: data.WeaponType,
|
||||||
|
BodyType: "/Lotus/Types/Game/KubrowPet/BodyTypes/InfestedKubrowPetBodyType",
|
||||||
|
Head: "/Lotus/Types/Game/InfestedPredatorPet/Heads/InfestedPredatorHeadB"
|
||||||
|
},
|
||||||
|
"/Lotus/Types/Friendly/Pets/CreaturePets/VizierPredatorKubrowPetPowerSuit": {
|
||||||
|
BaseColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorCommonBase",
|
||||||
|
SecondaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorCommonSecondary",
|
||||||
|
TertiaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorCommonTertiary",
|
||||||
|
AccentColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorCommonAccent",
|
||||||
|
EyeColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorCommonEyes",
|
||||||
|
FurPattern: "/Lotus/Types/Game/InfestedPredatorPet/Patterns/InfestedPredatorPatternDefault",
|
||||||
|
Personality: data.WeaponType,
|
||||||
|
BodyType: "/Lotus/Types/Game/KubrowPet/BodyTypes/InfestedKubrowPetBodyType",
|
||||||
|
Head: "/Lotus/Types/Game/InfestedPredatorPet/Heads/InfestedPredatorHeadC"
|
||||||
|
},
|
||||||
|
"/Lotus/Types/Friendly/Pets/CreaturePets/VulpineInfestedCatbrowPetPowerSuit": {
|
||||||
|
BaseColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorCommonBase",
|
||||||
|
SecondaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorCommonSecondary",
|
||||||
|
TertiaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorCommonTertiary",
|
||||||
|
AccentColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorCommonAccent",
|
||||||
|
EyeColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorCommonEyes",
|
||||||
|
FurPattern: "/Lotus/Types/Game/InfestedKavatPet/Patterns/InfestedCritterPatternDefault",
|
||||||
|
Personality: data.WeaponType,
|
||||||
|
BodyType: "/Lotus/Types/Game/CatbrowPet/BodyTypes/InfestedCatbrowPetRegularBodyType",
|
||||||
|
Head: "/Lotus/Types/Game/InfestedKavatPet/Heads/InfestedCritterHeadA"
|
||||||
|
}
|
||||||
|
}[data.WeaponType];
|
||||||
|
|
||||||
// Give weapon
|
if (!traits) {
|
||||||
const inventoryChanges = addEquipment(inventory, category, data.WeaponType, data.Parts, {}, { Configs: configs });
|
throw new Error(`unknown KubrowPets type: ${data.WeaponType}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultOverwrites.Details = {
|
||||||
|
Name: "",
|
||||||
|
IsPuppy: false,
|
||||||
|
HasCollar: true,
|
||||||
|
PrintsRemaining: 2,
|
||||||
|
Status: Status.StatusStasis,
|
||||||
|
HatchDate: new Date(Math.trunc(Date.now() / 86400000) * 86400000),
|
||||||
|
IsMale: !!getRandomInt(0, 1),
|
||||||
|
Size: getRandomInt(70, 100) / 100,
|
||||||
|
DominantTraits: traits,
|
||||||
|
RecessiveTraits: traits
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only save mutagen & antigen in the ModularParts.
|
||||||
|
defaultOverwrites.ModularParts = [data.Parts[1], data.Parts[2]];
|
||||||
|
|
||||||
|
for (const specialItem of ExportSentinels[data.WeaponType].exalted!) {
|
||||||
|
addSpecialItem(inventory, specialItem, inventoryChanges);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addEquipment(inventory, category, data.WeaponType, data.Parts, inventoryChanges, defaultOverwrites);
|
||||||
|
combineInventoryChanges(inventoryChanges, occupySlot(inventory, productCategoryToInventoryBin(category)!, false));
|
||||||
|
if (defaultUpgrades) {
|
||||||
|
inventoryChanges.RawUpgrades = defaultUpgrades.map(x => ({ ItemType: x.ItemType, ItemCount: 1 }));
|
||||||
|
}
|
||||||
|
|
||||||
// Remove credits & parts
|
// Remove credits & parts
|
||||||
const miscItemChanges = [];
|
const miscItemChanges = [];
|
||||||
@ -59,7 +149,13 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
|
|||||||
}
|
}
|
||||||
const currencyChanges = updateCurrency(
|
const currencyChanges = updateCurrency(
|
||||||
inventory,
|
inventory,
|
||||||
category == "Hoverboards" || category == "MoaPets" ? 5000 : 4000,
|
category == "Hoverboards" ||
|
||||||
|
category == "MoaPets" ||
|
||||||
|
category == "LongGuns" ||
|
||||||
|
category == "Pistols" ||
|
||||||
|
category == "KubrowPets"
|
||||||
|
? 5000
|
||||||
|
: 4000, // Definitely correct for Melee & OperatorAmps
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
addMiscItems(inventory, miscItemChanges);
|
addMiscItems(inventory, miscItemChanges);
|
||||||
|
@ -1,8 +1,185 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import modularWeaponSale from "@/static/fixed_responses/modularWeaponSale.json";
|
import { ExportWeapons } from "warframe-public-export-plus";
|
||||||
|
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";
|
||||||
|
|
||||||
const modularWeaponSaleController: RequestHandler = (_req, res) => {
|
export const modularWeaponSaleController: RequestHandler = async (req, res) => {
|
||||||
res.json(modularWeaponSale);
|
const partTypeToParts: Record<string, string[]> = {};
|
||||||
|
for (const [uniqueName, data] of Object.entries(ExportWeapons)) {
|
||||||
|
if (data.partType && data.premiumPrice) {
|
||||||
|
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)}`);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export { modularWeaponSaleController };
|
const getSaleInfos = (partTypeToParts: Record<string, string[]>, day: number): IModularWeaponSaleInfo[] => {
|
||||||
|
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) {
|
||||||
|
partsCost += ExportWeapons[part].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;
|
||||||
|
}
|
||||||
|
@ -12,15 +12,17 @@ export const nameWeaponController: RequestHandler = async (req, res) => {
|
|||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
const body = getJSONfromString<INameWeaponRequest>(String(req.body));
|
const body = getJSONfromString<INameWeaponRequest>(String(req.body));
|
||||||
const item = inventory[req.query.Category as string as TEquipmentKey].find(
|
const item = inventory[req.query.Category as string as TEquipmentKey].id(req.query.ItemId as string)!;
|
||||||
item => item._id.toString() == (req.query.ItemId as string)
|
|
||||||
)!;
|
|
||||||
if (body.ItemName != "") {
|
if (body.ItemName != "") {
|
||||||
item.ItemName = body.ItemName;
|
item.ItemName = body.ItemName;
|
||||||
} else {
|
} else {
|
||||||
item.ItemName = undefined;
|
item.ItemName = undefined;
|
||||||
}
|
}
|
||||||
const currencyChanges = updateCurrency(inventory, "webui" in req.query ? 0 : 15, true);
|
const currencyChanges = updateCurrency(
|
||||||
|
inventory,
|
||||||
|
req.query.Category == "Horses" || "webui" in req.query ? 0 : 15,
|
||||||
|
true
|
||||||
|
);
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({
|
res.json({
|
||||||
InventoryChanges: currencyChanges
|
InventoryChanges: currencyChanges
|
||||||
|
210
src/controllers/api/nemesisController.ts
Normal file
210
src/controllers/api/nemesisController.ts
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
// Update destination damage type if desired
|
||||||
|
if (body.UseSourceDmgType) {
|
||||||
|
destFingerprint.buffs[0].Tag = sourceFingerprint.buffs[0].Tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upgrade destination damage value
|
||||||
|
const destDamage = 0.25 + (destFingerprint.buffs[0].Value / 0x3fffffff) * (0.6 - 0.25);
|
||||||
|
const sourceDamage = 0.25 + (sourceFingerprint.buffs[0].Value / 0x3fffffff) * (0.6 - 0.25);
|
||||||
|
let newDamage = Math.max(destDamage, sourceDamage) * 1.1;
|
||||||
|
if (newDamage >= 0.5794998) {
|
||||||
|
newDamage = 0.6;
|
||||||
|
}
|
||||||
|
destFingerprint.buffs[0].Value = Math.trunc(((newDamage - 0.25) / (0.6 - 0.25)) * 0x3fffffff);
|
||||||
|
|
||||||
|
// Commit fingerprint
|
||||||
|
destWeapon.UpgradeFingerprint = JSON.stringify(destFingerprint);
|
||||||
|
|
||||||
|
// Remove source weapon
|
||||||
|
inventory[body.Category].pull({ _id: body.SourceWeapon.$oid });
|
||||||
|
freeUpSlot(inventory, InventorySlot.WEAPONS);
|
||||||
|
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
InventoryChanges: {
|
||||||
|
[body.Category]: [destWeapon.toJSON()],
|
||||||
|
RemovedIdItems: [{ ItemId: body.SourceWeapon }]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if ((req.query.mode as string) == "p") {
|
||||||
|
const inventory = await getInventory(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"
|
||||||
|
];
|
116
src/controllers/api/placeDecoInComponentController.ts
Normal file
116
src/controllers/api/placeDecoInComponentController.ts
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import {
|
||||||
|
getDojoClient,
|
||||||
|
getGuildForRequestEx,
|
||||||
|
getVaultMiscItemCount,
|
||||||
|
hasAccessToDojo,
|
||||||
|
hasGuildPermission,
|
||||||
|
processDojoBuildMaterialsGathered,
|
||||||
|
scaleRequiredCount
|
||||||
|
} 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, ExportResources } 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 ??= [];
|
||||||
|
if (request.MoveId) {
|
||||||
|
const deco = component.Decos.find(x => x._id.equals(request.MoveId))!;
|
||||||
|
deco.Pos = request.Pos;
|
||||||
|
deco.Rot = request.Rot;
|
||||||
|
} else {
|
||||||
|
const deco =
|
||||||
|
component.Decos[
|
||||||
|
component.Decos.push({
|
||||||
|
_id: new Types.ObjectId(),
|
||||||
|
Type: request.Type,
|
||||||
|
Pos: request.Pos,
|
||||||
|
Rot: request.Rot,
|
||||||
|
Name: request.Name,
|
||||||
|
Sockets: request.Sockets
|
||||||
|
}) - 1
|
||||||
|
];
|
||||||
|
const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == request.Type);
|
||||||
|
if (meta) {
|
||||||
|
if (meta.capacityCost) {
|
||||||
|
component.DecoCapacity -= meta.capacityCost;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)![0];
|
||||||
|
if (deco.Sockets !== undefined) {
|
||||||
|
guild.VaultFusionTreasures!.find(x => x.ItemType == itemType && x.Sockets == deco.Sockets)!.ItemCount -=
|
||||||
|
1;
|
||||||
|
} else {
|
||||||
|
guild.VaultShipDecorations!.find(x => x.ItemType == itemType)!.ItemCount -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!meta || (meta.price == 0 && meta.ingredients.length == 0)) {
|
||||||
|
deco.CompletionTime = new Date();
|
||||||
|
} else if (guild.AutoContributeFromVault && guild.VaultRegularCredits && guild.VaultMiscItems) {
|
||||||
|
if (guild.VaultRegularCredits >= scaleRequiredCount(guild.Tier, meta.price)) {
|
||||||
|
let enoughMiscItems = true;
|
||||||
|
for (const ingredient of meta.ingredients) {
|
||||||
|
if (
|
||||||
|
getVaultMiscItemCount(guild, ingredient.ItemType) <
|
||||||
|
scaleRequiredCount(guild.Tier, ingredient.ItemCount)
|
||||||
|
) {
|
||||||
|
enoughMiscItems = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (enoughMiscItems) {
|
||||||
|
guild.VaultRegularCredits -= scaleRequiredCount(guild.Tier, meta.price);
|
||||||
|
deco.RegularCredits = scaleRequiredCount(guild.Tier, meta.price);
|
||||||
|
|
||||||
|
deco.MiscItems = [];
|
||||||
|
for (const ingredient of meta.ingredients) {
|
||||||
|
guild.VaultMiscItems.find(x => x.ItemType == ingredient.ItemType)!.ItemCount -=
|
||||||
|
scaleRequiredCount(guild.Tier, ingredient.ItemCount);
|
||||||
|
deco.MiscItems.push({
|
||||||
|
ItemType: ingredient.ItemType,
|
||||||
|
ItemCount: scaleRequiredCount(guild.Tier, ingredient.ItemCount)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deco.CompletionTime = new Date(Date.now() + meta.time * 1000);
|
||||||
|
processDojoBuildMaterialsGathered(guild, meta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
Sockets?: number;
|
||||||
|
Scale?: number; // only provided alongside MoveId and seems to always be 1
|
||||||
|
MoveId?: string;
|
||||||
|
ShipDeco?: boolean;
|
||||||
|
VaultDeco?: boolean;
|
||||||
|
}
|
@ -6,7 +6,7 @@ import { RequestHandler } from "express";
|
|||||||
|
|
||||||
export const playerSkillsController: RequestHandler = async (req, res) => {
|
export const playerSkillsController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId, "PlayerSkills");
|
||||||
const request = getJSONfromString<IPlayerSkillsRequest>(String(req.body));
|
const request = getJSONfromString<IPlayerSkillsRequest>(String(req.body));
|
||||||
|
|
||||||
const oldRank: number = inventory.PlayerSkills[request.Skill as keyof IPlayerSkills];
|
const oldRank: number = inventory.PlayerSkills[request.Skill as keyof IPlayerSkills];
|
||||||
|
75
src/controllers/api/postGuildAdvertisementController.ts
Normal file
75
src/controllers/api/postGuildAdvertisementController.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { GuildAd, GuildMember } from "@/src/models/guildModel";
|
||||||
|
import {
|
||||||
|
addGuildMemberMiscItemContribution,
|
||||||
|
addVaultMiscItems,
|
||||||
|
getGuildForRequestEx,
|
||||||
|
getVaultMiscItemCount,
|
||||||
|
hasGuildPermissionEx
|
||||||
|
} from "@/src/services/guildService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { getVendorManifestByTypeName } from "@/src/services/serversideVendorsService";
|
||||||
|
import { GuildPermission } from "@/src/types/guildTypes";
|
||||||
|
import { IPurchaseParams } from "@/src/types/purchaseTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const postGuildAdvertisementController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "GuildId MiscItems");
|
||||||
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
|
const guildMember = (await GuildMember.findOne({ accountId, guildId: guild._id }))!;
|
||||||
|
if (!hasGuildPermissionEx(guild, guildMember, GuildPermission.Advertiser)) {
|
||||||
|
res.status(400).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const payload = getJSONfromString<IPostGuildAdvertisementRequest>(String(req.body));
|
||||||
|
|
||||||
|
// Handle resource cost
|
||||||
|
const vendor = getVendorManifestByTypeName(
|
||||||
|
"/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest"
|
||||||
|
)!;
|
||||||
|
const offer = vendor.VendorInfo.ItemManifest.find(x => x.StoreItem == payload.PurchaseParams.StoreItem)!;
|
||||||
|
if (getVaultMiscItemCount(guild, offer.ItemPrices![0].ItemType) >= offer.ItemPrices![0].ItemCount) {
|
||||||
|
addVaultMiscItems(guild, [
|
||||||
|
{
|
||||||
|
ItemType: offer.ItemPrices![0].ItemType,
|
||||||
|
ItemCount: offer.ItemPrices![0].ItemCount * -1
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
const miscItem = inventory.MiscItems.find(x => x.ItemType == offer.ItemPrices![0].ItemType);
|
||||||
|
if (!miscItem || miscItem.ItemCount < offer.ItemPrices![0].ItemCount) {
|
||||||
|
res.status(400).json("Insufficient funds");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
miscItem.ItemCount -= offer.ItemPrices![0].ItemCount;
|
||||||
|
addGuildMemberMiscItemContribution(guildMember, offer.ItemPrices![0]);
|
||||||
|
await guildMember.save();
|
||||||
|
await inventory.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create or update ad
|
||||||
|
await GuildAd.findOneAndUpdate(
|
||||||
|
{ GuildId: guild._id },
|
||||||
|
{
|
||||||
|
Emblem: guild.Emblem,
|
||||||
|
Expiry: new Date(Date.now() + 12 * 3600 * 1000),
|
||||||
|
Features: payload.Features,
|
||||||
|
GuildName: guild.Name,
|
||||||
|
MemberCount: await GuildMember.countDocuments({ guildId: guild._id, status: 0 }),
|
||||||
|
RecruitMsg: payload.RecruitMsg,
|
||||||
|
Tier: guild.Tier
|
||||||
|
},
|
||||||
|
{ upsert: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IPostGuildAdvertisementRequest {
|
||||||
|
Features: number;
|
||||||
|
RecruitMsg: string;
|
||||||
|
Languages: string[];
|
||||||
|
PurchaseParams: IPurchaseParams;
|
||||||
|
}
|
@ -1,19 +1,24 @@
|
|||||||
import { getDojoClient, getGuildForRequest } from "@/src/services/guildService";
|
import { config } from "@/src/services/configService";
|
||||||
|
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";
|
||||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
|
|
||||||
|
|
||||||
export const queueDojoComponentDestructionController: RequestHandler = async (req, res) => {
|
export const queueDojoComponentDestructionController: RequestHandler = async (req, res) => {
|
||||||
const guild = await getGuildForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const componentId = req.query.componentId as string;
|
const inventory = await getInventory(accountId, "GuildId LevelKeys");
|
||||||
const component = guild.DojoComponents!.splice(
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
guild.DojoComponents!.findIndex(x => x._id.toString() === componentId),
|
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Architect))) {
|
||||||
1
|
res.json({ DojoRequestStatus: -1 });
|
||||||
)[0];
|
return;
|
||||||
const room = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf);
|
|
||||||
if (room) {
|
|
||||||
guild.DojoCapacity -= room.capacity;
|
|
||||||
guild.DojoEnergy -= room.energy;
|
|
||||||
}
|
}
|
||||||
|
const componentId = req.query.componentId as string;
|
||||||
|
|
||||||
|
guild.DojoComponents.id(componentId)!.DestructionTime = new Date(
|
||||||
|
(Math.trunc(Date.now() / 1000) + (config.fastDojoRoomDestruction ? 5 : 2 * 3600)) * 1000
|
||||||
|
);
|
||||||
|
|
||||||
await guild.save();
|
await guild.save();
|
||||||
res.json(getDojoClient(guild, 1));
|
res.json(await getDojoClient(guild, 0, componentId));
|
||||||
};
|
};
|
||||||
|
34
src/controllers/api/redeemPromoCodeController.ts
Normal file
34
src/controllers/api/redeemPromoCodeController.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
import glyphCodes from "@/static/fixed_responses/glyphsCodes.json";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { addItem, getInventory } from "@/src/services/inventoryService";
|
||||||
|
|
||||||
|
export const redeemPromoCodeController: RequestHandler = async (req, res) => {
|
||||||
|
const body = getJSONfromString<IRedeemPromoCodeRequest>(String(req.body));
|
||||||
|
if (!(body.codeId in glyphCodes)) {
|
||||||
|
res.status(400).send("INVALID_CODE").end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "FlavourItems");
|
||||||
|
const acquiredGlyphs: string[] = [];
|
||||||
|
for (const glyph of (glyphCodes as Record<string, string[]>)[body.codeId]) {
|
||||||
|
if (!inventory.FlavourItems.find(x => x.ItemType == glyph)) {
|
||||||
|
acquiredGlyphs.push(glyph);
|
||||||
|
await addItem(inventory, glyph);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (acquiredGlyphs.length == 0) {
|
||||||
|
res.status(400).send("USED_CODE").end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
FlavourItems: acquiredGlyphs
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IRedeemPromoCodeRequest {
|
||||||
|
codeId: string;
|
||||||
|
}
|
23
src/controllers/api/releasePetController.ts
Normal file
23
src/controllers/api/releasePetController.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const releasePetController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "RegularCredits KubrowPets");
|
||||||
|
const payload = getJSONfromString<IReleasePetRequest>(String(req.body));
|
||||||
|
|
||||||
|
const inventoryChanges = updateCurrency(inventory, 25000, false);
|
||||||
|
|
||||||
|
inventoryChanges.RemovedIdItems = [{ ItemId: { $oid: payload.petId } }];
|
||||||
|
inventory.KubrowPets.pull({ _id: payload.petId });
|
||||||
|
|
||||||
|
await inventory.save();
|
||||||
|
res.json({ inventoryChanges }); // Not a mistake; it's "inventoryChanges" here.
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IReleasePetRequest {
|
||||||
|
recipeName: "/Lotus/Types/Game/KubrowPet/ReleasePetRecipe";
|
||||||
|
petId: string;
|
||||||
|
}
|
38
src/controllers/api/removeFromAllianceController.ts
Normal file
38
src/controllers/api/removeFromAllianceController.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
|
import { deleteAlliance } from "@/src/services/guildService";
|
||||||
|
import { getAccountForRequest } from "@/src/services/loginService";
|
||||||
|
import { GuildPermission } from "@/src/types/guildTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const removeFromAllianceController: RequestHandler = async (req, res) => {
|
||||||
|
// Check requester is a warlord in their guild
|
||||||
|
const account = await getAccountForRequest(req);
|
||||||
|
const guildMember = (await GuildMember.findOne({ accountId: account._id, status: 0 }))!;
|
||||||
|
if (guildMember.rank > 1) {
|
||||||
|
res.status(400).json({ Error: 104 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let allianceMember = (await AllianceMember.findOne({ guildId: guildMember.guildId }))!;
|
||||||
|
if (!guildMember.guildId.equals(req.query.guildId as string)) {
|
||||||
|
// Removing a guild that is not our own needs additional permissions
|
||||||
|
if (!(allianceMember.Permissions & GuildPermission.Ruler)) {
|
||||||
|
res.status(400).json({ Error: 104 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update allianceMember to point to the alliance to kick
|
||||||
|
allianceMember = (await AllianceMember.findOne({ guildId: req.query.guildId }))!;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allianceMember.Permissions & GuildPermission.Ruler) {
|
||||||
|
await deleteAlliance(allianceMember.allianceId);
|
||||||
|
} else {
|
||||||
|
await Promise.all([
|
||||||
|
await Guild.updateOne({ _id: allianceMember.guildId }, { $unset: { AllianceId: "" } }),
|
||||||
|
await AllianceMember.deleteOne({ _id: allianceMember._id })
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
};
|
88
src/controllers/api/removeFromGuildController.ts
Normal file
88
src/controllers/api/removeFromGuildController.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { GuildMember } from "@/src/models/guildModel";
|
||||||
|
import { Inbox } from "@/src/models/inboxModel";
|
||||||
|
import { Account } from "@/src/models/loginModel";
|
||||||
|
import { deleteGuild, getGuildForRequest, hasGuildPermission, removeDojoKeyItems } from "@/src/services/guildService";
|
||||||
|
import { createMessage } from "@/src/services/inboxService";
|
||||||
|
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.rank == 0) {
|
||||||
|
await deleteGuild(guild._id);
|
||||||
|
} else {
|
||||||
|
if (guildMember.status == 0) {
|
||||||
|
const inventory = await getInventory(payload.userId, "GuildId LevelKeys Recipes");
|
||||||
|
inventory.GuildId = undefined;
|
||||||
|
removeDojoKeyItems(inventory);
|
||||||
|
await inventory.save();
|
||||||
|
} else if (guildMember.status == 1) {
|
||||||
|
// TOVERIFY: Is this inbox message actually sent on live?
|
||||||
|
await createMessage(guildMember.accountId, [
|
||||||
|
{
|
||||||
|
sndr: "/Lotus/Language/Bosses/Ordis",
|
||||||
|
msg: "/Lotus/Language/Clan/RejectedFromClan",
|
||||||
|
sub: "/Lotus/Language/Clan/RejectedFromClanHeader",
|
||||||
|
arg: [
|
||||||
|
{
|
||||||
|
Key: "PLAYER_NAME",
|
||||||
|
Tag: (await Account.findOne({ _id: guildMember.accountId }, "DisplayName"))!.DisplayName
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "CLAN_NAME",
|
||||||
|
Tag: guild.Name
|
||||||
|
}
|
||||||
|
]
|
||||||
|
// TOVERIFY: If this message is sent on live, is it highPriority?
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
} else if (guildMember.status == 2) {
|
||||||
|
// Delete the inbox message for the invite
|
||||||
|
await Inbox.deleteOne({
|
||||||
|
ownerId: guildMember.accountId,
|
||||||
|
contextInfo: guild._id.toString(),
|
||||||
|
acceptAction: "GUILD_INVITE"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await GuildMember.deleteOne({ _id: guildMember._id });
|
||||||
|
|
||||||
|
guild.RosterActivity ??= [];
|
||||||
|
if (isKick) {
|
||||||
|
const kickee = (await Account.findById(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;
|
||||||
|
}
|
@ -2,19 +2,28 @@ import { RequestHandler } from "express";
|
|||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { IUnveiledRivenFingerprint, randomiseRivenStats } from "@/src/helpers/rivenFingerprintHelper";
|
import { createUnveiledRivenFingerprint, randomiseRivenStats, RivenFingerprint } from "@/src/helpers/rivenHelper";
|
||||||
import { ExportUpgrades } from "warframe-public-export-plus";
|
import { ExportUpgrades } from "warframe-public-export-plus";
|
||||||
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
|
|
||||||
export const rerollRandomModController: RequestHandler = async (req, res) => {
|
export const rerollRandomModController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const request = getJSONfromString<RerollRandomModRequest>(String(req.body));
|
const request = getJSONfromString<RerollRandomModRequest>(String(req.body));
|
||||||
if ("ItemIds" in request) {
|
if ("ItemIds" in request) {
|
||||||
const inventory = await getInventory(accountId, "Upgrades MiscItems");
|
const inventory = await getInventory(accountId, "Upgrades MiscItems");
|
||||||
const upgrade = inventory.Upgrades.id(request.ItemIds[0])!;
|
const changes: IChange[] = [];
|
||||||
const fingerprint = JSON.parse(upgrade.UpgradeFingerprint!) as IUnveiledRivenFingerprint;
|
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;
|
fingerprint.rerolls ??= 0;
|
||||||
const kuvaCost = fingerprint.rerolls < rerollCosts.length ? rerollCosts[fingerprint.rerolls] : 3500;
|
const kuvaCost = fingerprint.rerolls < rerollCosts.length ? rerollCosts[fingerprint.rerolls] : 3500;
|
||||||
|
totalKuvaCost += kuvaCost;
|
||||||
addMiscItems(inventory, [
|
addMiscItems(inventory, [
|
||||||
{
|
{
|
||||||
ItemType: "/Lotus/Types/Items/MiscItems/Kuva",
|
ItemType: "/Lotus/Types/Items/MiscItems/Kuva",
|
||||||
@ -27,18 +36,20 @@ export const rerollRandomModController: RequestHandler = async (req, res) => {
|
|||||||
|
|
||||||
randomiseRivenStats(ExportUpgrades[upgrade.ItemType], fingerprint);
|
randomiseRivenStats(ExportUpgrades[upgrade.ItemType], fingerprint);
|
||||||
upgrade.PendingRerollFingerprint = JSON.stringify(fingerprint);
|
upgrade.PendingRerollFingerprint = JSON.stringify(fingerprint);
|
||||||
|
}
|
||||||
|
|
||||||
|
changes.push({
|
||||||
|
ItemId: { $oid: request.ItemIds[0] },
|
||||||
|
UpgradeFingerprint: upgrade.UpgradeFingerprint,
|
||||||
|
PendingRerollFingerprint: upgrade.PendingRerollFingerprint
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
changes: [
|
changes: changes,
|
||||||
{
|
cost: totalKuvaCost
|
||||||
ItemId: { $oid: request.ItemIds[0] },
|
|
||||||
UpgradeFingerprint: upgrade.UpgradeFingerprint,
|
|
||||||
PendingRerollFingerprint: upgrade.PendingRerollFingerprint
|
|
||||||
}
|
|
||||||
],
|
|
||||||
cost: kuvaCost
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const inventory = await getInventory(accountId, "Upgrades");
|
const inventory = await getInventory(accountId, "Upgrades");
|
||||||
@ -63,4 +74,10 @@ interface AwDangitRequest {
|
|||||||
CommitReroll: boolean;
|
CommitReroll: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IChange {
|
||||||
|
ItemId: IOid;
|
||||||
|
UpgradeFingerprint?: string;
|
||||||
|
PendingRerollFingerprint?: string;
|
||||||
|
}
|
||||||
|
|
||||||
const rerollCosts = [900, 1000, 1200, 1400, 1700, 2000, 2350, 2750, 3150];
|
const rerollCosts = [900, 1000, 1200, 1400, 1700, 2000, 2350, 2750, 3150];
|
||||||
|
33
src/controllers/api/retrievePetFromStasisController.ts
Normal file
33
src/controllers/api/retrievePetFromStasisController.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { Status } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const retrievePetFromStasisController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "KubrowPets");
|
||||||
|
const data = getJSONfromString<IRetrievePetFromStasisRequest>(String(req.body));
|
||||||
|
|
||||||
|
let oldPetId: string | undefined;
|
||||||
|
for (const pet of inventory.KubrowPets) {
|
||||||
|
if (pet.Details!.Status == Status.StatusAvailable) {
|
||||||
|
pet.Details!.Status = Status.StatusStasis;
|
||||||
|
oldPetId = pet._id.toString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inventory.KubrowPets.id(data.petId)!.Details!.Status = Status.StatusAvailable;
|
||||||
|
|
||||||
|
await inventory.save();
|
||||||
|
res.json({
|
||||||
|
petId: data.petId,
|
||||||
|
oldPetId,
|
||||||
|
status: Status.StatusAvailable
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IRetrievePetFromStasisRequest {
|
||||||
|
petId: string;
|
||||||
|
}
|
@ -81,5 +81,11 @@ interface SaveCompletedDialogueRequest {
|
|||||||
Booleans: string[];
|
Booleans: string[];
|
||||||
ResetBooleans: string[];
|
ResetBooleans: string[];
|
||||||
Data: ICompletedDialogue;
|
Data: ICompletedDialogue;
|
||||||
OtherDialogueInfos: string[]; // unsure
|
OtherDialogueInfos: IOtherDialogueInfo[]; // unsure
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IOtherDialogueInfo {
|
||||||
|
Dialogue: string;
|
||||||
|
Tag: string;
|
||||||
|
Value: number;
|
||||||
}
|
}
|
||||||
|
@ -13,10 +13,10 @@ const saveSettingsController: RequestHandler = async (req, res): Promise<void> =
|
|||||||
|
|
||||||
const settingResults = getJSONfromString<ISaveSettingsRequest>(String(req.body));
|
const settingResults = getJSONfromString<ISaveSettingsRequest>(String(req.body));
|
||||||
|
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId, "Settings");
|
||||||
inventory.Settings = Object.assign(inventory.Settings, settingResults.Settings);
|
inventory.Settings = Object.assign(inventory.Settings ?? {}, settingResults.Settings);
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json(inventory.Settings);
|
res.json({ Settings: inventory.Settings });
|
||||||
};
|
};
|
||||||
|
|
||||||
export { saveSettingsController };
|
export { saveSettingsController };
|
||||||
|
25
src/controllers/api/saveVaultAutoContributeController.ts
Normal file
25
src/controllers/api/saveVaultAutoContributeController.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { Guild } from "@/src/models/guildModel";
|
||||||
|
import { 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 saveVaultAutoContributeController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "GuildId");
|
||||||
|
const guild = (await Guild.findById(inventory.GuildId!, "Ranks AutoContributeFromVault"))!;
|
||||||
|
if (!(await hasGuildPermission(guild, accountId, GuildPermission.Treasurer))) {
|
||||||
|
res.status(400).send("Invalid permission").end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = getJSONfromString<ISetVaultAutoContributeRequest>(String(req.body));
|
||||||
|
guild.AutoContributeFromVault = data.autoContributeFromVault;
|
||||||
|
await guild.save();
|
||||||
|
res.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ISetVaultAutoContributeRequest {
|
||||||
|
autoContributeFromVault: boolean;
|
||||||
|
}
|
@ -1,11 +1,54 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getInventory, addMods, addRecipes, addMiscItems, addConsumables } from "@/src/services/inventoryService";
|
import {
|
||||||
|
getInventory,
|
||||||
|
addMods,
|
||||||
|
addRecipes,
|
||||||
|
addMiscItems,
|
||||||
|
addConsumables,
|
||||||
|
freeUpSlot
|
||||||
|
} from "@/src/services/inventoryService";
|
||||||
|
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
|
||||||
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);
|
||||||
const inventory = await getInventory(accountId);
|
const requiredFields = new Set();
|
||||||
|
if (payload.SellCurrency == "SC_RegularCredits") {
|
||||||
|
requiredFields.add("RegularCredits");
|
||||||
|
} else if (payload.SellCurrency == "SC_FusionPoints") {
|
||||||
|
requiredFields.add("FusionPoints");
|
||||||
|
} else {
|
||||||
|
requiredFields.add("MiscItems");
|
||||||
|
}
|
||||||
|
for (const key of Object.keys(payload.Items)) {
|
||||||
|
requiredFields.add(key);
|
||||||
|
}
|
||||||
|
if (requiredFields.has("Upgrades")) {
|
||||||
|
requiredFields.add("RawUpgrades");
|
||||||
|
}
|
||||||
|
if (payload.Items.Suits) {
|
||||||
|
requiredFields.add(InventorySlot.SUITS);
|
||||||
|
}
|
||||||
|
if (payload.Items.LongGuns || payload.Items.Pistols || payload.Items.Melee) {
|
||||||
|
requiredFields.add(InventorySlot.WEAPONS);
|
||||||
|
}
|
||||||
|
if (payload.Items.SpaceSuits) {
|
||||||
|
requiredFields.add(InventorySlot.SPACESUITS);
|
||||||
|
}
|
||||||
|
if (payload.Items.SpaceGuns || payload.Items.SpaceMelee) {
|
||||||
|
requiredFields.add(InventorySlot.SPACEWEAPONS);
|
||||||
|
}
|
||||||
|
if (payload.Items.Sentinels || payload.Items.SentinelWeapons) {
|
||||||
|
requiredFields.add(InventorySlot.SENTINELS);
|
||||||
|
}
|
||||||
|
if (payload.Items.OperatorAmps) {
|
||||||
|
requiredFields.add(InventorySlot.AMPS);
|
||||||
|
}
|
||||||
|
if (payload.Items.Hoverboards) {
|
||||||
|
requiredFields.add(InventorySlot.SPACESUITS);
|
||||||
|
}
|
||||||
|
const inventory = await getInventory(accountId, Array.from(requiredFields).join(" "));
|
||||||
|
|
||||||
// Give currency
|
// Give currency
|
||||||
if (payload.SellCurrency == "SC_RegularCredits") {
|
if (payload.SellCurrency == "SC_RegularCredits") {
|
||||||
@ -34,56 +77,72 @@ 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) {
|
if (payload.Items.SpaceSuits) {
|
||||||
payload.Items.SpaceSuits.forEach(sellItem => {
|
payload.Items.SpaceSuits.forEach(sellItem => {
|
||||||
inventory.SpaceSuits.pull({ _id: sellItem.String });
|
inventory.SpaceSuits.pull({ _id: sellItem.String });
|
||||||
|
freeUpSlot(inventory, InventorySlot.SPACESUITS);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (payload.Items.SpaceGuns) {
|
if (payload.Items.SpaceGuns) {
|
||||||
payload.Items.SpaceGuns.forEach(sellItem => {
|
payload.Items.SpaceGuns.forEach(sellItem => {
|
||||||
inventory.SpaceGuns.pull({ _id: sellItem.String });
|
inventory.SpaceGuns.pull({ _id: sellItem.String });
|
||||||
|
freeUpSlot(inventory, InventorySlot.SPACEWEAPONS);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (payload.Items.SpaceMelee) {
|
if (payload.Items.SpaceMelee) {
|
||||||
payload.Items.SpaceMelee.forEach(sellItem => {
|
payload.Items.SpaceMelee.forEach(sellItem => {
|
||||||
inventory.SpaceMelee.pull({ _id: sellItem.String });
|
inventory.SpaceMelee.pull({ _id: sellItem.String });
|
||||||
|
freeUpSlot(inventory, InventorySlot.SPACEWEAPONS);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (payload.Items.Sentinels) {
|
if (payload.Items.Sentinels) {
|
||||||
payload.Items.Sentinels.forEach(sellItem => {
|
payload.Items.Sentinels.forEach(sellItem => {
|
||||||
inventory.Sentinels.pull({ _id: sellItem.String });
|
inventory.Sentinels.pull({ _id: sellItem.String });
|
||||||
|
freeUpSlot(inventory, InventorySlot.SENTINELS);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (payload.Items.SentinelWeapons) {
|
if (payload.Items.SentinelWeapons) {
|
||||||
payload.Items.SentinelWeapons.forEach(sellItem => {
|
payload.Items.SentinelWeapons.forEach(sellItem => {
|
||||||
inventory.SentinelWeapons.pull({ _id: sellItem.String });
|
inventory.SentinelWeapons.pull({ _id: sellItem.String });
|
||||||
|
freeUpSlot(inventory, InventorySlot.SENTINELS);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (payload.Items.OperatorAmps) {
|
if (payload.Items.OperatorAmps) {
|
||||||
payload.Items.OperatorAmps.forEach(sellItem => {
|
payload.Items.OperatorAmps.forEach(sellItem => {
|
||||||
inventory.OperatorAmps.pull({ _id: sellItem.String });
|
inventory.OperatorAmps.pull({ _id: sellItem.String });
|
||||||
|
freeUpSlot(inventory, InventorySlot.AMPS);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (payload.Items.Hoverboards) {
|
if (payload.Items.Hoverboards) {
|
||||||
payload.Items.Hoverboards.forEach(sellItem => {
|
payload.Items.Hoverboards.forEach(sellItem => {
|
||||||
inventory.Hoverboards.pull({ _id: sellItem.String });
|
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) {
|
if (payload.Items.Consumables) {
|
||||||
@ -152,6 +211,7 @@ interface ISellRequest {
|
|||||||
SentinelWeapons?: ISellItem[];
|
SentinelWeapons?: ISellItem[];
|
||||||
OperatorAmps?: ISellItem[];
|
OperatorAmps?: ISellItem[];
|
||||||
Hoverboards?: ISellItem[];
|
Hoverboards?: ISellItem[];
|
||||||
|
Drones?: ISellItem[];
|
||||||
};
|
};
|
||||||
SellPrice: number;
|
SellPrice: number;
|
||||||
SellCurrency:
|
SellCurrency:
|
||||||
|
38
src/controllers/api/setAllianceGuildPermissionsController.ts
Normal file
38
src/controllers/api/setAllianceGuildPermissionsController.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { AllianceMember, GuildMember } from "@/src/models/guildModel";
|
||||||
|
import { getAccountForRequest } from "@/src/services/loginService";
|
||||||
|
import { GuildPermission } from "@/src/types/guildTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const setAllianceGuildPermissionsController: RequestHandler = async (req, res) => {
|
||||||
|
// Check requester is a warlord in their guild
|
||||||
|
const account = await getAccountForRequest(req);
|
||||||
|
const guildMember = (await GuildMember.findOne({ accountId: account._id, status: 0 }))!;
|
||||||
|
if (guildMember.rank > 1) {
|
||||||
|
res.status(400).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check guild is the creator of the alliance and don't allow changing of own permissions. (Technically changing permissions requires the Promoter permission, but both are exclusive to the creator guild.)
|
||||||
|
const allianceMember = (await AllianceMember.findOne({
|
||||||
|
guildId: guildMember.guildId,
|
||||||
|
Pending: false
|
||||||
|
}))!;
|
||||||
|
if (
|
||||||
|
!(allianceMember.Permissions & GuildPermission.Ruler) ||
|
||||||
|
allianceMember.guildId.equals(req.query.guildId as string)
|
||||||
|
) {
|
||||||
|
res.status(400).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetAllianceMember = (await AllianceMember.findOne({
|
||||||
|
allianceId: allianceMember.allianceId,
|
||||||
|
guildId: req.query.guildId
|
||||||
|
}))!;
|
||||||
|
targetAllianceMember.Permissions =
|
||||||
|
parseInt(req.query.perms as string) &
|
||||||
|
(GuildPermission.Recruiter | GuildPermission.Treasurer | GuildPermission.ChatModerator);
|
||||||
|
await targetAllianceMember.save();
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
};
|
@ -4,7 +4,7 @@ import { getDojoClient, getGuildForRequest } from "@/src/services/guildService";
|
|||||||
export const setDojoComponentMessageController: RequestHandler = async (req, res) => {
|
export const setDojoComponentMessageController: RequestHandler = async (req, res) => {
|
||||||
const guild = await getGuildForRequest(req);
|
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.
|
// At this point, we know that a member of the guild is making this request. Assuming they are allowed to change the message.
|
||||||
const component = guild.DojoComponents!.find(x => x._id.equals(req.query.componentId as string))!;
|
const component = guild.DojoComponents.id(req.query.componentId as string)!;
|
||||||
const payload = JSON.parse(String(req.body)) as SetDojoComponentMessageRequest;
|
const payload = JSON.parse(String(req.body)) as SetDojoComponentMessageRequest;
|
||||||
if ("Name" in payload) {
|
if ("Name" in payload) {
|
||||||
component.Name = payload.Name;
|
component.Name = payload.Name;
|
||||||
@ -12,7 +12,7 @@ export const setDojoComponentMessageController: RequestHandler = async (req, res
|
|||||||
component.Message = payload.Message;
|
component.Message = payload.Message;
|
||||||
}
|
}
|
||||||
await guild.save();
|
await guild.save();
|
||||||
res.json(getDojoClient(guild, 1));
|
res.json(await getDojoClient(guild, 0, component._id));
|
||||||
};
|
};
|
||||||
|
|
||||||
type SetDojoComponentMessageRequest = { Name: string } | { Message: string };
|
type SetDojoComponentMessageRequest = { Name: string } | { Message: string };
|
||||||
|
@ -1,14 +1,21 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
|
|
||||||
export const setEquippedInstrumentController: RequestHandler = async (req, res) => {
|
export const setEquippedInstrumentController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await getInventory(accountId);
|
|
||||||
const body = getJSONfromString<ISetEquippedInstrumentRequest>(String(req.body));
|
const body = getJSONfromString<ISetEquippedInstrumentRequest>(String(req.body));
|
||||||
inventory.EquippedInstrument = body.Instrument;
|
|
||||||
await inventory.save();
|
await Inventory.updateOne(
|
||||||
|
{
|
||||||
|
accountOwnerId: accountId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EquippedInstrument: body.Instrument
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
res.end();
|
res.end();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
59
src/controllers/api/setGuildMotdController.ts
Normal file
59
src/controllers/api/setGuildMotdController.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { Alliance, Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
|
import { hasGuildPermissionEx } from "@/src/services/guildService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||||
|
import { GuildPermission, ILongMOTD } from "@/src/types/guildTypes";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const setGuildMotdController: RequestHandler = async (req, res) => {
|
||||||
|
const account = await getAccountForRequest(req);
|
||||||
|
const inventory = await getInventory(account._id.toString(), "GuildId");
|
||||||
|
const guild = (await Guild.findById(inventory.GuildId!))!;
|
||||||
|
const member = (await GuildMember.findOne({ accountId: account._id, guildId: guild._id }))!;
|
||||||
|
|
||||||
|
const IsLongMOTD = "longMOTD" in req.query;
|
||||||
|
const MOTD = req.body ? String(req.body) : undefined;
|
||||||
|
|
||||||
|
if ("alliance" in req.query) {
|
||||||
|
if (member.rank > 1) {
|
||||||
|
res.status(400).json("Invalid permission");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const alliance = (await Alliance.findById(guild.AllianceId!))!;
|
||||||
|
const motd = MOTD
|
||||||
|
? ({
|
||||||
|
message: MOTD,
|
||||||
|
authorName: getSuffixedName(account),
|
||||||
|
authorGuildName: guild.Name
|
||||||
|
} satisfies ILongMOTD)
|
||||||
|
: undefined;
|
||||||
|
if (IsLongMOTD) {
|
||||||
|
alliance.LongMOTD = motd;
|
||||||
|
} else {
|
||||||
|
alliance.MOTD = motd;
|
||||||
|
}
|
||||||
|
await alliance.save();
|
||||||
|
} else {
|
||||||
|
if (!hasGuildPermissionEx(guild, member, GuildPermission.Herald)) {
|
||||||
|
res.status(400).json("Invalid permission");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsLongMOTD) {
|
||||||
|
if (MOTD) {
|
||||||
|
guild.LongMOTD = {
|
||||||
|
message: MOTD,
|
||||||
|
authorName: getSuffixedName(account)
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
guild.LongMOTD = undefined;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
guild.MOTD = MOTD ?? "";
|
||||||
|
}
|
||||||
|
await guild.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ IsLongMOTD, MOTD });
|
||||||
|
};
|
@ -1,11 +1,18 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
|
|
||||||
export const setSupportedSyndicateController: RequestHandler = async (req, res) => {
|
export const setSupportedSyndicateController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await getInventory(accountId);
|
|
||||||
inventory.SupportedSyndicate = req.query.syndicate as string;
|
await Inventory.updateOne(
|
||||||
await inventory.save();
|
{
|
||||||
|
accountOwnerId: accountId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SupportedSyndicate: req.query.syndicate as string
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
res.end();
|
res.end();
|
||||||
};
|
};
|
||||||
|
@ -1,20 +1,25 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { WeaponTypeInternal } from "@/src/services/itemDataService";
|
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
|
import { equipmentKeys, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
|
|
||||||
export const setWeaponSkillTreeController: RequestHandler = async (req, res) => {
|
export const setWeaponSkillTreeController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await getInventory(accountId);
|
|
||||||
const payload = getJSONfromString<ISetWeaponSkillTreeRequest>(String(req.body));
|
const payload = getJSONfromString<ISetWeaponSkillTreeRequest>(String(req.body));
|
||||||
|
|
||||||
const item = inventory[req.query.Category as WeaponTypeInternal].find(
|
if (equipmentKeys.indexOf(req.query.Category as TEquipmentKey) != -1) {
|
||||||
item => item._id.toString() == (req.query.ItemId as string)
|
await Inventory.updateOne(
|
||||||
)!;
|
{
|
||||||
item.SkillTree = payload.SkillTree;
|
accountOwnerId: accountId,
|
||||||
|
[`${req.query.Category as string}._id`]: req.query.ItemId as string
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[`${req.query.Category as string}.$.SkillTree`]: payload.SkillTree
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await inventory.save();
|
|
||||||
res.end();
|
res.end();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user