Compare commits

..

No commits in common. "731be0d5e339f90b134a86d81e1e764d9961f319" and "9de0aee6f0b99b0c602f6f367e75c1978b15c523" have entirely different histories.

177 changed files with 3791 additions and 9140 deletions

View File

@ -15,16 +15,17 @@
"@typescript-eslint/restrict-template-expressions": "warn",
"@typescript-eslint/restrict-plus-operands": "warn",
"@typescript-eslint/no-unsafe-member-access": "warn",
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "caughtErrors": "none" }],
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"@typescript-eslint/no-misused-promises": "warn",
"@typescript-eslint/no-unsafe-argument": "error",
"@typescript-eslint/no-unsafe-call": "warn",
"@typescript-eslint/no-unsafe-assignment": "warn",
"@typescript-eslint/no-explicit-any": "warn",
"no-loss-of-precision": "warn",
"@typescript-eslint/no-loss-of-precision": "warn",
"@typescript-eslint/no-unnecessary-condition": "warn",
"@typescript-eslint/no-base-to-string": "off",
"no-case-declarations": "error",
"prettier/prettier": "error",
"@typescript-eslint/semi": "error",
"no-mixed-spaces-and-tabs": "error",
"require-await": "off",
"@typescript-eslint/require-await": "error"

2
.gitattributes vendored
View File

@ -1,4 +1,4 @@
# Auto detect text files and perform LF normalization
* text=auto eol=lf
* text=auto
static/webui/libs/ linguist-vendored

View File

@ -5,22 +5,17 @@ on:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
version: [18, 20, 22]
steps:
- name: Checkout
uses: actions/checkout@v4.1.2
- name: Setup Node.js environment
uses: actions/setup-node@v4.0.2
with:
node-version: ${{ matrix.version }}
- run: npm ci
- run: cp config.json.example config.json
- run: npm run verify
- run: npm run lint:ci
- run: npm run prettier
- run: npm run update-translations
- name: Fail if there are uncommitted changes
run: |
if [[ -n "$(git status --porcelain)" ]]; then
echo "Uncommitted changes detected:"
git status
git --no-pager diff
exit 1
fi
- run: npm run build
- run: npm run lint

View File

@ -1,4 +1,3 @@
src/routes/api.ts
static/webui/libs/
*.html
*.md

View File

@ -1,3 +0,0 @@
{
"recommendations": ["dbaeumer.vscode-eslint"]
}

View File

@ -10,6 +10,8 @@ ENV APP_SKIP_TUTORIAL=true
ENV APP_SKIP_ALL_DIALOGUE=true
ENV APP_UNLOCK_ALL_SCANS=true
ENV APP_UNLOCK_ALL_MISSIONS=true
ENV APP_UNLOCK_ALL_QUESTS=true
ENV APP_COMPLETE_ALL_QUESTS=true
ENV APP_INFINITE_RESOURCES=true
ENV APP_UNLOCK_ALL_SHIP_FEATURES=true
ENV APP_UNLOCK_ALL_SHIP_DECORATIONS=true

View File

@ -10,8 +10,5 @@ To get an idea of what functionality you can expect to be missing [have a look t
## config.json
SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [config.json.example](config.json.example), which has most cheats disabled.
- `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 ]`.
- `worldState.lockTime` will lock the time provided in worldState if nonzero, e.g. `1743202800` for night in POE.

View File

@ -29,26 +29,19 @@
"unlockExilusEverywhere": false,
"unlockArcanesEverywhere": false,
"noDailyStandingLimits": false,
"noDailyFocusLimit": false,
"noArgonCrystalDecay": false,
"noMasteryRankUpCooldown": false,
"noVendorPurchaseLimits": true,
"noDeathMarks": false,
"noKimCooldowns": false,
"instantResourceExtractorDrones": false,
"noResourceExtractorDronesDamage": false,
"noDojoRoomBuildStage": false,
"noDecoBuildStage": false,
"fastDojoRoomDestruction": false,
"noDojoResearchCosts": false,
"noDojoResearchTime": false,
"fastClanAscension": false,
"spoofMasteryRank": -1,
"worldState": {
"events": {
"creditBoost": false,
"affinityBoost": false,
"resourceBoost": false,
"starDays": true,
"lockTime": 0
"starDays": true
}
}

602
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,12 +4,10 @@
"description": "WF Emulator",
"main": "index.ts",
"scripts": {
"start": "node --enable-source-maps --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 ",
"build": "tsc --incremental --sourceMap && ncp static/webui build/static/webui",
"verify": "tsgo --noEmit",
"build": "tsc && copyfiles static/webui/** build",
"lint": "eslint --ext .ts .",
"lint:ci": "eslint --ext .ts --rule \"prettier/prettier: off\" .",
"lint:fix": "eslint --fix --ext .ts .",
"prettier": "prettier --write .",
"update-translations": "cd scripts && node update-translations.js"
@ -18,25 +16,24 @@
"dependencies": {
"@types/express": "^5",
"@types/morgan": "^1.9.9",
"copyfiles": "^2.4.1",
"crc-32": "^1.2.2",
"express": "^5",
"json-with-bigint": "^3.2.2",
"mongoose": "^8.11.0",
"morgan": "^1.10.0",
"ncp": "^2.0.0",
"typescript": "^5.5",
"warframe-public-export-plus": "^0.5.56",
"typescript": ">=5.5 <5.6.0",
"warframe-public-export-plus": "^0.5.48",
"warframe-riven-info": "^0.1.2",
"winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0"
},
"devDependencies": {
"@rxliuli/tsgo": "^2025.3.31",
"@typescript-eslint/eslint-plugin": "^8.28.0",
"@typescript-eslint/parser": "^8.28.0",
"eslint": "^8",
"eslint-plugin-prettier": "^5.2.5",
"prettier": "^3.5.3",
"@typescript-eslint/eslint-plugin": "^7.18",
"@typescript-eslint/parser": "^7.18",
"eslint": "^8.56.0",
"eslint-plugin-prettier": "^5.2.3",
"prettier": "^3.4.2",
"ts-node-dev": "^2.0.0",
"tsconfig-paths": "^4.2.0"
},

View File

@ -4,7 +4,7 @@
const fs = require("fs");
function extractStrings(content) {
const regex = /([a-zA-Z0-9_]+): `([^`]*)`,/g;
const regex = /([a-zA-Z_]+): `([^`]*)`,/g;
let matches;
const strings = {};
while ((matches = regex.exec(content)) !== null) {
@ -15,7 +15,7 @@ function extractStrings(content) {
const source = fs.readFileSync("../static/webui/translations/en.js", "utf8");
const sourceStrings = extractStrings(source);
const sourceLines = source.substring(0, source.length - 1).split("\n");
const sourceLines = source.split("\n");
fs.readdirSync("../static/webui/translations").forEach(file => {
if (fs.lstatSync(`../static/webui/translations/${file}`).isFile() && file !== "en.js") {
@ -36,7 +36,7 @@ fs.readdirSync("../static/webui/translations").forEach(file => {
fs.writeSync(fileHandle, ` ${key}: \`[UNTRANSLATED] ${value}\`,\n`);
}
});
} else {
} else if (line.length) {
fs.writeSync(fileHandle, line + "\n");
}
});

View File

@ -16,9 +16,9 @@ import { webuiRouter } from "@/src/routes/webui";
const app = express();
app.use((req, _res, next) => {
// 38.5.0 introduced "ezip" for encrypted body blobs and "e" for request verification only (encrypted body blobs with no application data).
// 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"] == "e") {
if (req.headers["content-encoding"] == "ezip") {
req.headers["content-encoding"] = undefined;
}
next();

View File

@ -32,7 +32,7 @@ export const abortDojoComponentController: RequestHandler = async (req, res) =>
if (request.DecoId) {
removeDojoDeco(guild, request.ComponentId, request.DecoId);
} else {
await removeDojoRoom(guild, request.ComponentId);
removeDojoRoom(guild, request.ComponentId);
}
await guild.save();

View File

@ -1,30 +0,0 @@
import { toOid } from "@/src/helpers/inventoryHelpers";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Account, Ignore } from "@/src/models/loginModel";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IFriendInfo } from "@/src/types/guildTypes";
import { RequestHandler } from "express";
export const addIgnoredUserController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const data = getJSONfromString<IAddIgnoredUserRequest>(String(req.body));
const ignoreeAccount = await Account.findOne(
{ DisplayName: data.playerName.substring(0, data.playerName.length - 1) },
"_id"
);
if (ignoreeAccount) {
await Ignore.create({ ignorer: accountId, ignoree: ignoreeAccount._id });
res.json({
Ignored: {
_id: toOid(ignoreeAccount._id),
DisplayName: data.playerName
} satisfies IFriendInfo
});
} else {
res.status(400).end();
}
};
interface IAddIgnoredUserRequest {
playerName: string;
}

View File

@ -1,117 +0,0 @@
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;
}

View File

@ -3,19 +3,15 @@ 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 { getAccountForRequest, 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");
@ -35,20 +31,24 @@ export const addToGuildController: RequestHandler = async (req, res) => {
res.status(400).json("Invalid permission");
}
try {
if (
await GuildMember.exists({
accountId: account._id,
guildId: payload.GuildId.$oid
})
) {
res.status(400).json("User already invited to clan");
return;
}
await GuildMember.insertOne({
accountId: account._id,
guildId: payload.GuildId.$oid,
status: 2 // outgoing invite
});
} 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, [
await createMessage(account._id.toString(), [
{
sndr: getSuffixedName(senderAccount),
msg: "/Lotus/Language/Menu/Mailbox_ClanInvite_Body",
@ -76,30 +76,9 @@ export const addToGuildController: RequestHandler = async (req, res) => {
};
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;
UserName: string;
GuildId: IOid;
RequestMsg?: string;
}

View File

@ -57,16 +57,12 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
payload.Consumed.forEach(upgrade => {
const meta = ExportUpgrades[upgrade.ItemType];
counts[meta.rarity] += upgrade.ItemCount;
if (upgrade.ItemId.$oid != "000000000000000000000000") {
inventory.Upgrades.pull({ _id: upgrade.ItemId.$oid });
} else {
addMods(inventory, [
{
ItemType: upgrade.ItemType,
ItemCount: upgrade.ItemCount * -1
}
]);
}
if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/AttackTransmuteCore") {
forcedPolarity = "AP_ATTACK";
} else if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/DefenseTransmuteCore") {
@ -76,15 +72,6 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
}
});
let newModType: string | undefined;
for (const specialModSet of specialModSets) {
if (specialModSet.indexOf(payload.Consumed[0].ItemType) != -1) {
newModType = getRandomElement(specialModSet);
break;
}
}
if (!newModType) {
// Based on the table on https://wiki.warframe.com/w/Transmutation
const weights: Record<TRarity, number> = {
COMMON: counts.COMMON * 95 + counts.UNCOMMON * 15 + counts.RARE * 4,
@ -100,9 +87,7 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
}
});
newModType = getRandomWeightedReward(options, weights)!.uniqueName;
}
const newModType = getRandomWeightedReward(options, weights)!.uniqueName;
addMods(inventory, [
{
ItemType: newModType,
@ -145,34 +130,3 @@ interface IAgnosticUpgradeClient {
ItemCount: number;
LastAdded: IOid;
}
const specialModSets: string[][] = [
[
"/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalTwoMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalThreeMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalFourMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalFiveMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalSixMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalSevenMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalEightMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalWildcardMod"
],
[
"/Lotus/Upgrades/Mods/Immortal/AntivirusOneMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusTwoMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusThreeMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusFourMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusFiveMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusSixMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusSevenMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusEightMod"
],
[
"/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndSpeedOnUseMod",
"/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndWeaponDamageOnUseMod",
"/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusLargeOnSingleUseMod",
"/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusOnUseMod",
"/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusSmallOnSingleUseMod"
]
];

View File

@ -1,20 +0,0 @@
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();
};

View File

@ -15,12 +15,6 @@ export const changeDojoRootController: RequestHandler = async (req, res) => {
return;
}
// Example POST body: {"pivot":[0, 0, -64],"components":"{\"670429301ca0a63848ccc467\":{\"R\":[0,0,0],\"P\":[0,3,32]},\"6704254a1ca0a63848ccb33c\":{\"R\":[0,0,0],\"P\":[0,9.25,-32]},\"670429461ca0a63848ccc731\":{\"R\":[-90,0,0],\"P\":[-47.999992370605,3,16]}}"}
if (req.body) {
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
throw new Error("dojo reparent operation should not need deco repositioning"); // because we always provide SortId
}
const idToNode: Record<string, INode> = {};
guild.DojoComponents.forEach(x => {
idToNode[x._id.toString()] = {
@ -49,13 +43,23 @@ export const changeDojoRootController: RequestHandler = async (req, res) => {
newRoot.component.pp = undefined;
newRoot.parent = undefined;
// Set/update SortId in top-to-bottom order
// Don't even ask me why this is needed because I don't know either
const stack: INode[] = [newRoot];
let i = 0;
const idMap: Record<string, Types.ObjectId> = {};
while (stack.length != 0) {
const top = stack.shift()!;
top.component.SortId = new Types.ObjectId();
idMap[top.component._id.toString()] = new Types.ObjectId(
(++i).toString(16).padStart(8, "0") + top.component._id.toString().substr(8)
);
top.children.forEach(x => stack.push(x));
}
guild.DojoComponents.forEach(x => {
x._id = idMap[x._id.toString()];
if (x.pi) {
x.pi = idMap[x.pi.toString()];
}
});
logger.debug("New tree:\n" + treeToString(newRoot));

View File

@ -18,9 +18,8 @@ import {
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
import { toOid } from "@/src/helpers/inventoryHelpers";
interface IClaimCompletedRecipeRequest {
export interface IClaimCompletedRecipeRequest {
RecipeIds: IOid[];
}
@ -81,7 +80,6 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
} else {
logger.debug("Claiming Recipe", { recipe, pendingRecipe });
let BrandedSuits: undefined | IOid[];
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
inventory.PendingSpectreLoadouts ??= [];
inventory.SpectreLoadouts ??= [];
@ -101,15 +99,9 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
inventory.SpectreLoadouts.push(inventory.PendingSpectreLoadouts[pendingLoadoutIndex]);
inventory.PendingSpectreLoadouts.splice(pendingLoadoutIndex, 1);
}
} else if (recipe.secretIngredientAction == "SIA_UNBRAND") {
inventory.BrandedSuits!.splice(
inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)),
1
);
BrandedSuits = [toOid(pendingRecipe.SuitToUnbrand!)];
}
let InventoryChanges: IInventoryChanges = {};
let InventoryChanges = {};
if (recipe.consumeOnUse) {
addRecipes(inventory, [
{
@ -119,24 +111,16 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
]);
}
if (req.query.rush) {
const end = Math.trunc(pendingRecipe.CompletionDate.getTime() / 1000);
const start = end - recipe.buildTime;
const secondsElapsed = Math.trunc(Date.now() / 1000) - start;
const progress = secondsElapsed / recipe.buildTime;
logger.debug(`rushing recipe at ${Math.trunc(progress * 100)}% completion`);
const cost = Math.round(recipe.skipBuildTimePrice * (1 - (progress - 0.5)));
InventoryChanges = {
...InventoryChanges,
...updateCurrency(inventory, cost, true)
...updateCurrency(inventory, recipe.skipBuildTimePrice, true)
};
}
if (recipe.secretIngredientAction != "SIA_UNBRAND") {
InventoryChanges = {
...InventoryChanges,
...(await addItem(inventory, recipe.resultType, recipe.num, false))
};
}
await inventory.save();
res.json({ InventoryChanges, BrandedSuits });
res.json({ InventoryChanges });
}
};

View File

@ -7,8 +7,6 @@ export const clearDialogueHistoryController: RequestHandler = async (req, res) =
const inventory = await getInventory(accountId);
const request = JSON.parse(String(req.body)) as IClearDialogueRequest;
if (inventory.DialogueHistory && inventory.DialogueHistory.Dialogues) {
inventory.DialogueHistory.Resets ??= 0;
inventory.DialogueHistory.Resets += 1;
for (const dialogueName of request.Dialogues) {
const index = inventory.DialogueHistory.Dialogues.findIndex(x => x.DialogueName == dialogueName);
if (index != -1) {

View File

@ -1,37 +0,0 @@
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
});
};

View File

@ -1,59 +1,26 @@
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 { getGuildClient, updateInventoryForConfirmedGuildJoin } from "@/src/services/guildService";
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
import { RequestHandler } from "express";
import { Types } from "mongoose";
// GET request: A player accepting an invite they got in their inbox.
export const confirmGuildInvitationGetController: RequestHandler = async (req, res) => {
export const confirmGuildInvitationController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
const invitedGuildMember = await GuildMember.findOne({
const guildMember = await GuildMember.findOne({
accountId: account._id,
guildId: req.query.clanId as string
});
if (invitedGuildMember && invitedGuildMember.status == 2) {
let inventoryChanges: IInventoryChanges = {};
// If 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();
guildMember.status = 0;
await guildMember.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();
await updateInventoryForConfirmedGuildJoin(
account._id.toString(),
new Types.ObjectId(req.query.clanId as string)
);
const guild = (await Guild.findById(req.query.clanId as string))!;
// Add join to clan log
guild.RosterActivity ??= [];
guild.RosterActivity.push({
dateTime: new Date(),
@ -64,61 +31,16 @@ export const confirmGuildInvitationGetController: RequestHandler = async (req, r
res.json({
...(await getGuildClient(guild, account._id.toString())),
InventoryChanges: inventoryChanges
InventoryChanges: {
Recipes: [
{
ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
ItemCount: 1
}
]
}
});
} else {
res.end();
}
};
// POST request: Clan representative accepting invite(s).
export const confirmGuildInvitationPostController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const guild = (await Guild.findById(req.query.clanId as string, "Ranks RosterActivity"))!;
if (!(await hasGuildPermission(guild, accountId, GuildPermission.Recruiter))) {
res.status(400).json("Invalid permission");
return;
}
const payload = getJSONfromString<{ userId: string }>(String(req.body));
const filter: { accountId?: string; status: number } = { status: 1 };
if (payload.userId != "all") {
filter.accountId = payload.userId;
}
const guildMembers = await GuildMember.find(filter);
const newMembers: string[] = [];
for (const guildMember of guildMembers) {
guildMember.status = 0;
guildMember.RequestMsg = undefined;
guildMember.RequestExpiry = undefined;
await guildMember.save();
// Remove other pending applications for this account
await GuildMember.deleteMany({ accountId: guildMember.accountId, status: 1 });
// Update inventory of new member
const inventory = await getInventory(guildMember.accountId.toString(), "GuildId 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
});
};

View File

@ -1,7 +1,8 @@
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Guild } from "@/src/models/guildModel";
import { checkClanAscensionHasRequiredContributors } from "@/src/services/guildService";
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";
@ -30,7 +31,43 @@ export const contributeGuildClassController: RequestHandler = async (req, res) =
guild.CeremonyContributors.push(new Types.ObjectId(accountId));
await checkClanAscensionHasRequiredContributors(guild);
// Once required contributor count is hit, the class is committed and there's 72 hours to claim endo.
if (guild.CeremonyContributors.length == payload.RequiredContributors) {
guild.Class = guild.CeremonyClass!;
guild.CeremonyClass = undefined;
guild.CeremonyResetDate = new Date(Date.now() + (config.fastClanAscension ? 5_000 : 72 * 3600_000));
if (!config.fastClanAscension) {
// Send message to all active guild members
const members = await GuildMember.find({ guildId: payload.GuildId, status: 0 }, "accountId");
for (const member of members) {
// somewhat unfaithful as on live the "msg" is not a loctag, but since we don't have the string, we'll let the client fill it in with "arg".
await createMessage(member.accountId.toString(), [
{
sndr: guild.Name,
msg: "/Lotus/Language/Clan/Clan_AscensionCeremonyInProgressDetails",
arg: [
{
Key: "RESETDATE",
Tag:
guild.CeremonyResetDate.getUTCMonth() +
"/" +
guild.CeremonyResetDate.getUTCDate() +
"/" +
(guild.CeremonyResetDate.getUTCFullYear() % 100) +
" " +
guild.CeremonyResetDate.getUTCHours().toString().padStart(2, "0") +
":" +
guild.CeremonyResetDate.getUTCMinutes().toString().padStart(2, "0")
}
],
sub: "/Lotus/Language/Clan/Clan_AscensionCeremonyInProgress",
icon: "/Lotus/Interface/Graphics/ClanTileImages/ClanEnterDojo.png",
highPriority: true
}
]);
}
}
}
await guild.save();

View File

@ -1,7 +1,6 @@
import { GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import {
addGuildMemberMiscItemContribution,
getDojoClient,
getGuildForRequestEx,
hasAccessToDojo,
@ -64,7 +63,9 @@ export const contributeToDojoComponentController: RequestHandler = async (req, r
}
}
await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
await guild.save();
await inventory.save();
await guildMember.save();
res.json({
...(await getDojoClient(guild, 0, component._id)),
InventoryChanges: inventoryChanges
@ -93,10 +94,10 @@ const processContribution = (
component.RegularCredits += request.VaultCredits;
guild.VaultRegularCredits! -= request.VaultCredits;
}
if (component.RegularCredits > scaleRequiredCount(guild.Tier, meta.price)) {
if (component.RegularCredits > scaleRequiredCount(meta.price)) {
guild.VaultRegularCredits ??= 0;
guild.VaultRegularCredits += component.RegularCredits - scaleRequiredCount(guild.Tier, meta.price);
component.RegularCredits = scaleRequiredCount(guild.Tier, meta.price);
guild.VaultRegularCredits += component.RegularCredits - scaleRequiredCount(meta.price);
component.RegularCredits = scaleRequiredCount(meta.price);
}
component.MiscItems ??= [];
@ -107,10 +108,10 @@ const processContribution = (
const ingredientMeta = meta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
if (
componentMiscItem.ItemCount + ingredientContribution.ItemCount >
scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount)
scaleRequiredCount(ingredientMeta.ItemCount)
) {
ingredientContribution.ItemCount =
scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
scaleRequiredCount(ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
}
componentMiscItem.ItemCount += ingredientContribution.ItemCount;
} else {
@ -128,10 +129,10 @@ const processContribution = (
const ingredientMeta = meta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
if (
componentMiscItem.ItemCount + ingredientContribution.ItemCount >
scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount)
scaleRequiredCount(ingredientMeta.ItemCount)
) {
ingredientContribution.ItemCount =
scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
scaleRequiredCount(ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
}
componentMiscItem.ItemCount += ingredientContribution.ItemCount;
} else {
@ -142,20 +143,18 @@ const processContribution = (
ItemCount: ingredientContribution.ItemCount * -1
});
addGuildMemberMiscItemContribution(guildMember, ingredientContribution);
guildMember.MiscItemsContributed ??= [];
guildMember.MiscItemsContributed.push(ingredientContribution);
}
addMiscItems(inventory, miscItemChanges);
inventoryChanges.MiscItems = miscItemChanges;
}
if (component.RegularCredits >= scaleRequiredCount(guild.Tier, meta.price)) {
if (component.RegularCredits >= scaleRequiredCount(meta.price)) {
let fullyFunded = true;
for (const ingredient of meta.ingredients) {
const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredient.ItemType);
if (
!componentMiscItem ||
componentMiscItem.ItemCount < scaleRequiredCount(guild.Tier, ingredient.ItemCount)
) {
if (!componentMiscItem || componentMiscItem.ItemCount < scaleRequiredCount(ingredient.ItemCount)) {
fullyFunded = false;
break;
}

View File

@ -1,104 +1,56 @@
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 { GuildMember } from "@/src/models/guildModel";
import { getGuildForRequestEx } from "@/src/services/guildService";
import { addFusionTreasures, addMiscItems, addShipDecorations, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IFusionTreasure, IMiscItem, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
import { RequestHandler } from "express";
export const contributeToVaultController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "GuildId RegularCredits MiscItems ShipDecorations FusionTreasures");
const request = JSON.parse(String(req.body)) as IContributeToVaultRequest;
if (request.Alliance) {
const inventory = await getInventory(accountId);
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(
const guildMember = (await GuildMember.findOne(
{ accountId, guildId: guild._id },
"RegularCreditsContributed MiscItemsContributed ShipDecorationsContributed"
))!;
}
const request = JSON.parse(String(req.body)) as IContributeToVaultRequest;
if (request.RegularCredits) {
updateCurrency(inventory, request.RegularCredits, false);
guild.VaultRegularCredits ??= 0;
guild.VaultRegularCredits += request.RegularCredits;
if (guildMember) {
guildMember.RegularCreditsContributed ??= 0;
guildMember.RegularCreditsContributed += request.RegularCredits;
}
}
if (request.MiscItems.length) {
addVaultMiscItems(guild, request.MiscItems);
guild.VaultMiscItems ??= [];
guildMember.MiscItemsContributed ??= [];
for (const item of request.MiscItems) {
if (guildMember) {
addGuildMemberMiscItemContribution(guildMember, item);
}
guild.VaultMiscItems.push(item);
guildMember.MiscItemsContributed.push(item);
addMiscItems(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
}
}
if (request.ShipDecorations.length) {
addVaultShipDecos(guild, request.ShipDecorations);
guild.VaultShipDecorations ??= [];
guildMember.ShipDecorationsContributed ??= [];
for (const item of request.ShipDecorations) {
if (guildMember) {
addGuildMemberShipDecoContribution(guildMember, item);
}
guild.VaultShipDecorations.push(item);
guildMember.ShipDecorationsContributed.push(item);
addShipDecorations(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
}
}
if (request.FusionTreasures.length) {
addVaultFusionTreasures(guild, request.FusionTreasures);
guild.VaultFusionTreasures ??= [];
for (const item of request.FusionTreasures) {
guild.VaultFusionTreasures.push(item);
addFusionTreasures(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
}
}
const promises: Promise<unknown>[] = [guild.save(), inventory.save()];
if (guildMember) {
promises.push(guildMember.save());
}
await Promise.all(promises);
await guild.save();
await inventory.save();
await guildMember.save();
res.end();
};
@ -107,7 +59,4 @@ interface IContributeToVaultRequest {
MiscItems: IMiscItem[];
ShipDecorations: ITypeCount[];
FusionTreasures: IFusionTreasure[];
Alliance?: boolean;
FromVault?: boolean;
GuildVault?: string;
}

View File

@ -1,50 +0,0 @@
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;
}

View File

@ -2,16 +2,16 @@ import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Guild, GuildMember } from "@/src/models/guildModel";
import { createUniqueClanName, getGuildClient } from "@/src/services/guildService";
import { addRecipes, getInventory } from "@/src/services/inventoryService";
import {
createUniqueClanName,
getGuildClient,
updateInventoryForConfirmedGuildJoin
} from "@/src/services/guildService";
export const createGuildController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const payload = getJSONfromString<ICreateGuildRequest>(String(req.body));
// Remove pending applications for this account
await GuildMember.deleteMany({ accountId, status: 1 });
// Create guild on database
const guild = new Guild({
Name: await createUniqueClanName(payload.guildName)
@ -26,15 +26,7 @@ export const createGuildController: RequestHandler = async (req, res) => {
rank: 0
});
const inventory = await getInventory(accountId, "GuildId Recipes");
inventory.GuildId = guild._id;
addRecipes(inventory, [
{
ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
ItemCount: 1
}
]);
await inventory.save();
await updateInventoryForConfirmedGuildJoin(accountId, guild._id);
res.json({
...(await getGuildClient(guild, accountId)),

View File

@ -1,28 +0,0 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { ICrewMemberClient } from "@/src/types/inventoryTypes/inventoryTypes";
import { RequestHandler } from "express";
import { Types } from "mongoose";
export const crewMembersController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "CrewMembers");
const data = getJSONfromString<ICrewMembersRequest>(String(req.body));
const dbCrewMember = inventory.CrewMembers.id(data.crewMember.ItemId.$oid)!;
dbCrewMember.AssignedRole = data.crewMember.AssignedRole;
dbCrewMember.SkillEfficiency = data.crewMember.SkillEfficiency;
dbCrewMember.WeaponConfigIdx = data.crewMember.WeaponConfigIdx;
dbCrewMember.WeaponId = new Types.ObjectId(data.crewMember.WeaponId.$oid);
dbCrewMember.Configs = data.crewMember.Configs;
dbCrewMember.SecondInCommand = data.crewMember.SecondInCommand;
await inventory.save();
res.json({
crewMemberId: data.crewMember.ItemId.$oid,
NemesisFingerprint: data.crewMember.NemesisFingerprint
});
};
interface ICrewMembersRequest {
crewMember: ICrewMemberClient;
}

View File

@ -1,91 +0,0 @@
import {
addCrewShipSalvagedWeaponSkin,
addCrewShipRawSalvage,
getInventory,
addEquipment
} from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
import { ICrewShipComponentFingerprint, IInnateDamageFingerprint } from "@/src/types/inventoryTypes/inventoryTypes";
import { ExportCustoms, ExportRailjackWeapons, ExportUpgrades } from "warframe-public-export-plus";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { getRandomInt } from "@/src/services/rngService";
import { IFingerprintStat } from "@/src/helpers/rivenHelper";
import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
export const crewShipIdentifySalvageController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(
accountId,
"CrewShipSalvagedWeaponSkins CrewShipSalvagedWeapons CrewShipRawSalvage"
);
const payload = getJSONfromString<ICrewShipIdentifySalvageRequest>(String(req.body));
const inventoryChanges: IInventoryChanges = {};
if (payload.ItemType in ExportCustoms) {
const meta = ExportCustoms[payload.ItemType];
let upgradeFingerprint: ICrewShipComponentFingerprint = { compat: payload.ItemType, buffs: [] };
if (meta.subroutines) {
upgradeFingerprint = {
SubroutineIndex: getRandomInt(0, meta.subroutines.length - 1),
...upgradeFingerprint
};
}
for (const upgrade of meta.randomisedUpgrades!) {
upgradeFingerprint.buffs.push({ Tag: upgrade.tag, Value: Math.trunc(Math.random() * 0x40000000) });
}
addCrewShipSalvagedWeaponSkin(
inventory,
payload.ItemType,
JSON.stringify(upgradeFingerprint),
inventoryChanges
);
} else {
const meta = ExportRailjackWeapons[payload.ItemType];
let defaultOverwrites: Partial<IEquipmentDatabase> | undefined;
if (meta.defaultUpgrades?.[0]) {
const upgradeType = meta.defaultUpgrades[0].ItemType;
const upgradeMeta = ExportUpgrades[upgradeType];
const buffs: IFingerprintStat[] = [];
for (const buff of upgradeMeta.upgradeEntries!) {
buffs.push({
Tag: buff.tag,
Value: Math.trunc(Math.random() * 0x40000000)
});
}
defaultOverwrites = {
UpgradeType: upgradeType,
UpgradeFingerprint: JSON.stringify({
compat: payload.ItemType,
buffs
} satisfies IInnateDamageFingerprint)
};
}
addEquipment(
inventory,
"CrewShipSalvagedWeapons",
payload.ItemType,
undefined,
inventoryChanges,
defaultOverwrites
);
}
inventoryChanges.CrewShipRawSalvage = [
{
ItemType: payload.ItemType,
ItemCount: -1
}
];
addCrewShipRawSalvage(inventory, inventoryChanges.CrewShipRawSalvage);
await inventory.save();
res.json({
InventoryChanges: inventoryChanges
});
};
interface ICrewShipIdentifySalvageRequest {
ItemType: string;
}

View File

@ -1,17 +0,0 @@
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();
};

View File

@ -1,67 +0,0 @@
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();
};

View File

@ -1,4 +1,4 @@
import { GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
import { GuildMember } 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";
@ -36,10 +36,10 @@ export const dojoComponentRushController: RequestHandler = async (req, res) => {
if (request.DecoId) {
const deco = component.Decos!.find(x => x._id.equals(request.DecoId))!;
const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == deco.Type)!;
processContribution(guild, deco, meta, platinumDonated);
processContribution(deco, meta, platinumDonated);
} else {
const meta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!;
processContribution(guild, component, meta, platinumDonated);
processContribution(component, meta, platinumDonated);
const entry = guild.RoomChanges?.find(x => x.componentId.equals(component._id));
if (entry) {
@ -47,11 +47,13 @@ export const dojoComponentRushController: RequestHandler = async (req, res) => {
}
}
await guild.save();
await inventory.save();
const guildMember = (await GuildMember.findOne({ accountId, guildId: guild._id }, "PremiumCreditsContributed"))!;
guildMember.PremiumCreditsContributed ??= 0;
guildMember.PremiumCreditsContributed += request.Amount;
await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
await guildMember.save();
res.json({
...(await getDojoClient(guild, 0, component._id)),
@ -59,13 +61,8 @@ export const dojoComponentRushController: RequestHandler = async (req, res) => {
});
};
const processContribution = (
guild: TGuildDatabaseDocument,
component: IDojoContributable,
meta: IDojoBuild,
platinumDonated: number
): void => {
const fullPlatinumCost = scaleRequiredCount(guild.Tier, meta.skipTimePrice);
const processContribution = (component: IDojoContributable, meta: IDojoBuild, platinumDonated: number): void => {
const fullPlatinumCost = scaleRequiredCount(meta.skipTimePrice);
const fullDurationSeconds = meta.time;
const secondsPerPlatinum = fullDurationSeconds / fullPlatinumCost;
component.CompletionTime = new Date(

View File

@ -1,11 +1,5 @@
import { RequestHandler } from "express";
// Arbiter Dojo endpoints, not really used by us as we don't provide a ContentURL.
export const dojoController: RequestHandler = (_req, res) => {
res.json("-1"); // Tell client to use authorised request.
};
export const setDojoURLController: RequestHandler = (_req, res) => {
res.end();
};

View File

@ -55,7 +55,7 @@ export const dronesController: RequestHandler = async (req, res) => {
? new Date()
: new Date(Date.now() + getRandomInt(3 * 3600 * 1000, 4 * 3600 * 1000));
drone.PendingDamage =
!config.noResourceExtractorDronesDamage && Math.random() < system.damageChance
Math.random() < system.damageChance
? getRandomInt(system.droneDamage.minValue, system.droneDamage.maxValue)
: 0;
const resource = getRandomWeightedRewardUc(system.resources, droneMeta.probabilities)!;

View File

@ -17,7 +17,7 @@ export const evolveWeaponController: RequestHandler = async (req, res) => {
recipe.ingredients.map(x => ({ ItemType: x.ItemType, ItemCount: x.ItemCount * -1 }))
);
const item = inventory[payload.Category].id(req.query.ItemId as string)!;
const item = inventory[payload.Category].find(item => item._id.toString() == (req.query.ItemId as string))!;
item.Features ??= 0;
item.Features |= EquipmentFeatures.INCARNON_GENESIS;
@ -39,7 +39,7 @@ export const evolveWeaponController: RequestHandler = async (req, res) => {
}
]);
const item = inventory[payload.Category].id(req.query.ItemId as string)!;
const item = inventory[payload.Category].find(item => item._id.toString() == (req.query.ItemId as string))!;
item.Features! &= ~EquipmentFeatures.INCARNON_GENESIS;
} else {
throw new Error(`unexpected evolve weapon action: ${payload.Action}`);

View File

@ -1,9 +1,10 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { addMiscItems, addStanding, getInventory } from "@/src/services/inventoryService";
import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper";
import { addMiscItems, getInventory, getStandingLimit, updateStandingLimit } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import { RequestHandler } from "express";
import { ExportResources } from "warframe-public-export-plus";
import { ExportResources, ExportSyndicates } from "warframe-public-export-plus";
export const fishmongerController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
@ -30,15 +31,32 @@ export const fishmongerController: RequestHandler = async (req, res) => {
miscItemChanges.push({ ItemType: fish.ItemType, ItemCount: fish.ItemCount * -1 });
}
addMiscItems(inventory, miscItemChanges);
let affiliationMod;
if (gainedStanding && syndicateTag) affiliationMod = addStanding(inventory, syndicateTag, gainedStanding);
if (gainedStanding && syndicateTag) {
let syndicate = inventory.Affiliations.find(x => x.Tag == syndicateTag);
if (!syndicate) {
syndicate = inventory.Affiliations[inventory.Affiliations.push({ Tag: syndicateTag, Standing: 0 }) - 1];
}
const syndicateMeta = ExportSyndicates[syndicateTag];
const max = getMaxStanding(syndicateMeta, syndicate.Title ?? 0);
if (syndicate.Standing + gainedStanding > max) {
gainedStanding = max - syndicate.Standing;
}
if (gainedStanding > getStandingLimit(inventory, syndicateMeta.dailyLimitBin)) {
gainedStanding = getStandingLimit(inventory, syndicateMeta.dailyLimitBin);
}
syndicate.Standing += gainedStanding;
updateStandingLimit(inventory, syndicateMeta.dailyLimitBin, gainedStanding);
}
await inventory.save();
res.json({
InventoryChanges: {
MiscItems: miscItemChanges
},
SyndicateTag: syndicateTag,
StandingChange: affiliationMod?.Standing || 0
StandingChange: gainedStanding
});
};

View File

@ -18,8 +18,8 @@ export const focusController: RequestHandler = async (req, res) => {
case FocusOperation.InstallLens: {
const request = JSON.parse(String(req.body)) as ILensInstallRequest;
const inventory = await getInventory(accountId);
const item = inventory[request.Category].id(request.WeaponId);
if (item) {
for (const item of inventory[request.Category]) {
if (item._id.toString() == request.WeaponId) {
item.FocusLens = request.LensType;
addMiscItems(inventory, [
{
@ -27,6 +27,8 @@ export const focusController: RequestHandler = async (req, res) => {
ItemCount: -1
} satisfies IMiscItem
]);
break;
}
}
await inventory.save();
res.json({

View File

@ -1,25 +1,7 @@
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";
export const getAllianceController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "GuildId");
if (inventory.GuildId) {
const guild = (await Guild.findById(inventory.GuildId, "Name Tier AllianceId"))!;
if (guild.AllianceId) {
const alliance = (await Alliance.findById(guild.AllianceId))!;
res.json(await getAllianceClient(alliance, guild));
return;
}
}
res.end();
const getAllianceController: RequestHandler = (_req, res) => {
res.sendStatus(200);
};
/*interface IGetAllianceRequest {
memberCount: number;
clanLeaderName: string;
clanLeaderId: string;
}*/
export { getAllianceController };

View File

@ -1,7 +1,6 @@
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) => {
@ -9,11 +8,11 @@ export const getGuildContributionsController: RequestHandler = async (req, res)
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 },
_id: { $oid: req.query.buddyId },
RegularCreditsContributed: guildMember.RegularCreditsContributed,
PremiumCreditsContributed: guildMember.PremiumCreditsContributed,
MiscItemsContributed: guildMember.MiscItemsContributed,
ConsumablesContributed: [], // ???
ShipDecorationsContributed: guildMember.ShipDecorationsContributed
} satisfies Partial<IGuildMemberClient>);
});
};

View File

@ -5,7 +5,7 @@ import { logger } from "@/src/utils/logger";
import { getInventory } from "@/src/services/inventoryService";
import { createUniqueClanName, getGuildClient } from "@/src/services/guildService";
export const getGuildController: RequestHandler = async (req, res) => {
const getGuildController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "GuildId");
if (inventory.GuildId) {
@ -28,5 +28,7 @@ export const getGuildController: RequestHandler = async (req, res) => {
return;
}
}
res.end();
res.sendStatus(200);
};
export { getGuildController };

View File

@ -1,20 +1,16 @@
import { toOid } from "@/src/helpers/inventoryHelpers";
import { Account, Ignore } from "@/src/models/loginModel";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IFriendInfo } from "@/src/types/guildTypes";
import { parallelForeach } from "@/src/utils/async-utils";
import { RequestHandler } from "express";
export const getIgnoredUsersController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const ignores = await Ignore.find({ ignorer: accountId });
const ignoredUsers: IFriendInfo[] = [];
await parallelForeach(ignores, async ignore => {
const ignoreeAccount = (await Account.findById(ignore.ignoree, "DisplayName"))!;
ignoredUsers.push({
_id: toOid(ignore.ignoree),
DisplayName: ignoreeAccount.DisplayName + ""
const getIgnoredUsersController: RequestHandler = (_req, res) => {
res.writeHead(200, {
"Content-Type": "text/html",
"Content-Length": "3"
});
});
res.json({ IgnoredUsers: ignoredUsers });
res.end(
Buffer.from([
0x7b, 0x22, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x22, 0x3a, 0x38, 0x33, 0x30, 0x34, 0x30, 0x37, 0x37, 0x32, 0x32,
0x34, 0x30, 0x32, 0x32, 0x32, 0x36, 0x31, 0x35, 0x30, 0x31, 0x7d
])
);
};
export { getIgnoredUsersController };

View File

@ -1,12 +1,13 @@
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";
export const getNewRewardSeedController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const rewardSeed = generateRewardSeed();
logger.debug(`generated new reward seed: ${rewardSeed}`);
await Inventory.updateOne(
{
accountOwnerId: accountId
@ -17,3 +18,9 @@ export const getNewRewardSeedController: RequestHandler = async (req, res) => {
);
res.json({ rewardSeed: rewardSeed });
};
export function generateRewardSeed(): number {
const min = -Number.MAX_SAFE_INTEGER;
const max = Number.MAX_SAFE_INTEGER;
return Math.floor(Math.random() * (max - min + 1)) + min;
}

View File

@ -26,10 +26,7 @@ export const getShipController: RequestHandler = async (req, res) => {
Colors: personalRooms.ShipInteriorColors,
ShipAttachments: ship.ShipAttachments,
SkinFlavourItem: ship.SkinFlavourItem
},
FavouriteLoadoutId: personalRooms.Ship.FavouriteLoadoutId
? toOid(personalRooms.Ship.FavouriteLoadoutId)
: undefined
}
},
Apartment: personalRooms.Apartment,
TailorShop: personalRooms.TailorShop

View File

@ -1,5 +1,5 @@
import { RequestHandler } from "express";
import { getVendorManifestByTypeName } from "@/src/services/serversideVendorsService";
import { getVendorManifestByTypeName, preprocessVendorManifest } from "@/src/services/serversideVendorsService";
export const getVendorInfoController: RequestHandler = (req, res) => {
if (typeof req.query.vendor == "string") {
@ -7,7 +7,7 @@ export const getVendorInfoController: RequestHandler = (req, res) => {
if (!manifest) {
throw new Error(`Unknown vendor: ${req.query.vendor}`);
}
res.json(manifest);
res.json(preprocessVendorManifest(manifest));
} else {
res.status(400).end();
}

View File

@ -56,7 +56,7 @@ export const giftingController: RequestHandler = async (req, res) => {
await senderInventory.save();
const senderName = getSuffixedName(senderAccount);
await createMessage(account._id, [
await createMessage(account._id.toString(), [
{
sndr: senderName,
msg: data.Message || "/Lotus/Language/Menu/GiftReceivedBody_NoCustomMessage",

View File

@ -2,25 +2,36 @@ import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import { WeaponTypeInternal } from "@/src/services/itemDataService";
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")[] = [
"LongGuns",
"Pistols",
"Melee",
"OperatorAmps",
"Hoverboards"
];
interface IGildWeaponRequest {
ItemName: string;
Recipe: string; // e.g. /Lotus/Weapons/SolarisUnited/LotusGildKitgunBlueprint
PolarizeSlot?: number;
PolarizeValue?: ArtifactPolarity;
ItemId: string;
Category: TEquipmentKey;
Category: WeaponTypeInternal | "Hoverboards";
}
export const gildWeaponController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const data = getJSONfromString<IGildWeaponRequest>(String(req.body));
data.ItemId = String(req.query.ItemId);
data.Category = req.query.Category as TEquipmentKey;
if (!modularWeaponCategory.includes(req.query.Category as WeaponTypeInternal | "Hoverboards")) {
throw new Error(`Unknown modular weapon Category: ${String(req.query.Category)}`);
}
data.Category = req.query.Category as WeaponTypeInternal | "Hoverboards";
const inventory = await getInventory(accountId);
const weaponIndex = inventory[data.Category].findIndex(x => String(x._id) === data.ItemId);
@ -31,10 +42,8 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
const weapon = inventory[data.Category][weaponIndex];
weapon.Features ??= 0;
weapon.Features |= EquipmentFeatures.GILDED;
if (data.Recipe != "webui") {
weapon.ItemName = data.ItemName;
weapon.XP = 0;
}
if (data.Category != "OperatorAmps" && data.PolarizeSlot && data.PolarizeValue) {
weapon.Polarity = [
{
@ -47,9 +56,6 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
const inventoryChanges: IInventoryChanges = {};
inventoryChanges[data.Category] = [weapon.toJSON<IEquipmentClient>()];
const affiliationMods = [];
if (data.Recipe != "webui") {
const recipe = ExportRecipes[data.Recipe];
inventoryChanges.MiscItems = recipe.secretIngredients!.map(ingredient => ({
ItemType: ingredient.ItemType,
@ -57,6 +63,7 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
}));
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;
@ -65,7 +72,6 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
Standing: recipe.syndicateStandingChange.value
});
}
}
await inventory.save();
res.json({

View File

@ -2,8 +2,8 @@ import { RequestHandler } from "express";
import { parseString } from "@/src/helpers/general";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getInventory } from "@/src/services/inventoryService";
import { IGroup } from "@/src/types/loginTypes";
import { giveKeyChainItem } from "@/src/services/questService";
import { IKeyChainRequest } from "@/src/types/requestTypes";
export const giveKeyChainTriggeredItemsController: RequestHandler = async (req, res) => {
const accountId = parseString(req.query.accountId);
@ -15,3 +15,9 @@ export const giveKeyChainTriggeredItemsController: RequestHandler = async (req,
res.send(inventoryChanges);
};
export interface IKeyChainRequest {
KeyChain: string;
ChainStage: number;
Groups?: IGroup[];
}

View File

@ -1,7 +1,7 @@
import { IKeyChainRequest } from "@/src/controllers/api/giveKeyChainTriggeredItemsController";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { giveKeyChainMessage } from "@/src/services/questService";
import { IKeyChainRequest } from "@/src/types/requestTypes";
import { RequestHandler } from "express";
export const giveKeyChainTriggeredMessageController: RequestHandler = async (req, res) => {

View File

@ -16,15 +16,15 @@ export const giveQuestKeyRewardController: RequestHandler = async (req, res) =>
const inventory = await getInventory(accountId);
const inventoryChanges = await addItem(inventory, reward.ItemType, reward.Amount);
await inventory.save();
res.json(inventoryChanges);
res.json(inventoryChanges.InventoryChanges);
//TODO: consider whishlist changes
};
interface IQuestKeyRewardRequest {
export interface IQuestKeyRewardRequest {
reward: IQuestKeyReward;
}
interface IQuestKeyReward {
export interface IQuestKeyReward {
RewardType: string;
CouponType: string;
Icon: string;

View File

@ -1,20 +0,0 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { addLoreFragmentScans, addShipDecorations, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { ILoreFragmentScan, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
import { RequestHandler } from "express";
export const giveShipDecoAndLoreFragmentController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "LoreFragmentScans ShipDecorations");
const data = getJSONfromString<IGiveShipDecoAndLoreFragmentRequest>(String(req.body));
addLoreFragmentScans(inventory, data.LoreFragmentScans);
addShipDecorations(inventory, data.ShipDecorations);
await inventory.save();
res.end();
};
interface IGiveShipDecoAndLoreFragmentRequest {
LoreFragmentScans: ILoreFragmentScan[];
ShipDecorations: ITypeCount[];
}

View File

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

View File

@ -1,46 +1,38 @@
import { RequestHandler } from "express";
import {
addGuildMemberMiscItemContribution,
getGuildForRequestEx,
getGuildVault,
hasAccessToDojo,
hasGuildPermission,
processFundedGuildTechProject,
processGuildTechProjectContributionsUpdate,
removePigmentsFromGuildMembers,
scaleRequiredCount,
setGuildTechLogState
scaleRequiredCount
} from "@/src/services/guildService";
import { ExportDojoRecipes } from "warframe-public-export-plus";
import { ExportDojoRecipes, IDojoResearch } from "warframe-public-export-plus";
import { getAccountIdForRequest } from "@/src/services/loginService";
import {
addCrewShipWeaponSkin,
addEquipment,
addItem,
addMiscItems,
addRecipes,
combineInventoryChanges,
getInventory,
occupySlot,
updateCurrency
} from "@/src/services/inventoryService";
import { IMiscItem, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
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, toOid } from "@/src/helpers/inventoryHelpers";
import { GuildPermission, ITechProjectClient, ITechProjectDatabase } from "@/src/types/guildTypes";
import { GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
import { logger } from "@/src/utils/logger";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
export const guildTechController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const guild = await getGuildForRequestEx(req, inventory);
const data = JSON.parse(String(req.body)) as TGuildTechRequest;
if (data.Action == "Sync") {
let needSave = false;
const techProjects: ITechProjectClient[] = [];
const guild = await getGuildForRequestEx(req, inventory);
if (guild.TechProjects) {
for (const project of guild.TechProjects) {
const techProject: ITechProjectClient = {
@ -52,7 +44,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
if (project.CompletionDate) {
techProject.CompletionDate = toMongoDate(project.CompletionDate);
if (Date.now() >= project.CompletionDate.getTime()) {
needSave ||= setGuildTechLogState(guild, project.ItemType, 4, project.CompletionDate);
needSave ||= setTechLogState(guild, project.ItemType, 4, project.CompletionDate);
}
}
techProjects.push(techProject);
@ -63,8 +55,6 @@ export const guildTechController: RequestHandler = async (req, res) => {
}
res.json({ TechProjects: techProjects });
} else if (data.Action == "Start") {
if (data.Mode == "Guild") {
const guild = await getGuildForRequestEx(req, inventory);
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
res.status(400).send("-1").end();
return;
@ -76,17 +66,17 @@ export const guildTechController: RequestHandler = async (req, res) => {
guild.TechProjects[
guild.TechProjects.push({
ItemType: data.RecipeType,
ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, recipe.price),
ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(recipe.price),
ReqItems: recipe.ingredients.map(x => ({
ItemType: x.ItemType,
ItemCount: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, x.ItemCount)
ItemCount: config.noDojoResearchCosts ? 0 : scaleRequiredCount(x.ItemCount)
})),
State: 0
}) - 1
];
setGuildTechLogState(guild, techProject.ItemType, 5);
setTechLogState(guild, techProject.ItemType, 5);
if (config.noDojoResearchCosts) {
processFundedGuildTechProject(guild, techProject, recipe);
processFundedProject(guild, techProject, recipe);
} else {
if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
guild.ActiveDojoColorResearch = data.RecipeType;
@ -95,109 +85,38 @@ export const guildTechController: RequestHandler = async (req, res) => {
}
await guild.save();
res.end();
} else {
const recipe = ExportDojoRecipes.research[data.RecipeType];
if (data.TechProductCategory) {
if (
data.TechProductCategory != "CrewShipWeapons" &&
data.TechProductCategory != "CrewShipWeaponSkins"
) {
throw new Error(`unexpected TechProductCategory: ${data.TechProductCategory}`);
}
if (!inventory[getSalvageCategory(data.TechProductCategory)].id(data.CategoryItemId)) {
throw new Error(
`no item with id ${data.CategoryItemId} in ${getSalvageCategory(data.TechProductCategory)} array`
);
}
}
const techProject =
inventory.PersonalTechProjects[
inventory.PersonalTechProjects.push({
State: 0,
ReqCredits: recipe.price,
ItemType: data.RecipeType,
ProductCategory: data.TechProductCategory,
CategoryItemId: data.CategoryItemId,
ReqItems: recipe.ingredients
}) - 1
];
await inventory.save();
res.json({
isPersonal: true,
action: "Start",
personalTech: techProject.toJSON()
});
}
} else if (data.Action == "Contribute") {
if ((req.query.guildId as string) == "000000000000000000000000") {
const techProject = inventory.PersonalTechProjects.id(data.ResearchId)!;
techProject.ReqCredits -= data.RegularCredits;
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, data.RegularCredits, false);
const miscItemChanges = [];
for (const miscItem of data.MiscItems) {
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
if (reqItem) {
if (miscItem.ItemCount > reqItem.ItemCount) {
miscItem.ItemCount = reqItem.ItemCount;
}
reqItem.ItemCount -= miscItem.ItemCount;
miscItemChanges.push({
ItemType: miscItem.ItemType,
ItemCount: miscItem.ItemCount * -1
});
}
}
addMiscItems(inventory, miscItemChanges);
inventoryChanges.MiscItems = miscItemChanges;
techProject.HasContributions = true;
if (techProject.ReqCredits == 0 && !techProject.ReqItems.find(x => x.ItemCount > 0)) {
techProject.State = 1;
const recipe = ExportDojoRecipes.research[techProject.ItemType];
techProject.CompletionDate = new Date(Date.now() + recipe.time * 1000);
}
await inventory.save();
res.json({
InventoryChanges: inventoryChanges,
PersonalResearch: { $oid: data.ResearchId },
PersonalResearchDate: techProject.CompletionDate ? toMongoDate(techProject.CompletionDate) : undefined
});
} else {
if (!hasAccessToDojo(inventory)) {
res.status(400).send("-1").end();
return;
}
const guild = await getGuildForRequestEx(req, inventory);
const guildMember = (await GuildMember.findOne(
{ accountId, guildId: guild._id },
"RegularCreditsContributed MiscItemsContributed"
))!;
const techProject = guild.TechProjects!.find(x => x.ItemType == data.RecipeType)!;
const contributions = data;
const techProject = guild.TechProjects!.find(x => x.ItemType == contributions.RecipeType)!;
if (data.VaultCredits) {
if (data.VaultCredits > techProject.ReqCredits) {
data.VaultCredits = techProject.ReqCredits;
if (contributions.VaultCredits) {
if (contributions.VaultCredits > techProject.ReqCredits) {
contributions.VaultCredits = techProject.ReqCredits;
}
techProject.ReqCredits -= data.VaultCredits;
guild.VaultRegularCredits! -= data.VaultCredits;
techProject.ReqCredits -= contributions.VaultCredits;
guild.VaultRegularCredits! -= contributions.VaultCredits;
}
if (data.RegularCredits > techProject.ReqCredits) {
data.RegularCredits = techProject.ReqCredits;
if (contributions.RegularCredits > techProject.ReqCredits) {
contributions.RegularCredits = techProject.ReqCredits;
}
techProject.ReqCredits -= data.RegularCredits;
techProject.ReqCredits -= contributions.RegularCredits;
guildMember.RegularCreditsContributed ??= 0;
guildMember.RegularCreditsContributed += data.RegularCredits;
guildMember.RegularCreditsContributed += contributions.RegularCredits;
if (data.VaultMiscItems.length) {
for (const miscItem of data.VaultMiscItems) {
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) {
@ -212,7 +131,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
}
const miscItemChanges = [];
for (const miscItem of data.MiscItems) {
for (const miscItem of contributions.MiscItems) {
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
if (reqItem) {
if (miscItem.ItemCount > reqItem.ItemCount) {
@ -224,33 +143,37 @@ export const guildTechController: RequestHandler = async (req, res) => {
ItemCount: miscItem.ItemCount * -1
});
addGuildMemberMiscItemContribution(guildMember, miscItem);
guildMember.MiscItemsContributed ??= [];
guildMember.MiscItemsContributed.push(miscItem);
}
}
addMiscItems(inventory, miscItemChanges);
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, data.RegularCredits, false);
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, contributions.RegularCredits, false);
inventoryChanges.MiscItems = miscItemChanges;
// Check if research is fully funded now.
await processGuildTechProjectContributionsUpdate(guild, techProject);
if (techProject.ReqCredits == 0 && !techProject.ReqItems.find(x => x.ItemCount > 0)) {
// This research is now fully funded.
const recipe = ExportDojoRecipes.research[data.RecipeType];
processFundedProject(guild, techProject, recipe);
if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
guild.ActiveDojoColorResearch = "";
await removePigmentsFromGuildMembers(guild._id);
}
}
await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
await guild.save();
await inventory.save();
await guildMember.save();
res.json({
InventoryChanges: inventoryChanges,
Vault: getGuildVault(guild)
});
}
} else if (data.Action.split(",")[0] == "Buy") {
const purchase = data as IGuildTechBuyRequest;
if (purchase.Mode == "Guild") {
const guild = await getGuildForRequestEx(req, inventory);
if (
!hasAccessToDojo(inventory) ||
!(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))
) {
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
res.status(400).send("-1").end();
return;
}
const purchase = data as IGuildTechBuyRequest;
const quantity = parseInt(data.Action.split(",")[1]);
const recipeChanges = [
{
@ -272,15 +195,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
Recipes: recipeChanges
}
});
} else {
const inventoryChanges = claimSalvagedComponent(inventory, purchase.CategoryItemId!);
await inventory.save();
res.json({
inventoryChanges: inventoryChanges
});
}
} else if (data.Action == "Fabricate") {
const guild = await getGuildForRequestEx(req, inventory);
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
res.status(400).send("-1").end();
return;
@ -297,7 +212,6 @@ export const guildTechController: RequestHandler = async (req, res) => {
// Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`.
res.json({ inventoryChanges: inventoryChanges });
} else if (data.Action == "Pause") {
const guild = await getGuildForRequestEx(req, inventory);
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
res.status(400).send("-1").end();
return;
@ -309,7 +223,6 @@ export const guildTechController: RequestHandler = async (req, res) => {
await removePigmentsFromGuildMembers(guild._id);
res.end();
} else if (data.Action == "Unpause") {
const guild = await getGuildForRequestEx(req, inventory);
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
res.status(400).send("-1").end();
return;
@ -319,146 +232,72 @@ export const guildTechController: RequestHandler = async (req, res) => {
guild.ActiveDojoColorResearch = data.RecipeType;
await guild.save();
res.end();
} else if (data.Action == "Cancel" && data.CategoryItemId) {
const personalTechProjectIndex = inventory.PersonalTechProjects.findIndex(x =>
x.CategoryItemId?.equals(data.CategoryItemId)
);
const personalTechProject = inventory.PersonalTechProjects[personalTechProjectIndex];
inventory.PersonalTechProjects.splice(personalTechProjectIndex, 1);
const meta = ExportDojoRecipes.research[personalTechProject.ItemType];
const contributedCredits = meta.price - personalTechProject.ReqCredits;
const inventoryChanges = updateCurrency(inventory, contributedCredits * -1, false);
inventoryChanges.MiscItems = [];
for (const ingredient of meta.ingredients) {
const reqItem = personalTechProject.ReqItems.find(x => x.ItemType == ingredient.ItemType);
if (reqItem) {
const contributedItems = ingredient.ItemCount - reqItem.ItemCount;
inventoryChanges.MiscItems.push({
ItemType: ingredient.ItemType,
ItemCount: contributedItems
});
}
}
addMiscItems(inventory, inventoryChanges.MiscItems);
await inventory.save();
res.json({
action: "Cancel",
isPersonal: true,
inventoryChanges: inventoryChanges,
personalTech: {
ItemId: toOid(personalTechProject._id)
}
});
} else if (data.Action == "Rush" && data.CategoryItemId) {
const inventoryChanges: IInventoryChanges = {
...updateCurrency(inventory, 20, true),
...claimSalvagedComponent(inventory, data.CategoryItemId)
};
await inventory.save();
res.json({
inventoryChanges: inventoryChanges
});
} else if (data.Action == "InstantFinish") {
if (data.TechProductCategory != "CrewShipWeapons" && data.TechProductCategory != "CrewShipWeaponSkins") {
throw new Error(`unexpected TechProductCategory: ${data.TechProductCategory}`);
}
const inventoryChanges = finishComponentRepair(inventory, data.TechProductCategory, data.CategoryItemId!);
inventoryChanges.MiscItems = [
{
ItemType: "/Lotus/Types/Items/MiscItems/InstantSalvageRepairItem",
ItemCount: -1
}
];
addMiscItems(inventory, inventoryChanges.MiscItems);
await inventory.save();
res.json({
inventoryChanges: inventoryChanges
});
} else {
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
throw new Error(`unhandled guildTech request`);
throw new Error(`unknown guildTech action: ${data.Action}`);
}
};
const processFundedProject = (
guild: TGuildDatabaseDocument,
techProject: ITechProjectDatabase,
recipe: IDojoResearch
): void => {
techProject.State = 1;
techProject.CompletionDate = new Date(Date.now() + (config.noDojoResearchTime ? 0 : recipe.time) * 1000);
if (recipe.guildXpValue) {
guild.XP += recipe.guildXpValue;
}
setTechLogState(guild, techProject.ItemType, config.noDojoResearchTime ? 4 : 3, techProject.CompletionDate);
};
const setTechLogState = (
guild: TGuildDatabaseDocument,
type: string,
state: number,
dateTime: Date | undefined = undefined
): boolean => {
guild.TechChanges ??= [];
const entry = guild.TechChanges.find(x => x.details == type);
if (entry) {
if (entry.entryType == state) {
return false;
}
entry.dateTime = dateTime;
entry.entryType = state;
} else {
guild.TechChanges.push({
dateTime: dateTime,
entryType: state,
details: type
});
}
return true;
};
type TGuildTechRequest =
| { Action: "Sync" | "SomethingElseThatWeMightNotKnowAbout" }
| IGuildTechBasicRequest
| IGuildTechContributeRequest;
interface IGuildTechBasicRequest {
Action: "Start" | "Fabricate" | "Pause" | "Unpause" | "Cancel" | "Rush" | "InstantFinish";
Mode: "Guild" | "Personal";
Action: "Start" | "Fabricate" | "Pause" | "Unpause";
Mode: "Guild";
RecipeType: string;
TechProductCategory?: string;
CategoryItemId?: string;
}
interface IGuildTechBuyRequest extends Omit<IGuildTechBasicRequest, "Action"> {
interface IGuildTechBuyRequest {
Action: string;
Mode: "Guild";
RecipeType: string;
}
interface IGuildTechContributeRequest {
Action: "Contribute";
ResearchId: string;
ResearchId: "";
RecipeType: string;
RegularCredits: number;
MiscItems: IMiscItem[];
VaultCredits: number;
VaultMiscItems: IMiscItem[];
}
const getSalvageCategory = (
category: "CrewShipWeapons" | "CrewShipWeaponSkins"
): "CrewShipSalvagedWeapons" | "CrewShipSalvagedWeaponSkins" => {
return category == "CrewShipWeapons" ? "CrewShipSalvagedWeapons" : "CrewShipSalvagedWeaponSkins";
};
const claimSalvagedComponent = (inventory: TInventoryDatabaseDocument, itemId: string): IInventoryChanges => {
// delete personal tech project
const personalTechProjectIndex = inventory.PersonalTechProjects.findIndex(x => x.CategoryItemId?.equals(itemId));
const personalTechProject = inventory.PersonalTechProjects[personalTechProjectIndex];
inventory.PersonalTechProjects.splice(personalTechProjectIndex, 1);
const category = personalTechProject.ProductCategory! as "CrewShipWeapons" | "CrewShipWeaponSkins";
return finishComponentRepair(inventory, category, itemId);
};
const finishComponentRepair = (
inventory: TInventoryDatabaseDocument,
category: "CrewShipWeapons" | "CrewShipWeaponSkins",
itemId: string
): IInventoryChanges => {
const salvageCategory = getSalvageCategory(category);
// find salved part & delete it
const salvageIndex = inventory[salvageCategory].findIndex(x => x._id.equals(itemId));
const salvageItem = inventory[salvageCategory][salvageIndex];
inventory[salvageCategory].splice(salvageIndex, 1);
// add final item
const inventoryChanges = {
...(category == "CrewShipWeaponSkins"
? addCrewShipWeaponSkin(inventory, salvageItem.ItemType, salvageItem.UpgradeFingerprint)
: addEquipment(
inventory,
category,
salvageItem.ItemType,
undefined,
{},
{
UpgradeFingerprint: salvageItem.UpgradeFingerprint
}
)),
...occupySlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS, false)
};
inventoryChanges.RemovedIdItems = [
{
ItemId: { $oid: itemId }
}
];
return inventoryChanges;
};

View File

@ -34,8 +34,8 @@ export const inboxController: RequestHandler = async (req, res) => {
message.r = true;
await message.save();
const attachmentItems = message.attVisualOnly ? undefined : message.att;
const attachmentCountedItems = message.attVisualOnly ? undefined : message.countedAtt;
const attachmentItems = message.att;
const attachmentCountedItems = message.countedAtt;
if (!attachmentItems && !attachmentCountedItems && !message.gifts) {
res.status(200).end();
@ -67,7 +67,7 @@ export const inboxController: RequestHandler = async (req, res) => {
(await handleStoreItemAcquisition(gift.GiftType, inventory, giftQuantity)).InventoryChanges
);
if (sender) {
await createMessage(sender._id, [
await createMessage(sender._id.toString(), [
{
sndr: recipientName,
msg: "/Lotus/Language/Menu/GiftReceivedConfirmationBody",

View File

@ -6,21 +6,20 @@ import { IOid } from "@/src/types/commonTypes";
import {
IConsumedSuit,
IHelminthFoodRecord,
IInfestedFoundryClient,
IInfestedFoundryDatabase,
IInventoryClient,
IMiscItem,
InventorySlot
InventorySlot,
ITypeCount
} from "@/src/types/inventoryTypes/inventoryTypes";
import { ExportMisc } from "warframe-public-export-plus";
import { ExportMisc, ExportRecipes } from "warframe-public-export-plus";
import { getRecipe } from "@/src/services/itemDataService";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
import { logger } from "@/src/utils/logger";
import { colorToShard } from "@/src/helpers/shardHelper";
import { config } from "@/src/services/configService";
import {
addInfestedFoundryXP,
applyCheatsToInfestedFoundry,
handleSubsumeCompletion
} from "@/src/services/infestedFoundryService";
export const infestedFoundryController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
@ -29,7 +28,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
// shard installation
const request = getJSONfromString<IShardInstallRequest>(String(req.body));
const inventory = await getInventory(accountId);
const suit = inventory.Suits.id(request.SuitId.$oid)!;
const suit = inventory.Suits.find(suit => suit._id.toString() == request.SuitId.$oid)!;
if (!suit.ArchonCrystalUpgrades || suit.ArchonCrystalUpgrades.length != 5) {
suit.ArchonCrystalUpgrades = [{}, {}, {}, {}, {}];
}
@ -57,7 +56,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
// shard removal
const request = getJSONfromString<IShardUninstallRequest>(String(req.body));
const inventory = await getInventory(accountId);
const suit = inventory.Suits.id(request.SuitId.$oid)!;
const suit = inventory.Suits.find(suit => suit._id.toString() == request.SuitId.$oid)!;
const miscItemChanges: IMiscItem[] = [];
if (suit.ArchonCrystalUpgrades![request.Slot].Color) {
@ -384,11 +383,116 @@ 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 {
SuitId: IOid;
Recipe: string;
}
export const handleSubsumeCompletion = (inventory: TInventoryDatabaseDocument): ITypeCount[] => {
const [recipeType] = Object.entries(ExportRecipes).find(
([_recipeType, recipe]) =>
recipe.secretIngredientAction == "SIA_WARFRAME_ABILITY" &&
recipe.secretIngredients![0].ItemType == inventory.InfestedFoundry!.LastConsumedSuit!.ItemType
)!;
inventory.InfestedFoundry!.LastConsumedSuit = undefined;
inventory.InfestedFoundry!.AbilityOverrideUnlockCooldown = undefined;
const recipeChanges: ITypeCount[] = [
{
ItemType: recipeType,
ItemCount: 1
}
];
addRecipes(inventory, recipeChanges);
return recipeChanges;
};
export const applyCheatsToInfestedFoundry = (infestedFoundry: IInfestedFoundryClient): void => {
if (config.infiniteHelminthMaterials) {
infestedFoundry.Resources = [
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthCalx", Count: 1000 },
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthBiotics", Count: 1000 },
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthSynthetics", Count: 1000 },
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthPheromones", Count: 1000 },
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthBile", Count: 1000 },
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthOxides", Count: 1000 }
];
}
};
interface IHelminthOfferingsUpdate {
OfferingsIndex: number;
SuitTypes: string[];

View File

@ -13,10 +13,9 @@ import {
ExportResources,
ExportVirtuals
} from "warframe-public-export-plus";
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "@/src/services/infestedFoundryService";
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "./infestedFoundryController";
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) => {
const accountId = await getAccountIdForRequest(request);
@ -35,7 +34,6 @@ export const inventoryController: RequestHandler = async (request, response) =>
}
inventory.DailyFocus = 250000 + inventory.PlayerLevel * 5000;
inventory.GiftsRemaining = Math.max(8, inventory.PlayerLevel);
inventory.TradesRemaining = inventory.PlayerLevel;
inventory.LibraryAvailableDailyTaskInfo = createLibraryDailyTask();
@ -53,11 +51,9 @@ export const inventoryController: RequestHandler = async (request, response) =>
if (numArgonCrystals == 0) {
break;
}
const numStableArgonCrystals = Math.min(
numArgonCrystals,
const numStableArgonCrystals =
inventory.FoundToday?.find(x => x.ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal")
?.ItemCount ?? 0
);
?.ItemCount ?? 0;
const numDecayingArgonCrystals = numArgonCrystals - numStableArgonCrystals;
const numDecayingArgonCrystalsToRemove = Math.ceil(numDecayingArgonCrystals / 2);
logger.debug(`ticking argon crystals for day ${i + 1} of ${daysPassed}`, {
@ -149,7 +145,7 @@ export const getInventoryResponse = async (
inventoryResponse.ShipDecorations = [];
for (const [uniqueName, item] of Object.entries(ExportResources)) {
if (item.productCategory == "ShipDecorations") {
inventoryResponse.ShipDecorations.push({ ItemType: uniqueName, ItemCount: 999_999 });
inventoryResponse.ShipDecorations.push({ ItemType: uniqueName, ItemCount: 1 });
}
}
}
@ -202,8 +198,7 @@ export const getInventoryResponse = async (
if (config.universalPolarityEverywhere) {
const Polarity: IPolarity[] = [];
// 12 is needed for necramechs. 15 is needed for plexus/crewshipharness.
for (let i = 0; i != 15; ++i) {
for (let i = 0; i != 12; ++i) {
Polarity.push({
Slot: i,
Value: ArtifactPolarity.Any
@ -258,10 +253,6 @@ export const getInventoryResponse = async (
}
}
if (config.noDailyFocusLimit) {
inventoryResponse.DailyFocus = Math.max(999_999, 250000 + inventoryResponse.PlayerLevel * 5000);
}
if (inventoryResponse.InfestedFoundry) {
applyCheatsToInfestedFoundry(inventoryResponse.InfestedFoundry);
}
@ -275,7 +266,7 @@ export const getInventoryResponse = async (
return inventoryResponse;
};
const addString = (arr: string[], str: string): void => {
export const addString = (arr: string[], str: string): void => {
if (!arr.find(x => x == str)) {
arr.push(str);
}
@ -305,3 +296,13 @@ const resourceGetParent = (resourceName: string): string | undefined => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
return ExportVirtuals[resourceName]?.parentName;
};
// This is FNV1a-32 except operating under modulus 2^31 because JavaScript is stinky and likes producing negative integers out of nowhere.
export const catBreadHash = (name: string): number => {
let hash = 2166136261;
for (let i = 0; i != name.length; ++i) {
hash = (hash ^ name.charCodeAt(i)) & 0x7fffffff;
hash = (hash * 16777619) & 0x7fffffff;
}
return hash;
};

View File

@ -54,12 +54,8 @@ export const loginController: RequestHandler = async (request, response) => {
}
}
if (!account) {
response.status(400).json({ error: "unknown user" });
return;
}
if (!isCorrectPassword(loginRequest.password, account.password)) {
//email not found or incorrect password
if (!account || !isCorrectPassword(loginRequest.password, account.password)) {
response.status(400).json({ error: "incorrect login data" });
return;
}

View File

@ -47,21 +47,16 @@ import { logger } from "@/src/utils/logger";
- [ ] FpsSamples
*/
//move credit calc in here, return MissionRewards: [] if no reward info
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const missionInventoryUpdateController: RequestHandler = async (req, res): Promise<void> => {
const accountId = await getAccountIdForRequest(req);
const missionReport = getJSONfromString<IMissionInventoryUpdateRequest>((req.body as string).toString());
logger.debug("mission report:", missionReport);
const inventory = await getInventory(accountId);
const firstCompletion = missionReport.SortieId
? inventory.CompletedSorties.indexOf(missionReport.SortieId) == -1
: false;
const inventoryUpdates = await addMissionInventoryUpdates(inventory, missionReport);
if (
missionReport.MissionStatus !== "GS_SUCCESS" &&
!(missionReport.RewardInfo?.jobId || missionReport.RewardInfo?.challengeMissionId)
) {
if (missionReport.MissionStatus !== "GS_SUCCESS") {
await inventory.save();
const inventoryResponse = await getInventoryResponse(inventory, true);
res.json({
@ -71,8 +66,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
return;
}
const { MissionRewards, inventoryChanges, credits, AffiliationMods, SyndicateXPItemReward } =
await addMissionRewards(inventory, missionReport, firstCompletion);
const { MissionRewards, inventoryChanges, credits } = await addMissionRewards(inventory, missionReport);
await inventory.save();
const inventoryResponse = await getInventoryResponse(inventory, true);
@ -84,9 +78,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
MissionRewards,
...credits,
...inventoryUpdates,
//FusionPoints: inventoryChanges?.FusionPoints, // This in combination with InventoryJson or InventoryChanges seems to just double the number of endo shown, so unsure when this is needed.
SyndicateXPItemReward,
AffiliationMods
FusionPoints: inventoryChanges?.FusionPoints
});
};

View File

@ -17,13 +17,12 @@ import { getDefaultUpgrades } from "@/src/services/itemDataService";
import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { getRandomInt } from "@/src/services/rngService";
import { ExportSentinels, IDefaultUpgrade } from "warframe-public-export-plus";
import { ExportSentinels } from "warframe-public-export-plus";
import { Status } from "@/src/types/inventoryTypes/inventoryTypes";
interface IModularCraftRequest {
WeaponType: string;
Parts: string[];
isWebUi?: boolean;
}
export const modularWeaponCraftingController: RequestHandler = async (req, res) => {
@ -35,8 +34,10 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
const category = modularWeaponTypes[data.WeaponType];
const inventory = await getInventory(accountId);
let defaultUpgrades: IDefaultUpgrade[] | undefined;
const defaultOverwrites: Partial<IEquipmentDatabase> = {};
const defaultUpgrades = getDefaultUpgrades(data.Parts);
const defaultOverwrites: Partial<IEquipmentDatabase> = {
Configs: applyDefaultUpgrades(inventory, defaultUpgrades)
};
const inventoryChanges: IInventoryChanges = {};
if (category == "KubrowPets") {
const traits = {
@ -128,37 +129,25 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
// Only save mutagen & antigen in the ModularParts.
defaultOverwrites.ModularParts = [data.Parts[1], data.Parts[2]];
const meta = ExportSentinels[data.WeaponType];
for (const specialItem of meta.exalted!) {
for (const specialItem of ExportSentinels[data.WeaponType].exalted!) {
addSpecialItem(inventory, specialItem, inventoryChanges);
}
defaultUpgrades = meta.defaultUpgrades;
} else {
defaultUpgrades = getDefaultUpgrades(data.Parts);
}
defaultOverwrites.Configs = applyDefaultUpgrades(inventory, defaultUpgrades);
addEquipment(inventory, category, data.WeaponType, data.Parts, inventoryChanges, defaultOverwrites);
combineInventoryChanges(
inventoryChanges,
occupySlot(inventory, productCategoryToInventoryBin(category)!, !!data.isWebUi)
);
combineInventoryChanges(inventoryChanges, occupySlot(inventory, productCategoryToInventoryBin(category)!, false));
if (defaultUpgrades) {
inventoryChanges.RawUpgrades = defaultUpgrades.map(x => ({ ItemType: x.ItemType, ItemCount: 1 }));
}
// Remove credits & parts
const miscItemChanges = [];
let currencyChanges = {};
if (!data.isWebUi) {
for (const part of data.Parts) {
miscItemChanges.push({
ItemType: part,
ItemCount: -1
});
}
currencyChanges = updateCurrency(
const currencyChanges = updateCurrency(
inventory,
category == "Hoverboards" ||
category == "MoaPets" ||
@ -170,9 +159,8 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
false
);
addMiscItems(inventory, miscItemChanges);
}
await inventory.save();
// Tell client what we did
res.json({
InventoryChanges: {

View File

@ -22,6 +22,7 @@ export const modularWeaponSaleController: RequestHandler = async (req, res) => {
const partTypeToParts: Record<string, string[]> = {};
for (const [uniqueName, data] of Object.entries(ExportWeapons)) {
if (data.partType && data.premiumPrice) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
partTypeToParts[data.partType] ??= [];
partTypeToParts[data.partType].push(uniqueName);
}

View File

@ -12,17 +12,15 @@ export const nameWeaponController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const body = getJSONfromString<INameWeaponRequest>(String(req.body));
const item = inventory[req.query.Category as string as TEquipmentKey].id(req.query.ItemId as string)!;
const item = inventory[req.query.Category as string as TEquipmentKey].find(
item => item._id.toString() == (req.query.ItemId as string)
)!;
if (body.ItemName != "") {
item.ItemName = body.ItemName;
} else {
item.ItemName = undefined;
}
const currencyChanges = updateCurrency(
inventory,
req.query.Category == "Horses" || "webui" in req.query ? 0 : 15,
true
);
const currencyChanges = updateCurrency(inventory, "webui" in req.query ? 0 : 15, true);
await inventory.save();
res.json({
InventoryChanges: currencyChanges

View File

@ -1,25 +1,10 @@
import {
consumeModCharge,
encodeNemesisGuess,
getInfNodes,
getNemesisPasscode,
IKnifeResponse
} from "@/src/helpers/nemesisHelpers";
import { getInfNodes, getNemesisPasscode } from "@/src/helpers/nemesisHelpers";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
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 { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import {
IInnateDamageFingerprint,
InventorySlot,
IUpgradeClient,
IWeaponSkinClient,
LoadoutIndex,
TEquipmentKey
} from "@/src/types/inventoryTypes/inventoryTypes";
import { IInnateDamageFingerprint, InventorySlot, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express";
@ -33,7 +18,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
const destFingerprint = JSON.parse(destWeapon.UpgradeFingerprint!) as IInnateDamageFingerprint;
const sourceFingerprint = JSON.parse(sourceWeapon.UpgradeFingerprint!) as IInnateDamageFingerprint;
// Update destination damage type if desired
// Upgrade destination damage type if desireed
if (body.UseSourceDmgType) {
destFingerprint.buffs[0].Tag = sourceFingerprint.buffs[0].Tag;
}
@ -42,7 +27,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
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) {
if (newDamage >= 0.58) {
newDamage = 0.6;
}
destFingerprint.buffs[0].Value = Math.trunc(((newDamage - 0.25) / (0.6 - 0.25)) * 0x3fffffff);
@ -57,14 +42,13 @@ export const nemesisController: RequestHandler = async (req, res) => {
await inventory.save();
res.json({
InventoryChanges: {
[body.Category]: [destWeapon.toJSON()],
RemovedIdItems: [{ ItemId: body.SourceWeapon }]
[body.Category]: [destWeapon.toJSON()]
}
});
} else if ((req.query.mode as string) == "p") {
const inventory = await getInventory(accountId, "Nemesis");
const body = getJSONfromString<INemesisPrespawnCheckRequest>(String(req.body));
const passcode = getNemesisPasscode(inventory.Nemesis!);
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) {
@ -81,88 +65,6 @@ export const nemesisController: RequestHandler = async (req, res) => {
}
}
res.json({ GuessResult: guessResult });
} else if (req.query.mode == "r") {
const inventory = await getInventory(
accountId,
"Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades"
);
const body = getJSONfromString<INemesisRequiemRequest>(String(req.body));
if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
const guess: number[] = [body.guess & 0xf, (body.guess >> 4) & 0xf, (body.guess >> 8) & 0xf];
const passcode = getNemesisPasscode(inventory.Nemesis!)[0];
// Add to GuessHistory
const result1 = passcode == guess[0] ? 0 : 1;
const result2 = passcode == guess[1] ? 0 : 1;
const result3 = passcode == guess[2] ? 0 : 1;
inventory.Nemesis!.GuessHistory.push(
encodeNemesisGuess(guess[0], result1, guess[1], result2, guess[2], result3)
);
// Increase antivirus
let antivirusGain = 5;
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!;
const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid);
const dataknifeConfigIndex = dataknifeLoadout?.s?.mod ?? 0;
const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex].Upgrades!;
const response: IKnifeResponse = {};
for (const upgrade of body.knife!.AttachedUpgrades) {
switch (upgrade.ItemType) {
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndSpeedOnUseMod":
antivirusGain += 10;
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
break;
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndWeaponDamageOnUseMod":
antivirusGain += 10;
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
break;
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusLargeOnSingleUseMod": // Instant Secure
antivirusGain += 15;
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
break;
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusOnUseMod": // Immuno Shield
antivirusGain += 15;
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
break;
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusSmallOnSingleUseMod":
antivirusGain += 10;
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
break;
}
}
inventory.Nemesis!.HenchmenKilled += antivirusGain;
if (inventory.Nemesis!.HenchmenKilled >= 100) {
inventory.Nemesis!.HenchmenKilled = 100;
inventory.Nemesis!.InfNodes = [
{
Node: "CrewBattleNode559",
Influence: 1
}
];
inventory.Nemesis!.Weakened = true;
} else {
inventory.Nemesis!.InfNodes = getInfNodes("FC_INFESTATION", 0);
}
await inventory.save();
res.json(response);
} else {
const passcode = getNemesisPasscode(inventory.Nemesis!);
if (passcode[body.position] != body.guess) {
res.end();
} else {
inventory.Nemesis!.Rank += 1;
inventory.Nemesis!.InfNodes = getInfNodes(inventory.Nemesis!.Faction, inventory.Nemesis!.Rank);
await inventory.save();
res.json({ RankIncrease: 1 });
}
}
} else if ((req.query.mode as string) == "rs") {
// report spawn; POST but no application data in body
const inventory = await getInventory(accountId, "Nemesis");
inventory.Nemesis!.LastEnc = inventory.Nemesis!.MissionCount;
await inventory.save();
res.json({ LastEnc: inventory.Nemesis!.LastEnc });
} else if ((req.query.mode as string) == "s") {
const inventory = await getInventory(accountId, "Nemesis");
const body = getJSONfromString<INemesisStartRequest>(String(req.body));
@ -270,20 +172,6 @@ interface INemesisPrespawnCheckRequest {
potency?: number[];
}
interface INemesisRequiemRequest {
guess: number; // grn/crp: 4 bits | coda: 3x 4 bits
position: number; // grn/crp: 0-2 | coda: 0
// knife field provided for coda only
knife?: {
Item: IEquipmentClient;
Skins: IWeaponSkinClient[];
ModSlot: number;
CustSlot: number;
AttachedUpgrades: IUpgradeClient[];
HiddenWhenHolstered: boolean;
};
}
const kuvaLichVersionSixWeapons = [
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Drakgoon/KuvaDrakgoon",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Karak/KuvaKarak",

View File

@ -1,19 +1,10 @@
import {
getDojoClient,
getGuildForRequestEx,
getVaultMiscItemCount,
hasAccessToDojo,
hasGuildPermission,
processDojoBuildMaterialsGathered,
scaleRequiredCount
} from "@/src/services/guildService";
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes";
import { RequestHandler } from "express";
import { Types } from "mongoose";
import { ExportDojoRecipes, ExportResources } from "warframe-public-export-plus";
import { config } from "@/src/services/configService";
import { ExportDojoRecipes } from "warframe-public-export-plus";
export const placeDecoInComponentController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
@ -33,11 +24,6 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
}
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({
@ -45,61 +31,17 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
Type: request.Type,
Pos: request.Pos,
Rot: request.Rot,
Name: request.Name,
Sockets: request.Sockets
Name: request.Name
}) - 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 (deco.Type != "/Lotus/Objects/Tenno/Props/TnoPaintBotDojoDeco") {
if (!meta || (meta.price == 0 && meta.ingredients.length == 0) || config.noDojoDecoBuildStage) {
if (meta.price == 0 && meta.ingredients.length == 0) {
deco.CompletionTime = new Date();
if (meta) {
processDojoBuildMaterialsGathered(guild, meta);
}
} 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);
}
}
}
}
}
@ -114,9 +56,4 @@ interface IPlaceDecoInComponentRequest {
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;
}

View File

@ -1,9 +0,0 @@
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
export const playedParkourTutorialController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
await Inventory.updateOne({ accountOwnerId: accountId }, { PlayedParkourTutorial: true });
res.end();
};

View File

@ -1,75 +0,0 @@
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;
}

View File

@ -16,7 +16,7 @@ export const queueDojoComponentDestructionController: RequestHandler = async (re
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
Date.now() + (config.fastDojoRoomDestruction ? 5_000 : 2 * 3600_000)
);
await guild.save();

View File

@ -1,27 +0,0 @@
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,
payload.recipeName == "/Lotus/Types/Game/KubrowPet/ReleasePetRecipe" ? 25000 : 0,
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" | "webui";
petId: string;
}

View File

@ -1,38 +0,0 @@
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();
};

View File

@ -1,8 +1,7 @@
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 { getGuildForRequest, hasGuildPermission } from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes";
@ -19,34 +18,24 @@ export const removeFromGuildController: RequestHandler = async (req, res) => {
}
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");
const inventory = await getInventory(payload.userId);
inventory.GuildId = undefined;
removeDojoKeyItems(inventory);
// Remove clan key or blueprint from kicked member
const itemIndex = inventory.LevelKeys.findIndex(x => x.ItemType == "/Lotus/Types/Keys/DojoKey");
if (itemIndex != -1) {
inventory.LevelKeys.splice(itemIndex, 1);
} else {
const recipeIndex = inventory.Recipes.findIndex(x => x.ItemType == "/Lotus/Types/Keys/DojoKeyBlueprint");
if (recipeIndex != -1) {
inventory.Recipes.splice(recipeIndex, 1);
}
}
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?
}
]);
// TODO: Handle clan leader kicking themselves (guild should be deleted in this case, I think)
} else if (guildMember.status == 2) {
// Delete the inbox message for the invite
await Inbox.deleteOne({
@ -73,7 +62,6 @@ export const removeFromGuildController: RequestHandler = async (req, res) => {
});
}
await guild.save();
}
res.json({
_id: payload.userId,

View File

@ -1,21 +0,0 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Account, Ignore } from "@/src/models/loginModel";
import { getAccountForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
export const removeIgnoredUserController: RequestHandler = async (req, res) => {
const accountId = await getAccountForRequest(req);
const data = getJSONfromString<IRemoveIgnoredUserRequest>(String(req.body));
const ignoreeAccount = await Account.findOne(
{ DisplayName: data.playerName.substring(0, data.playerName.length - 1) },
"_id"
);
if (ignoreeAccount) {
await Ignore.deleteOne({ ignorer: accountId, ignoree: ignoreeAccount._id });
}
res.end();
};
interface IRemoveIgnoredUserRequest {
playerName: string;
}

View File

@ -1,9 +1,6 @@
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { config } from "@/src/services/configService";
import { addEmailItem, getInventory, updateCurrency } from "@/src/services/inventoryService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { ICompletedDialogue, IDialogueDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { ICompletedDialogue } from "@/src/types/inventoryTypes/inventoryTypes";
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express";
@ -11,7 +8,7 @@ export const saveDialogueController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const request = JSON.parse(String(req.body)) as SaveDialogueRequest;
if ("YearIteration" in request) {
const inventory = await getInventory(accountId, "DialogueHistory");
const inventory = await getInventory(accountId);
if (inventory.DialogueHistory) {
inventory.DialogueHistory.YearIteration = request.YearIteration;
} else {
@ -24,25 +21,33 @@ export const saveDialogueController: RequestHandler = async (req, res) => {
if (!inventory.DialogueHistory) {
throw new Error("bad inventory state");
}
const inventoryChanges: IInventoryChanges = {};
const tomorrowAt0Utc = config.noKimCooldowns
? Date.now()
: (Math.trunc(Date.now() / 86400_000) + 1) * 86400_000;
if (request.QueuedDialogues.length != 0 || request.OtherDialogueInfos.length != 0) {
logger.error(`saveDialogue request not fully handled: ${String(req.body)}`);
}
inventory.DialogueHistory.Dialogues ??= [];
const dialogue = getDialogue(inventory, request.DialogueName);
let dialogue = inventory.DialogueHistory.Dialogues.find(x => x.DialogueName == request.DialogueName);
if (!dialogue) {
dialogue =
inventory.DialogueHistory.Dialogues[
inventory.DialogueHistory.Dialogues.push({
Rank: 0,
Chemistry: 0,
AvailableDate: new Date(0),
AvailableGiftDate: new Date(0),
RankUpExpiry: new Date(0),
BountyChemExpiry: new Date(0),
Gifts: [],
Booleans: [],
Completed: [],
DialogueName: request.DialogueName
}) - 1
];
}
dialogue.Rank = request.Rank;
dialogue.Chemistry = request.Chemistry;
if (request.Data) {
dialogue.QueuedDialogues = request.QueuedDialogues;
//dialogue.QueuedDialogues = request.QueuedDialogues;
for (const bool of request.Booleans) {
dialogue.Booleans.push(bool);
if (bool == "LizzieShawzin") {
await addEmailItem(
inventory,
"/Lotus/Types/Items/EmailItems/LizzieShawzinSkinEmailItem",
inventoryChanges
);
}
}
for (const bool of request.ResetBooleans) {
const index = dialogue.Booleans.findIndex(x => x == bool);
@ -51,36 +56,13 @@ export const saveDialogueController: RequestHandler = async (req, res) => {
}
}
dialogue.Completed.push(request.Data);
const tomorrowAt0Utc = (Math.trunc(Date.now() / (86400 * 1000)) + 1) * 86400 * 1000;
dialogue.AvailableDate = new Date(tomorrowAt0Utc);
for (const info of request.OtherDialogueInfos) {
const otherDialogue = getDialogue(inventory, info.Dialogue);
if (info.Tag != "") {
otherDialogue.QueuedDialogues.push(info.Tag);
}
otherDialogue.Chemistry += info.Value; // unsure
}
await inventory.save();
res.json({
InventoryChanges: inventoryChanges,
InventoryChanges: [],
AvailableDate: { $date: { $numberLong: tomorrowAt0Utc.toString() } }
});
} else if (request.Gift) {
const inventoryChanges = updateCurrency(inventory, request.Gift.Cost, false);
const gift = dialogue.Gifts.find(x => x.Item == request.Gift!.Item);
if (gift) {
gift.GiftedQuantity += 1;
} else {
dialogue.Gifts.push({ Item: request.Gift.Item, GiftedQuantity: 1 });
}
dialogue.AvailableGiftDate = new Date(tomorrowAt0Utc);
await inventory.save();
res.json({
InventoryChanges: inventoryChanges,
AvailableGiftDate: { $date: { $numberLong: tomorrowAt0Utc.toString() } }
});
} else {
logger.error(`saveDialogue request not fully handled: ${String(req.body)}`);
}
}
};
@ -95,17 +77,11 @@ interface SaveCompletedDialogueRequest {
Rank: number;
Chemistry: number;
CompletionType: number;
QueuedDialogues: string[];
Gift?: {
Item: string;
GainedChemistry: number;
Cost: number;
GiftedQuantity: number;
};
QueuedDialogues: string[]; // unsure
Booleans: string[];
ResetBooleans: string[];
Data?: ICompletedDialogue;
OtherDialogueInfos: IOtherDialogueInfo[];
Data: ICompletedDialogue;
OtherDialogueInfos: IOtherDialogueInfo[]; // unsure
}
interface IOtherDialogueInfo {
@ -113,26 +89,3 @@ interface IOtherDialogueInfo {
Tag: string;
Value: number;
}
const getDialogue = (inventory: TInventoryDatabaseDocument, dialogueName: string): IDialogueDatabase => {
let dialogue = inventory.DialogueHistory!.Dialogues!.find(x => x.DialogueName == dialogueName);
if (!dialogue) {
dialogue =
inventory.DialogueHistory!.Dialogues![
inventory.DialogueHistory!.Dialogues!.push({
Rank: 0,
Chemistry: 0,
AvailableDate: new Date(0),
AvailableGiftDate: new Date(0),
RankUpExpiry: new Date(0),
BountyChemExpiry: new Date(0),
QueuedDialogues: [],
Gifts: [],
Booleans: [],
Completed: [],
DialogueName: dialogueName
}) - 1
];
}
return dialogue;
};

View File

@ -0,0 +1,32 @@
import { RequestHandler } from "express";
import { ISaveLoadoutRequest } from "@/src/types/saveLoadoutTypes";
import { handleInventoryItemConfigChange } from "@/src/services/saveLoadoutService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { logger } from "@/src/utils/logger";
export const saveLoadoutController: RequestHandler = async (req, res) => {
//validate here
const accountId = await getAccountIdForRequest(req);
try {
const body: ISaveLoadoutRequest = JSON.parse(req.body as string) as ISaveLoadoutRequest;
// console.log(util.inspect(body, { showHidden: false, depth: null, colors: true }));
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { UpgradeVer, ...equipmentChanges } = body;
const newLoadoutId = await handleInventoryItemConfigChange(equipmentChanges, accountId);
//send back new loadout id, if new loadout was added
if (newLoadoutId) {
res.send(newLoadoutId);
}
res.status(200).end();
} catch (error: unknown) {
if (error instanceof Error) {
logger.error(`error in saveLoadoutController: ${error.message}`);
res.status(400).json({ error: error.message });
} else {
res.status(400).json({ error: "unknown error" });
}
}
};

View File

@ -1,21 +0,0 @@
import { RequestHandler } from "express";
import { ISaveLoadoutRequest } from "@/src/types/saveLoadoutTypes";
import { handleInventoryItemConfigChange } from "@/src/services/saveLoadoutService";
import { getAccountIdForRequest } from "@/src/services/loginService";
export const saveLoadoutController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const body: ISaveLoadoutRequest = JSON.parse(req.body as string) as ISaveLoadoutRequest;
// console.log(util.inspect(body, { showHidden: false, depth: null, colors: true }));
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { UpgradeVer, ...equipmentChanges } = body;
const newLoadoutId = await handleInventoryItemConfigChange(equipmentChanges, accountId);
//send back new loadout id, if new loadout was added
if (newLoadoutId) {
res.send(newLoadoutId);
}
res.end();
};

View File

@ -1,25 +0,0 @@
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;
}

View File

@ -6,64 +6,14 @@ import {
addRecipes,
addMiscItems,
addConsumables,
freeUpSlot,
combineInventoryChanges,
addCrewShipRawSalvage
freeUpSlot
} from "@/src/services/inventoryService";
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
import { ExportDojoRecipes } from "warframe-public-export-plus";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
export const sellController: RequestHandler = async (req, res) => {
const payload = JSON.parse(String(req.body)) as ISellRequest;
const accountId = await getAccountIdForRequest(req);
const requiredFields = new Set<keyof TInventoryDatabaseDocument>();
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 as keyof TInventoryDatabaseDocument);
}
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);
}
if (payload.Items.CrewShipWeapons || payload.Items.CrewShipWeaponSkins) {
requiredFields.add(InventorySlot.RJ_COMPONENT_AND_ARMAMENTS);
requiredFields.add("CrewShipRawSalvage");
if (payload.Items.CrewShipWeapons) {
requiredFields.add("CrewShipSalvagedWeapons");
}
if (payload.Items.CrewShipWeaponSkins) {
requiredFields.add("CrewShipSalvagedWeaponSkins");
}
}
const inventory = await getInventory(accountId, Array.from(requiredFields).join(" "));
const inventory = await getInventory(accountId);
// Give currency
if (payload.SellCurrency == "SC_RegularCredits") {
@ -84,14 +34,10 @@ export const sellController: RequestHandler = async (req, res) => {
ItemCount: payload.SellPrice
}
]);
} else if (payload.SellCurrency == "SC_Resources") {
// Will add appropriate MiscItems from CrewShipWeapons or CrewShipWeaponSkins
} else {
throw new Error("Unknown SellCurrency: " + payload.SellCurrency);
}
const inventoryChanges: IInventoryChanges = {};
// Remove item(s)
if (payload.Items.Suits) {
payload.Items.Suits.forEach(sellItem => {
@ -164,56 +110,6 @@ export const sellController: RequestHandler = async (req, res) => {
inventory.Drones.pull({ _id: sellItem.String });
});
}
if (payload.Items.CrewShipWeapons) {
payload.Items.CrewShipWeapons.forEach(sellItem => {
if (sellItem.String[0] == "/") {
addCrewShipRawSalvage(inventory, [
{
ItemType: sellItem.String,
ItemCount: sellItem.Count * -1
}
]);
} else {
const index = inventory.CrewShipWeapons.findIndex(x => x._id.equals(sellItem.String));
if (index != -1) {
if (payload.SellCurrency == "SC_Resources") {
refundPartialBuildCosts(inventory, inventory.CrewShipWeapons[index].ItemType, inventoryChanges);
}
inventory.CrewShipWeapons.splice(index, 1);
freeUpSlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS);
} else {
inventory.CrewShipSalvagedWeapons.pull({ _id: sellItem.String });
}
}
});
}
if (payload.Items.CrewShipWeaponSkins) {
payload.Items.CrewShipWeaponSkins.forEach(sellItem => {
if (sellItem.String[0] == "/") {
addCrewShipRawSalvage(inventory, [
{
ItemType: sellItem.String,
ItemCount: sellItem.Count * -1
}
]);
} else {
const index = inventory.CrewShipWeaponSkins.findIndex(x => x._id.equals(sellItem.String));
if (index != -1) {
if (payload.SellCurrency == "SC_Resources") {
refundPartialBuildCosts(
inventory,
inventory.CrewShipWeaponSkins[index].ItemType,
inventoryChanges
);
}
inventory.CrewShipWeaponSkins.splice(index, 1);
freeUpSlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS);
} else {
inventory.CrewShipSalvagedWeaponSkins.pull({ _id: sellItem.String });
}
}
});
}
if (payload.Items.Consumables) {
const consumablesChanges = [];
for (const sellItem of payload.Items.Consumables) {
@ -260,9 +156,7 @@ export const sellController: RequestHandler = async (req, res) => {
}
await inventory.save();
res.json({
inventoryChanges: inventoryChanges // "inventoryChanges" for this response instead of the usual "InventoryChanges"
});
res.json({});
};
interface ISellRequest {
@ -283,8 +177,6 @@ interface ISellRequest {
OperatorAmps?: ISellItem[];
Hoverboards?: ISellItem[];
Drones?: ISellItem[];
CrewShipWeapons?: ISellItem[];
CrewShipWeaponSkins?: ISellItem[];
};
SellPrice: number;
SellCurrency:
@ -301,33 +193,3 @@ interface ISellItem {
String: string; // oid or uniqueName
Count: number;
}
const refundPartialBuildCosts = (
inventory: TInventoryDatabaseDocument,
itemType: string,
inventoryChanges: IInventoryChanges
): void => {
// House versions
const research = Object.values(ExportDojoRecipes.research).find(x => x.resultType == itemType);
if (research) {
const miscItemChanges = research.ingredients.map(x => ({
ItemType: x.ItemType,
ItemCount: Math.trunc(x.ItemCount * 0.8)
}));
addMiscItems(inventory, miscItemChanges);
combineInventoryChanges(inventoryChanges, { MiscItems: miscItemChanges });
return;
}
// Sigma versions
const recipe = Object.values(ExportDojoRecipes.fabrications).find(x => x.resultType == itemType);
if (recipe) {
const miscItemChanges = recipe.ingredients.map(x => ({
ItemType: x.ItemType,
ItemCount: Math.trunc(x.ItemCount * 0.8)
}));
addMiscItems(inventory, miscItemChanges);
combineInventoryChanges(inventoryChanges, { MiscItems: miscItemChanges });
return;
}
};

View File

@ -1,31 +0,0 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { createMessage } from "@/src/services/inboxService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
export const sendMsgToInBoxController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const data = getJSONfromString<ISendMsgToInBoxRequest>(String(req.body));
await createMessage(accountId, [
{
sub: data.title,
msg: data.message,
sndr: data.sender ?? "/Lotus/Language/Bosses/Ordis",
icon: data.senderIcon,
highPriority: data.highPriority,
transmission: data.transmission,
att: data.attachments
}
]);
res.end();
};
interface ISendMsgToInBoxRequest {
title: string;
message: string;
sender?: string;
senderIcon?: string;
highPriority?: boolean;
transmission?: string;
attachments?: string[];
}

View File

@ -1,38 +0,0 @@
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();
};

View File

@ -1,34 +0,0 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
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 setDojoComponentColorsController: 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 data = getJSONfromString<ISetDojoComponentColorsRequest>(String(req.body));
const component = guild.DojoComponents.id(data.ComponentId)!;
//const deco = component.Decos!.find(x => x._id.equals(data.DecoId))!;
//deco.Pending = true;
//component.PaintBot = new Types.ObjectId(data.DecoId);
if ("lights" in req.query) {
component.PendingLights = data.Colours;
} else {
component.PendingColors = data.Colours;
}
await guild.save();
res.json(await getDojoClient(guild, 0, component._id));
};
interface ISetDojoComponentColorsRequest {
ComponentId: string;
DecoId: string;
Colours: number[];
}

View File

@ -1,25 +0,0 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
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 setDojoComponentSettingsController: 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 component = guild.DojoComponents.id(req.query.componentId)!;
const data = getJSONfromString<ISetDojoComponentSettingsRequest>(String(req.body));
component.Settings = data.Settings;
await guild.save();
res.json(await getDojoClient(guild, 0, component._id));
};
interface ISetDojoComponentSettingsRequest {
Settings: string;
}

View File

@ -1,45 +1,22 @@
import { Alliance, Guild, GuildMember } from "@/src/models/guildModel";
import { hasGuildPermissionEx } from "@/src/services/guildService";
import { Guild } from "@/src/models/guildModel";
import { hasGuildPermission } 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 { GuildPermission } 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 }))!;
if (!(await hasGuildPermission(guild, account._id, GuildPermission.Herald))) {
res.status(400).json("Invalid permission");
return;
}
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 = {
@ -53,7 +30,6 @@ export const setGuildMotdController: RequestHandler = async (req, res) => {
guild.MOTD = MOTD ?? "";
}
await guild.save();
}
res.json({ IsLongMOTD, MOTD });
};

View File

@ -1,21 +0,0 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IHubNpcCustomization } from "@/src/types/inventoryTypes/inventoryTypes";
import { RequestHandler } from "express";
export const setHubNpcCustomizationsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "HubNpcCustomizations");
const upload = getJSONfromString<IHubNpcCustomization>(String(req.body));
inventory.HubNpcCustomizations ??= [];
const cust = inventory.HubNpcCustomizations.find(x => x.Tag == upload.Tag);
if (cust) {
cust.Colors = upload.Colors;
cust.Pattern = upload.Pattern;
} else {
inventory.HubNpcCustomizations.push(upload);
}
await inventory.save();
res.end();
};

View File

@ -1,5 +1,5 @@
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IPictureFrameInfo, ISetPlacedDecoInfoRequest } from "@/src/types/shipTypes";
import { ISetPlacedDecoInfoRequest } from "@/src/types/shipTypes";
import { RequestHandler } from "express";
import { handleSetPlacedDecoInfo } from "@/src/services/shipCustomizationsService";
@ -7,17 +7,5 @@ export const setPlacedDecoInfoController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const payload = JSON.parse(req.body as string) as ISetPlacedDecoInfoRequest;
await handleSetPlacedDecoInfo(accountId, payload);
res.json({
DecoId: payload.DecoId,
IsPicture: true,
PictureFrameInfo: payload.PictureFrameInfo,
BootLocation: payload.BootLocation
} satisfies ISetPlacedDecoInfoResponse);
res.end();
};
interface ISetPlacedDecoInfoResponse {
DecoId: string;
IsPicture: boolean;
PictureFrameInfo?: IPictureFrameInfo;
BootLocation?: string;
}

View File

@ -3,40 +3,29 @@ import { RequestHandler } from "express";
import { getPersonalRooms } from "@/src/services/personalRoomsService";
import { IOid } from "@/src/types/commonTypes";
import { Types } from "mongoose";
import { IFavouriteLoadoutDatabase, TBootLocation } from "@/src/types/shipTypes";
export const setShipFavouriteLoadoutController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const personalRooms = await getPersonalRooms(accountId);
const body = JSON.parse(String(req.body)) as ISetShipFavouriteLoadoutRequest;
if (body.BootLocation == "LISET") {
personalRooms.Ship.FavouriteLoadoutId = new Types.ObjectId(body.FavouriteLoadoutId.$oid);
} else if (body.BootLocation == "APARTMENT") {
updateTaggedDisplay(personalRooms.Apartment.FavouriteLoadouts, body);
} else if (body.BootLocation == "SHOP") {
updateTaggedDisplay(personalRooms.TailorShop.FavouriteLoadouts, body);
} else {
console.log(body);
if (body.BootLocation != "SHOP") {
throw new Error(`unexpected BootLocation: ${body.BootLocation}`);
}
const display = personalRooms.TailorShop.FavouriteLoadouts.find(x => x.Tag == body.TagName);
if (display) {
display.LoadoutId = new Types.ObjectId(body.FavouriteLoadoutId.$oid);
} else {
personalRooms.TailorShop.FavouriteLoadouts.push({
Tag: body.TagName,
LoadoutId: new Types.ObjectId(body.FavouriteLoadoutId.$oid)
});
}
await personalRooms.save();
res.json({});
};
interface ISetShipFavouriteLoadoutRequest {
BootLocation: TBootLocation;
BootLocation: string;
FavouriteLoadoutId: IOid;
TagName?: string;
TagName: string;
}
const updateTaggedDisplay = (arr: IFavouriteLoadoutDatabase[], body: ISetShipFavouriteLoadoutRequest): void => {
const display = arr.find(x => x.Tag == body.TagName!);
if (display) {
display.LoadoutId = new Types.ObjectId(body.FavouriteLoadoutId.$oid);
} else {
arr.push({
Tag: body.TagName!,
LoadoutId: new Types.ObjectId(body.FavouriteLoadoutId.$oid)
});
}
};

View File

@ -1,48 +0,0 @@
import { addMiscItems, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getPersonalRooms } from "@/src/services/personalRoomsService";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express";
export const setShipVignetteController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "MiscItems");
const personalRooms = await getPersonalRooms(accountId);
const body = JSON.parse(String(req.body)) as ISetShipVignetteRequest;
personalRooms.Ship.Wallpaper = body.Wallpaper;
personalRooms.Ship.Vignette = body.Vignette;
personalRooms.Ship.VignetteFish ??= [];
const inventoryChanges: IInventoryChanges = {};
for (let i = 0; i != body.Fish.length; ++i) {
if (body.Fish[i] && !personalRooms.Ship.VignetteFish[i]) {
logger.debug(`moving ${body.Fish[i]} from inventory to vignette slot ${i}`);
const miscItemsDelta = [{ ItemType: body.Fish[i], ItemCount: -1 }];
addMiscItems(inventory, miscItemsDelta);
combineInventoryChanges(inventoryChanges, { MiscItems: miscItemsDelta });
} else if (personalRooms.Ship.VignetteFish[i] && !body.Fish[i]) {
logger.debug(`moving ${personalRooms.Ship.VignetteFish[i]} from vignette slot ${i} to inventory`);
const miscItemsDelta = [{ ItemType: personalRooms.Ship.VignetteFish[i], ItemCount: +1 }];
addMiscItems(inventory, miscItemsDelta);
combineInventoryChanges(inventoryChanges, { MiscItems: miscItemsDelta });
}
}
personalRooms.Ship.VignetteFish = body.Fish;
if (body.VignetteDecos.length) {
logger.error(`setShipVignette request not fully handled:`, body);
}
await Promise.all([inventory.save(), personalRooms.save()]);
res.json({
Wallpaper: body.Wallpaper,
Vignette: body.Vignette,
VignetteFish: body.Fish,
InventoryChanges: inventoryChanges
});
};
interface ISetShipVignetteRequest {
Wallpaper: string;
Vignette: string;
Fish: string[];
VignetteDecos: unknown[];
}

View File

@ -1,25 +1,20 @@
import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
import { equipmentKeys, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import { WeaponTypeInternal } from "@/src/services/itemDataService";
export const setWeaponSkillTreeController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const payload = getJSONfromString<ISetWeaponSkillTreeRequest>(String(req.body));
if (equipmentKeys.indexOf(req.query.Category as TEquipmentKey) != -1) {
await Inventory.updateOne(
{
accountOwnerId: accountId,
[`${req.query.Category as string}._id`]: req.query.ItemId as string
},
{
[`${req.query.Category as string}.$.SkillTree`]: payload.SkillTree
}
);
}
const item = inventory[req.query.Category as WeaponTypeInternal].find(
item => item._id.toString() == (req.query.ItemId as string)
)!;
item.SkillTree = payload.SkillTree;
await inventory.save();
res.end();
};

View File

@ -90,6 +90,7 @@ export const startRecipeController: RequestHandler = async (req, res) => {
spectreLoadout.LongGuns = item.ItemType;
spectreLoadout.LongGunsModularParts = item.ModularParts;
} else {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
console.assert(type == "/Lotus/Types/Game/LotusMeleeWeapon");
const item = inventory.Melee.id(oid)!;
spectreLoadout.Melee = item.ItemType;
@ -110,8 +111,6 @@ export const startRecipeController: RequestHandler = async (req, res) => {
inventory.PendingSpectreLoadouts.push(spectreLoadout);
logger.debug("pending spectre loadout", spectreLoadout);
}
} else if (recipe.secretIngredientAction == "SIA_UNBRAND") {
pr.SuitToUnbrand = new Types.ObjectId(startRecipeRequest.Ids[recipe.ingredients.length + 0]);
}
await inventory.save();

View File

@ -3,9 +3,15 @@ import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { ExportNightwave, ExportSyndicates, ISyndicateSacrifice } from "warframe-public-export-plus";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { addMiscItems, combineInventoryChanges, getInventory, updateCurrency } from "@/src/services/inventoryService";
import {
addItem,
addMiscItems,
combineInventoryChanges,
getInventory,
updateCurrency
} from "@/src/services/inventoryService";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { isStoreItem, toStoreItem } from "@/src/services/itemDataService";
import { fromStoreItem, isStoreItem } from "@/src/services/itemDataService";
export const syndicateSacrificeController: RequestHandler = async (request, response) => {
const accountId = await getAccountIdForRequest(request);
@ -51,7 +57,7 @@ export const syndicateSacrificeController: RequestHandler = async (request, resp
syndicate.Title ??= 0;
syndicate.Title += 1;
if (syndicate.Title > 0 && manifest.favours.find(x => x.rankUpReward && x.requiredLevel == syndicate.Title)) {
if (syndicate.Title > 0 && manifest.favours.length != 0) {
syndicate.FreeFavorsEarned ??= [];
if (!syndicate.FreeFavorsEarned.includes(syndicate.Title)) {
syndicate.FreeFavorsEarned.push(syndicate.Title);
@ -71,13 +77,10 @@ export const syndicateSacrificeController: RequestHandler = async (request, resp
res.NewEpisodeReward = true;
const reward = ExportNightwave.rewards[index];
let rewardType = reward.uniqueName;
if (!isStoreItem(rewardType)) {
rewardType = toStoreItem(rewardType);
if (isStoreItem(rewardType)) {
rewardType = fromStoreItem(rewardType);
}
combineInventoryChanges(
res.InventoryChanges,
(await handleStoreItemAcquisition(rewardType, inventory, reward.itemCount)).InventoryChanges
);
combineInventoryChanges(res.InventoryChanges, await addItem(inventory, rewardType, reward.itemCount ?? 1));
}
}

View File

@ -1,9 +1,16 @@
import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { addMiscItems, addStanding, freeUpSlot, getInventory } from "@/src/services/inventoryService";
import {
addMiscItems,
freeUpSlot,
getInventory,
getStandingLimit,
updateStandingLimit
} from "@/src/services/inventoryService";
import { IMiscItem, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
import { IOid } from "@/src/types/commonTypes";
import { ExportSyndicates, ExportWeapons } from "warframe-public-export-plus";
import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper";
import { logger } from "@/src/utils/logger";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
@ -54,13 +61,38 @@ export const syndicateStandingBonusController: RequestHandler = async (req, res)
inventoryChanges[slotBin] = { count: -1, platinum: 0, Slots: 1 };
}
const affiliationMod = addStanding(inventory, request.Operation.AffiliationTag, gainedStanding, true);
let syndicate = inventory.Affiliations.find(x => x.Tag == request.Operation.AffiliationTag);
if (!syndicate) {
syndicate =
inventory.Affiliations[
inventory.Affiliations.push({ Tag: request.Operation.AffiliationTag, Standing: 0 }) - 1
];
}
const max = getMaxStanding(syndicateMeta, syndicate.Title ?? 0);
if (syndicate.Standing + gainedStanding > max) {
gainedStanding = max - syndicate.Standing;
}
if (syndicateMeta.medallionsCappedByDailyLimit) {
if (gainedStanding > getStandingLimit(inventory, syndicateMeta.dailyLimitBin)) {
gainedStanding = getStandingLimit(inventory, syndicateMeta.dailyLimitBin);
}
updateStandingLimit(inventory, syndicateMeta.dailyLimitBin, gainedStanding);
}
syndicate.Standing += gainedStanding;
await inventory.save();
res.json({
InventoryChanges: inventoryChanges,
AffiliationMods: [affiliationMod]
AffiliationMods: [
{
Tag: request.Operation.AffiliationTag,
Standing: gainedStanding
}
]
});
};

View File

@ -6,7 +6,6 @@ import { RequestHandler } from "express";
import { unixTimesInMs } from "@/src/constants/timeConstants";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { createMessage } from "@/src/services/inboxService";
import { config } from "@/src/services/configService";
interface ITrainingResultsRequest {
numLevelsGained: number;
@ -23,17 +22,11 @@ const trainingResultController: RequestHandler = async (req, res): Promise<void>
const trainingResults = getJSONfromString<ITrainingResultsRequest>(String(req.body));
const inventory = await getInventory(accountId, "TrainingDate PlayerLevel TradesRemaining");
const inventory = await getInventory(accountId);
if (trainingResults.numLevelsGained == 1) {
let time = Date.now();
if (!config.noMasteryRankUpCooldown) {
time += unixTimesInMs.hour * 23;
}
inventory.TrainingDate = new Date(time);
inventory.TrainingDate = new Date(Date.now() + unixTimesInMs.hour * 23);
inventory.PlayerLevel += 1;
inventory.TradesRemaining += 1;
await createMessage(accountId, [
{

View File

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

View File

@ -1,8 +1,10 @@
import { RequestHandler } from "express";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { addChallenges, getInventory } from "@/src/services/inventoryService";
import { addChallenges, addSeasonalChallengeHistory, getInventory } from "@/src/services/inventoryService";
import { IChallengeProgress, ISeasonChallenge } from "@/src/types/inventoryTypes/inventoryTypes";
import { ExportNightwave } from "warframe-public-export-plus";
import { logger } from "@/src/utils/logger";
import { IAffiliationMods } from "@/src/types/purchaseTypes";
export const updateChallengeProgressController: RequestHandler = async (req, res) => {
@ -10,19 +12,41 @@ export const updateChallengeProgressController: RequestHandler = async (req, res
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "ChallengeProgress SeasonChallengeHistory Affiliations");
let affiliationMods: IAffiliationMods[] = [];
if (challenges.ChallengeProgress) {
affiliationMods = addChallenges(inventory, challenges.ChallengeProgress, challenges.SeasonChallengeCompletions);
addChallenges(inventory, challenges.ChallengeProgress);
}
if (challenges.SeasonChallengeHistory) {
challenges.SeasonChallengeHistory.forEach(({ challenge, id }) => {
const itemIndex = inventory.SeasonChallengeHistory.findIndex(i => i.challenge === challenge);
if (itemIndex !== -1) {
inventory.SeasonChallengeHistory[itemIndex].id = id;
} else {
inventory.SeasonChallengeHistory.push({ challenge, id });
addSeasonalChallengeHistory(inventory, challenges.SeasonChallengeHistory);
}
const affiliationMods: IAffiliationMods[] = [];
if (challenges.ChallengeProgress && challenges.SeasonChallengeCompletions) {
for (const challenge of challenges.SeasonChallengeCompletions) {
// Ignore challenges that weren't completed just now
if (!challenges.ChallengeProgress.find(x => challenge.challenge.indexOf(x.Name) != -1)) {
continue;
}
const meta = ExportNightwave.challenges[challenge.challenge];
logger.debug("Completed challenge", meta);
let affiliation = inventory.Affiliations.find(x => x.Tag == ExportNightwave.affiliationTag);
if (!affiliation) {
affiliation =
inventory.Affiliations[
inventory.Affiliations.push({
Tag: ExportNightwave.affiliationTag,
Standing: 0
}) - 1
];
}
affiliation.Standing += meta.standing;
if (affiliationMods.length == 0) {
affiliationMods.push({ Tag: ExportNightwave.affiliationTag });
}
affiliationMods[0].Standing ??= 0;
affiliationMods[0].Standing += meta.standing;
}
});
}
await inventory.save();

View File

@ -5,6 +5,7 @@ import { updateQuestKey, IUpdateQuestRequest } from "@/src/services/questService
import { getInventory } from "@/src/services/inventoryService";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const updateQuestController: RequestHandler = async (req, res) => {
const accountId = parseString(req.query.accountId);
const updateQuestRequest = getJSONfromString<IUpdateQuestRequest>((req.body as string).toString());

View File

@ -11,7 +11,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
import { addMiscItems, addRecipes, getInventory, updateCurrency } from "@/src/services/inventoryService";
import { getRecipeByResult } from "@/src/services/itemDataService";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { addInfestedFoundryXP, applyCheatsToInfestedFoundry } from "@/src/services/infestedFoundryService";
import { addInfestedFoundryXP } from "./infestedFoundryController";
import { config } from "@/src/services/configService";
export const upgradesController: RequestHandler = async (req, res) => {
@ -25,13 +25,7 @@ export const upgradesController: RequestHandler = async (req, res) => {
operation.UpgradeRequirement == "/Lotus/Types/Items/MiscItems/CustomizationSlotUnlocker"
) {
updateCurrency(inventory, 10, true);
} else if (
operation.OperationType != "UOT_SWAP_POLARITY" &&
operation.OperationType != "UOT_ABILITY_OVERRIDE"
) {
if (!operation.UpgradeRequirement) {
throw new Error(`${operation.OperationType} operation should be free?`);
}
} else {
addMiscItems(inventory, [
{
ItemType: operation.UpgradeRequirement,
@ -72,7 +66,6 @@ export const upgradesController: RequestHandler = async (req, res) => {
inventoryChanges.Recipes = recipeChanges;
inventoryChanges.InfestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry;
applyCheatsToInfestedFoundry(inventoryChanges.InfestedFoundry!);
} else
switch (operation.UpgradeRequirement) {
case "/Lotus/Types/Items/MiscItems/OrokinReactor":

View File

@ -1,6 +1,6 @@
import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { Account, Ignore } from "@/src/models/loginModel";
import { Account } from "@/src/models/loginModel";
import { Inbox } from "@/src/models/inboxModel";
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
@ -9,22 +9,13 @@ import { Ship } from "@/src/models/shipModel";
import { Stats } from "@/src/models/statsModel";
import { GuildMember } from "@/src/models/guildModel";
import { Leaderboard } from "@/src/models/leaderboardModel";
import { deleteGuild } from "@/src/services/guildService";
export const deleteAccountController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
// If account is the founding warlord of a guild, delete that guild as well.
const guildMember = await GuildMember.findOne({ accountId, rank: 0, status: 0 });
if (guildMember) {
await deleteGuild(guildMember.guildId);
}
// TODO: Handle the account being the creator of a guild
await Promise.all([
Account.deleteOne({ _id: accountId }),
GuildMember.deleteMany({ accountId: accountId }),
Ignore.deleteMany({ ignorer: accountId }),
Ignore.deleteMany({ ignoree: accountId }),
Inbox.deleteMany({ ownerId: accountId }),
Inventory.deleteOne({ accountOwnerId: accountId }),
Leaderboard.deleteMany({ ownerId: accountId }),

View File

@ -1,4 +1,4 @@
import { AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
import { Guild, GuildMember } from "@/src/models/guildModel";
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
import { RequestHandler } from "express";
@ -12,19 +12,9 @@ export const getAccountInfoController: RequestHandler = async (req, res) => {
}
const guildMember = await GuildMember.findOne({ accountId: account._id, status: 0 }, "guildId rank");
if (guildMember) {
const guild = (await Guild.findById(guildMember.guildId, "Ranks AllianceId"))!;
const guild = (await Guild.findById(guildMember.guildId, "Ranks"))!;
info.GuildId = guildMember.guildId.toString();
info.GuildPermissions = guild.Ranks[guildMember.rank].Permissions;
info.GuildRank = guildMember.rank;
if (guild.AllianceId) {
//const alliance = (await Alliance.findById(guild.AllianceId))!;
const allianceMember = (await AllianceMember.findOne({
allianceId: guild.AllianceId,
guildId: guild._id
}))!;
info.AllianceId = guild.AllianceId.toString();
info.AlliancePermissions = allianceMember.Permissions;
}
}
res.json(info);
};
@ -34,7 +24,4 @@ interface IAccountInfo {
IsAdministrator?: boolean;
GuildId?: string;
GuildPermissions?: number;
GuildRank?: number;
AllianceId?: string;
AlliancePermissions?: number;
}

View File

@ -3,10 +3,8 @@ import { getDict, getItemName, getString } from "@/src/services/itemDataService"
import {
ExportArcanes,
ExportAvionics,
ExportCustoms,
ExportDrones,
ExportGear,
ExportKeys,
ExportMisc,
ExportRailjackWeapons,
ExportRecipes,
@ -27,8 +25,6 @@ interface ListedItem {
fusionLimit?: number;
exalted?: string[];
badReason?: "starter" | "frivolous" | "notraw";
partType?: string;
chainLength?: number;
}
const relicQualitySuffixes: Record<TRelicQuality, string> = {
@ -54,9 +50,6 @@ const getItemListsController: RequestHandler = (req, response) => {
res.MechSuits = [];
res.miscitems = [];
res.Syndicates = [];
res.OperatorAmps = [];
res.QuestKeys = [];
res.KubrowPets = [];
for (const [uniqueName, item] of Object.entries(ExportWarframes)) {
res[item.productCategory].push({
uniqueName,
@ -65,7 +58,7 @@ const getItemListsController: RequestHandler = (req, response) => {
});
}
for (const [uniqueName, item] of Object.entries(ExportSentinels)) {
if (item.productCategory != "SpecialItems") {
if (item.productCategory == "Sentinels") {
res[item.productCategory].push({
uniqueName,
name: getString(item.name, lang)
@ -73,14 +66,21 @@ const getItemListsController: RequestHandler = (req, response) => {
}
}
for (const [uniqueName, item] of Object.entries(ExportWeapons)) {
if (item.partType) {
if (!uniqueName.startsWith("/Lotus/Types/Items/Deimos/")) {
if (
uniqueName.split("/")[4] == "OperatorAmplifiers" ||
uniqueName.split("/")[5] == "SUModularSecondarySet1" ||
uniqueName.split("/")[5] == "SUModularPrimarySet1" ||
uniqueName.split("/")[5] == "InfKitGun" ||
uniqueName.split("/")[5] == "HoverboardParts" ||
uniqueName.split("/")[5] == "ModularMelee01" ||
uniqueName.split("/")[5] == "ModularMelee02" ||
uniqueName.split("/")[5] == "ModularMeleeInfested" ||
uniqueName.split("/")[6] == "CreaturePetParts"
) {
res.ModularParts.push({
uniqueName,
name: getString(item.name, lang),
partType: item.partType
name: getString(item.name, lang)
});
}
if (uniqueName.split("/")[5] != "SentTrainingAmplifier") {
res.miscitems.push({
uniqueName: uniqueName,
@ -94,8 +94,7 @@ const getItemListsController: RequestHandler = (req, response) => {
item.productCategory == "Melee" ||
item.productCategory == "SpaceGuns" ||
item.productCategory == "SpaceMelee" ||
item.productCategory == "SentinelWeapons" ||
item.productCategory == "OperatorAmps"
item.productCategory == "SentinelWeapons"
) {
res[item.productCategory].push({
uniqueName,
@ -122,7 +121,6 @@ const getItemListsController: RequestHandler = (req, response) => {
}
}
if (
name &&
uniqueName.substr(0, 30) != "/Lotus/Types/Game/Projections/" &&
uniqueName != "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLanthornBundle"
) {
@ -154,11 +152,9 @@ const getItemListsController: RequestHandler = (req, response) => {
if (!item.hidden) {
const resultName = getItemName(item.resultType);
if (resultName) {
let itemName = getString(resultName, lang);
if (item.num > 1) itemName = `${itemName} X ${item.num}`;
res.miscitems.push({
uniqueName: uniqueName,
name: recipeNameTemplate.replace("|ITEM|", itemName)
name: recipeNameTemplate.replace("|ITEM|", getString(resultName, lang))
});
}
}
@ -175,12 +171,6 @@ const getItemListsController: RequestHandler = (req, response) => {
name: getString(item.name, lang)
});
}
for (const [uniqueName, item] of Object.entries(ExportCustoms)) {
res.miscitems.push({
uniqueName: uniqueName,
name: getString(item.name, lang)
});
}
res.mods = [];
for (const [uniqueName, upgrade] of Object.entries(ExportUpgrades)) {
@ -223,20 +213,6 @@ const getItemListsController: RequestHandler = (req, response) => {
name: getString(syndicate.name, lang)
});
}
for (const [uniqueName, key] of Object.entries(ExportKeys)) {
if (key.chainStages) {
res.QuestKeys.push({
uniqueName,
name: getString(key.name || "", lang),
chainLength: key.chainStages.length
});
} else if (key.name) {
res.miscitems.push({
uniqueName,
name: getString(key.name, lang)
});
}
}
response.json({
archonCrystalUpgrades,

View File

@ -1,11 +1,7 @@
import { addString } from "@/src/controllers/api/inventoryController";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import {
addQuestKey,
completeQuest,
giveKeyChainMissionReward,
giveKeyChainStageTriggered
} from "@/src/services/questService";
import { addQuestKey, completeQuest, IUpdateQuestRequest, updateQuestKey } from "@/src/services/questService";
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express";
import { ExportKeys } from "warframe-public-export-plus";
@ -13,17 +9,13 @@ import { ExportKeys } from "warframe-public-export-plus";
export const manageQuestsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const operation = req.query.operation as
| "unlockAll"
| "completeAll"
| "resetAll"
| "giveAll"
| "completeKey"
| "deleteKey"
| "resetKey"
| "prevStage"
| "nextStage"
| "setInactive";
const questItemType = req.query.itemType as string;
| "ResetAll"
| "completeAllUnlocked"
| "updateKey"
| "giveAll";
const questKeyUpdate = req.body as IUpdateQuestRequest["QuestKeys"];
const allQuestKeys: string[] = [];
for (const [k, v] of Object.entries(ExportKeys)) {
@ -34,13 +26,47 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
const inventory = await getInventory(accountId);
switch (operation) {
case "completeAll": {
for (const questKey of inventory.QuestKeys) {
await completeQuest(inventory, questKey.ItemType);
case "updateKey": {
//TODO: if this is intended to be used, one needs to add a updateQuestKeyMultiple, the game does never intend to do it, so it errors for multiple keys.
await updateQuestKey(inventory, questKeyUpdate);
break;
}
case "unlockAll": {
for (const questKey of allQuestKeys) {
addQuestKey(inventory, { ItemType: questKey, Completed: false, unlock: true, Progress: [] });
}
break;
}
case "resetAll": {
case "completeAll": {
logger.info("completing all quests..");
for (const questKey of allQuestKeys) {
try {
await completeQuest(inventory, questKey);
} catch (error) {
if (error instanceof Error) {
logger.error(
`Something went wrong completing quest ${questKey}, probably could not add some item`
);
logger.error(error.message);
}
}
//Skip "Watch The Maker"
if (questKey === "/Lotus/Types/Keys/NewWarIntroQuest/NewWarIntroKeyChain") {
addString(
inventory.NodeIntrosCompleted,
"/Lotus/Levels/Cinematics/NewWarIntro/NewWarStageTwo.level"
);
}
if (questKey === "/Lotus/Types/Keys/ArchwingQuest/ArchwingQuestKeyChain") {
inventory.ArchwingEnabled = true;
}
}
break;
}
case "ResetAll": {
logger.info("resetting all quests..");
for (const questKey of inventory.QuestKeys) {
questKey.Completed = false;
questKey.Progress = [];
@ -49,110 +75,40 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
inventory.ActiveQuest = "";
break;
}
case "completeAllUnlocked": {
logger.info("completing all unlocked quests..");
for (const questKey of inventory.QuestKeys) {
try {
await completeQuest(inventory, questKey.ItemType);
} catch (error) {
if (error instanceof Error) {
logger.error(
`Something went wrong completing quest ${questKey.ItemType}, probably could not add some item`
);
logger.error(error.message);
}
}
//Skip "Watch The Maker"
if (questKey.ItemType === "/Lotus/Types/Keys/NewWarIntroQuest/NewWarIntroKeyChain") {
addString(
inventory.NodeIntrosCompleted,
"/Lotus/Levels/Cinematics/NewWarIntro/NewWarStageTwo.level"
);
}
if (questKey.ItemType === "/Lotus/Types/Keys/ArchwingQuest/ArchwingQuestKeyChain") {
inventory.ArchwingEnabled = true;
}
}
break;
}
case "giveAll": {
allQuestKeys.forEach(questKey => addQuestKey(inventory, { ItemType: questKey }));
break;
}
case "deleteKey": {
const questKey = inventory.QuestKeys.find(key => key.ItemType === questItemType);
if (!questKey) {
logger.error(`Quest key not found in inventory: ${questItemType}`);
break;
}
inventory.QuestKeys.pull({ ItemType: questItemType });
break;
}
case "completeKey": {
if (allQuestKeys.includes(questItemType)) {
const questKey = inventory.QuestKeys.find(key => key.ItemType === questItemType);
if (!questKey) {
logger.error(`Quest key not found in inventory: ${questItemType}`);
break;
}
await completeQuest(inventory, questItemType);
for (const questKey of allQuestKeys) {
addQuestKey(inventory, { ItemType: questKey });
}
break;
}
case "resetKey": {
if (allQuestKeys.includes(questItemType)) {
const questKey = inventory.QuestKeys.find(key => key.ItemType === questItemType);
if (!questKey) {
logger.error(`Quest key not found in inventory: ${questItemType}`);
break;
}
questKey.Completed = false;
questKey.Progress = [];
questKey.CompletionDate = undefined;
}
break;
}
case "prevStage": {
if (allQuestKeys.includes(questItemType)) {
const questKey = inventory.QuestKeys.find(key => key.ItemType === questItemType);
if (!questKey) {
logger.error(`Quest key not found in inventory: ${questItemType}`);
break;
}
if (!questKey.Progress) break;
if (questKey.Completed) {
questKey.Completed = false;
questKey.CompletionDate = undefined;
}
questKey.Progress.pop();
const stage = questKey.Progress.length - 1;
if (stage > 0) {
await giveKeyChainStageTriggered(inventory, {
KeyChain: questKey.ItemType,
ChainStage: stage
});
}
}
break;
}
case "nextStage": {
if (allQuestKeys.includes(questItemType)) {
const questKey = inventory.QuestKeys.find(key => key.ItemType === questItemType);
const questManifest = ExportKeys[questItemType];
if (!questKey) {
logger.error(`Quest key not found in inventory: ${questItemType}`);
break;
}
if (!questKey.Progress) break;
const currentStage = questKey.Progress.length;
if (currentStage + 1 == questManifest.chainStages?.length) {
logger.debug(`Trying to complete last stage with nextStage, calling completeQuest instead`);
await completeQuest(inventory, questKey.ItemType);
} else {
const progress = {
c: questManifest.chainStages![currentStage].key ? -1 : 0,
i: false,
m: false,
b: []
};
questKey.Progress.push(progress);
await giveKeyChainStageTriggered(inventory, {
KeyChain: questKey.ItemType,
ChainStage: currentStage
});
if (currentStage > 0) {
await giveKeyChainMissionReward(inventory, {
KeyChain: questKey.ItemType,
ChainStage: currentStage - 1
});
}
}
}
break;
}
case "setInactive":
inventory.ActiveQuest = "";
break;
}
await inventory.save();

View File

@ -5,7 +5,7 @@ import { getInventory } from "@/src/services/inventoryService";
export const popArchonCrystalUpgradeController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const suit = inventory.Suits.id(req.query.oid as string);
const suit = inventory.Suits.find(suit => suit._id.toString() == (req.query.oid as string));
if (suit && suit.ArchonCrystalUpgrades) {
suit.ArchonCrystalUpgrades = suit.ArchonCrystalUpgrades.filter(
x => x.UpgradeType != (req.query.type as string)

View File

@ -5,7 +5,7 @@ import { getInventory } from "@/src/services/inventoryService";
export const pushArchonCrystalUpgradeController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const suit = inventory.Suits.id(req.query.oid as string);
const suit = inventory.Suits.find(suit => suit._id.toString() == (req.query.oid as string));
if (suit) {
suit.ArchonCrystalUpgrades ??= [];
const count = (req.query.count as number | undefined) ?? 1;

View File

@ -1,7 +1,6 @@
import { RequestHandler } from "express";
import { getAccountForRequest, isAdministrator, isNameTaken } from "@/src/services/loginService";
import { config } from "@/src/services/configService";
import { saveConfig } from "@/src/services/configWatcherService";
import { config, saveConfig } from "@/src/services/configService";
export const renameAccountController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);

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