Compare commits

..

1 Commits

Author SHA1 Message Date
10eaa35e98 feat: WeaponSkins IsNew flag
All checks were successful
Build / build (18) (push) Successful in 42s
Build / build (22) (push) Successful in 1m11s
Build / build (18) (pull_request) Successful in 43s
Build / build (20) (push) Successful in 1m25s
Build / build (20) (pull_request) Successful in 1m12s
Build / build (22) (pull_request) Successful in 52s
2025-03-27 20:32:02 +01:00
211 changed files with 5112 additions and 13262 deletions

View File

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

2
.gitattributes vendored
View File

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

View File

@ -5,22 +5,17 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
matrix:
version: [18, 20, 22]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.2 uses: actions/checkout@v4.1.2
- name: Setup Node.js environment - name: Setup Node.js environment
uses: actions/setup-node@v4.0.2 uses: actions/setup-node@v4.0.2
with:
node-version: ${{ matrix.version }}
- run: npm ci - run: npm ci
- run: cp config.json.example config.json - run: cp config.json.example config.json
- run: npm run verify - run: npm run build
- run: npm run lint:ci - run: npm run lint
- 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

View File

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

View File

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

View File

@ -5,40 +5,19 @@ ENV APP_MY_ADDRESS=localhost
ENV APP_HTTP_PORT=80 ENV APP_HTTP_PORT=80
ENV APP_HTTPS_PORT=443 ENV APP_HTTPS_PORT=443
ENV APP_AUTO_CREATE_ACCOUNT=true ENV APP_AUTO_CREATE_ACCOUNT=true
ENV APP_SKIP_TUTORIAL=false ENV APP_SKIP_STORY_MODE_CHOICE=true
ENV APP_SKIP_ALL_DIALOGUE=false ENV APP_SKIP_TUTORIAL=true
ENV APP_UNLOCK_ALL_SCANS=false ENV APP_SKIP_ALL_DIALOGUE=true
ENV APP_UNLOCK_ALL_MISSIONS=false ENV APP_UNLOCK_ALL_SCANS=true
ENV APP_INFINITE_CREDITS=false ENV APP_UNLOCK_ALL_MISSIONS=true
ENV APP_INFINITE_PLATINUM=false ENV APP_UNLOCK_ALL_QUESTS=true
ENV APP_INFINITE_ENDO=false ENV APP_COMPLETE_ALL_QUESTS=true
ENV APP_INFINITE_REGAL_AYA=false ENV APP_INFINITE_RESOURCES=true
ENV APP_INFINITE_HELMINTH_MATERIALS=false ENV APP_UNLOCK_ALL_SHIP_FEATURES=true
ENV APP_DONT_SUBTRACT_CONSUMABLES=false ENV APP_UNLOCK_ALL_SHIP_DECORATIONS=true
ENV APP_UNLOCK_ALL_SHIP_FEATURES=false ENV APP_UNLOCK_ALL_FLAVOUR_ITEMS=true
ENV APP_UNLOCK_ALL_SHIP_DECORATIONS=false ENV APP_UNLOCK_ALL_SKINS=true
ENV APP_UNLOCK_ALL_FLAVOUR_ITEMS=false ENV APP_UNIVERSAL_POLARITY_EVERYWHERE=true
ENV APP_UNLOCK_ALL_SKINS=false
ENV APP_UNLOCK_ALL_CAPTURA_SCENES=false
ENV APP_UNIVERSAL_POLARITY_EVERYWHERE=false
ENV APP_UNLOCK_DOUBLE_CAPACITY_POTATOES_EVERYWHERE=false
ENV APP_UNLOCK_EXILUS_EVERYWHERE=false
ENV APP_UNLOCK_ARCANES_EVERYWHERE=false
ENV APP_NO_DAILY_FOCUS_LIMIT=false
ENV APP_NO_ARGON_CRYSTAL_DECAY=false
ENV APP_NO_MASTERY_RANK_UP_COOLDOWN=false
ENV APP_NO_VENDOR_PURCHASE_LIMITS=true
ENV APP_NO_DEATH_MARKS=false
ENV APP_NO_KIM_COOLDOWNS=false
ENV APP_INSTANT_RESOURCE_EXTRACTOR_DRONES=false
ENV APP_NO_RESOURCE_EXTRACTOR_DRONES_DAMAGE=false
ENV APP_SKIP_CLAN_KEY_CRAFTING=false
ENV APP_NO_DOJO_ROOM_BUILD_STAGE=false
ENV APP_NO_DECO_BUILD_STAGE=false
ENV APP_FAST_DOJO_ROOM_DESTRUCTION=false
ENV APP_NO_DOJO_RESEARCH_COSTS=false
ENV APP_NO_DOJO_RESEARCH_TIME=false
ENV APP_FAST_CLAN_ASCENSION=false
ENV APP_SPOOF_MASTERY_RANK=-1 ENV APP_SPOOF_MASTERY_RANK=-1
RUN apk add --no-cache bash sed wget jq RUN apk add --no-cache bash sed wget jq

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 ## 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`. - `logger.level` can be `fatal`, `error`, `warn`, `info`, `http`, `debug`, or `trace`.
- `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`. - `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`.
- `worldState.lockTime` will lock the time provided in worldState if nonzero, e.g. `1743202800` for night in POE.

View File

@ -1,6 +1,7 @@
@echo off @echo off
echo Updating SpaceNinjaServer... echo Updating SpaceNinjaServer...
git config remote.origin.url https://openwf.io/SpaceNinjaServer.git
git fetch --prune git fetch --prune
git stash git stash
git reset --hard origin/main git reset --hard origin/main

View File

@ -19,7 +19,6 @@
"infiniteEndo": false, "infiniteEndo": false,
"infiniteRegalAya": false, "infiniteRegalAya": false,
"infiniteHelminthMaterials": false, "infiniteHelminthMaterials": false,
"dontSubtractConsumables": false,
"unlockAllShipFeatures": false, "unlockAllShipFeatures": false,
"unlockAllShipDecorations": false, "unlockAllShipDecorations": false,
"unlockAllFlavourItems": false, "unlockAllFlavourItems": false,
@ -30,27 +29,19 @@
"unlockExilusEverywhere": false, "unlockExilusEverywhere": false,
"unlockArcanesEverywhere": false, "unlockArcanesEverywhere": false,
"noDailyStandingLimits": false, "noDailyStandingLimits": false,
"noDailyFocusLimit": false,
"noArgonCrystalDecay": false, "noArgonCrystalDecay": false,
"noMasteryRankUpCooldown": false,
"noVendorPurchaseLimits": true, "noVendorPurchaseLimits": true,
"noDeathMarks": false,
"noKimCooldowns": false,
"instantResourceExtractorDrones": false, "instantResourceExtractorDrones": false,
"noResourceExtractorDronesDamage": false,
"skipClanKeyCrafting": false,
"noDojoRoomBuildStage": false, "noDojoRoomBuildStage": false,
"noDecoBuildStage": false,
"fastDojoRoomDestruction": false, "fastDojoRoomDestruction": false,
"noDojoResearchCosts": false, "noDojoResearchCosts": false,
"noDojoResearchTime": false, "noDojoResearchTime": false,
"fastClanAscension": false, "fastClanAscension": false,
"spoofMasteryRank": -1, "spoofMasteryRank": -1,
"worldState": { "events": {
"creditBoost": false, "creditBoost": false,
"affinityBoost": false, "affinityBoost": false,
"resourceBoost": false, "resourceBoost": false,
"starDays": true, "starDays": true
"lockTime": 0
} }
} }

View File

@ -12,40 +12,19 @@ services:
# APP_HTTP_PORT: 80 # APP_HTTP_PORT: 80
# APP_HTTPS_PORT: 443 # APP_HTTPS_PORT: 443
# APP_AUTO_CREATE_ACCOUNT: true # APP_AUTO_CREATE_ACCOUNT: true
# APP_SKIP_TUTORIAL: false # APP_SKIP_STORY_MODE_CHOICE: true
# APP_SKIP_ALL_DIALOGUE: false # APP_SKIP_TUTORIAL: true
# APP_UNLOCK_ALL_SCANS: false # APP_SKIP_ALL_DIALOGUE: true
# APP_UNLOCK_ALL_MISSIONS: false # APP_UNLOCK_ALL_SCANS: true
# APP_INFINITE_CREDITS: false # APP_UNLOCK_ALL_MISSIONS: true
# APP_INFINITE_PLATINUM: false # APP_UNLOCK_ALL_QUESTS: true
# APP_INFINITE_ENDO: false # APP_COMPLETE_ALL_QUESTS: true
# APP_INFINITE_REGAL_AYA: false # APP_INFINITE_RESOURCES: true
# APP_INFINITE_HELMINTH_MATERIALS: false # APP_UNLOCK_ALL_SHIP_FEATURES: true
# APP_DONT_SUBTRACT_CONSUMABLES: false # APP_UNLOCK_ALL_SHIP_DECORATIONS: true
# APP_UNLOCK_ALL_SHIP_FEATURES: false # APP_UNLOCK_ALL_FLAVOUR_ITEMS: true
# APP_UNLOCK_ALL_SHIP_DECORATIONS: false # APP_UNLOCK_ALL_SKINS: true
# APP_UNLOCK_ALL_FLAVOUR_ITEMS: false # APP_UNIVERSAL_POLARITY_EVERYWHERE: true
# APP_UNLOCK_ALL_SKINS: false
# APP_UNLOCK_ALL_CAPTURA_SCENES: false
# APP_UNIVERSAL_POLARITY_EVERYWHERE: false
# APP_UNLOCK_DOUBLE_CAPACITY_POTATOES_EVERYWHERE: false
# APP_UNLOCK_EXILUS_EVERYWHERE: false
# APP_UNLOCK_ARCANES_EVERYWHERE: false
# APP_NO_DAILY_FOCUS_LIMIT: false
# APP_NO_ARGON_CRYSTAL_DECAY: false
# APP_NO_MASTERY_RANK_UP_COOLDOWN: false
# APP_NO_VENDOR_PURCHASE_LIMITS: true
# APP_NO_DEATH_MARKS: false
# APP_NO_KIM_COOLDOWNS: false
# APP_INSTANT_RESOURCE_EXTRACTOR_DRONES: false
# APP_NO_RESOURCE_EXTRACTOR_DRONES_DAMAGE: false
# APP_SKIP_CLAN_KEY_CRAFTING: false
# APP_NO_DOJO_ROOM_BUILD_STAGE: false
# APP_NO_DECO_BUILD_STAGE: false
# APP_FAST_DOJO_ROOM_DESTRUCTION: false
# APP_NO_DOJO_RESEARCH_COSTS: false
# APP_NO_DOJO_RESEARCH_TIME: false
# APP_FAST_CLAN_ASCENSION: false
# APP_SPOOF_MASTERY_RANK: -1 # APP_SPOOF_MASTERY_RANK: -1
volumes: volumes:
- ./docker-data/static:/app/static/data - ./docker-data/static:/app/static/data

View File

@ -19,6 +19,5 @@ do
mv config.tmp config.json mv config.tmp config.json
done done
npm i --omit=dev npm install
npm run build exec npm run dev
exec npm run start

602
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -16,24 +16,17 @@ import { webuiRouter } from "@/src/routes/webui";
const app = express(); const app = express();
app.use((req, _res, next) => { app.use((req, _res, next) => {
// 38.5.0 introduced "ezip" for encrypted body blobs and "e" for request verification only (encrypted body blobs with no application data). // 38.5.0 introduced "ezip" for encrypted body blobs.
// The bootstrapper decrypts it for us but having an unsupported Content-Encoding here would still be an issue for Express, so removing it. // The bootstrapper decrypts it for us but having an unsupported Content-Encoding here would still be an issue for Express, so removing it.
if (req.headers["content-encoding"] == "ezip" || req.headers["content-encoding"] == "e") { if (req.headers["content-encoding"] == "ezip") {
req.headers["content-encoding"] = undefined; req.headers["content-encoding"] = undefined;
} }
// U18 uses application/x-www-form-urlencoded even tho the data is JSON which Express doesn't like.
// U17 sets no Content-Type at all, which Express also doesn't like.
if (!req.headers["content-type"] || req.headers["content-type"] == "application/x-www-form-urlencoded") {
req.headers["content-type"] = "application/octet-stream";
}
next(); next();
}); });
app.use(bodyParser.raw()); app.use(bodyParser.raw());
app.use(express.json({ limit: "4mb" })); app.use(express.json({ limit: "4mb" }));
app.use(bodyParser.text({ limit: "4mb" })); app.use(bodyParser.text());
app.use(requestLogger); app.use(requestLogger);
app.use("/api", apiRouter); app.use("/api", apiRouter);

View File

@ -2,18 +2,15 @@ const millisecondsPerSecond = 1000;
const secondsPerMinute = 60; const secondsPerMinute = 60;
const minutesPerHour = 60; const minutesPerHour = 60;
const hoursPerDay = 24; const hoursPerDay = 24;
const daysPerWeek = 7;
const unixSecond = millisecondsPerSecond; const unixSecond = millisecondsPerSecond;
const unixMinute = secondsPerMinute * millisecondsPerSecond; const unixMinute = secondsPerMinute * millisecondsPerSecond;
const unixHour = unixMinute * minutesPerHour; const unixHour = unixMinute * minutesPerHour;
const unixDay = hoursPerDay * unixHour; const unixDay = hoursPerDay * unixHour;
const unixWeek = daysPerWeek * unixDay;
export const unixTimesInMs = { export const unixTimesInMs = {
second: unixSecond, second: unixSecond,
minute: unixMinute, minute: unixMinute,
hour: unixHour, hour: unixHour,
day: unixDay, day: unixDay
week: unixWeek
}; };

View File

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

View File

@ -17,7 +17,7 @@ export const activateRandomModController: RequestHandler = async (req, res) => {
ItemCount: -1 ItemCount: -1
} }
]); ]);
const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType])!; const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType]);
const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]); const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
const upgradeIndex = const upgradeIndex =
inventory.Upgrades.push({ inventory.Upgrades.push({

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,52 +3,45 @@ import { Account } from "@/src/models/loginModel";
import { fillInInventoryDataForGuildMember, hasGuildPermission } from "@/src/services/guildService"; import { fillInInventoryDataForGuildMember, hasGuildPermission } from "@/src/services/guildService";
import { createMessage } from "@/src/services/inboxService"; import { createMessage } from "@/src/services/inboxService";
import { getInventory } from "@/src/services/inventoryService"; 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 { IOid } from "@/src/types/commonTypes";
import { GuildPermission, IGuildMemberClient } from "@/src/types/guildTypes"; import { GuildPermission, IGuildMemberClient } from "@/src/types/guildTypes";
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { ExportFlavour } from "warframe-public-export-plus"; import { ExportFlavour } from "warframe-public-export-plus";
export const addToGuildController: RequestHandler = async (req, res) => { export const addToGuildController: RequestHandler = async (req, res) => {
const payload = JSON.parse(String(req.body)) as IAddToGuildRequest; 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 }); const account = await Account.findOne({ DisplayName: payload.UserName });
if (!account) { if (!account) {
res.status(400).json("Username does not exist"); res.status(400).json("Username does not exist");
return; return;
} }
const inventory = await getInventory(account._id.toString(), "Settings"); const guild = (await Guild.findById(payload.GuildId.$oid, "Name"))!;
// TODO: Also consider GIFT_MODE_FRIENDS once friends are implemented
if (inventory.Settings?.GuildInvRestriction == "GIFT_MODE_NONE") {
res.status(400).json("Invite restricted");
return;
}
const guild = (await Guild.findById(payload.GuildId.$oid, "Name Ranks"))!;
const senderAccount = await getAccountForRequest(req); const senderAccount = await getAccountForRequest(req);
if (!(await hasGuildPermission(guild, senderAccount._id.toString(), GuildPermission.Recruiter))) { if (!(await hasGuildPermission(guild, senderAccount._id.toString(), GuildPermission.Recruiter))) {
res.status(400).json("Invalid permission"); res.status(400).json("Invalid permission");
} }
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({ await GuildMember.insertOne({
accountId: account._id, accountId: account._id,
guildId: payload.GuildId.$oid, guildId: payload.GuildId.$oid,
status: 2 // outgoing invite 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"); const senderInventory = await getInventory(senderAccount._id.toString(), "ActiveAvatarImageType");
await createMessage(account._id, [ await createMessage(account._id.toString(), [
{ {
sndr: getSuffixedName(senderAccount), sndr: getSuffixedName(senderAccount),
msg: "/Lotus/Language/Menu/Mailbox_ClanInvite_Body", msg: "/Lotus/Language/Menu/Mailbox_ClanInvite_Body",
@ -76,30 +69,9 @@ export const addToGuildController: RequestHandler = async (req, res) => {
}; };
await fillInInventoryDataForGuildMember(member); await fillInInventoryDataForGuildMember(member);
res.json({ NewMember: 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 { interface IAddToGuildRequest {
UserName?: string; UserName: string;
GuildId: IOid; GuildId: IOid;
RequestMsg?: string;
} }

View File

@ -28,7 +28,7 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
}); });
const rawRivenType = getRandomRawRivenType(); const rawRivenType = getRandomRawRivenType();
const rivenType = getRandomElement(rivenRawToRealWeighted[rawRivenType])!; const rivenType = getRandomElement(rivenRawToRealWeighted[rawRivenType]);
const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]); const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
const upgradeIndex = const upgradeIndex =
@ -57,16 +57,12 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
payload.Consumed.forEach(upgrade => { payload.Consumed.forEach(upgrade => {
const meta = ExportUpgrades[upgrade.ItemType]; const meta = ExportUpgrades[upgrade.ItemType];
counts[meta.rarity] += upgrade.ItemCount; counts[meta.rarity] += upgrade.ItemCount;
if (upgrade.ItemId.$oid != "000000000000000000000000") {
inventory.Upgrades.pull({ _id: upgrade.ItemId.$oid });
} else {
addMods(inventory, [ addMods(inventory, [
{ {
ItemType: upgrade.ItemType, ItemType: upgrade.ItemType,
ItemCount: upgrade.ItemCount * -1 ItemCount: upgrade.ItemCount * -1
} }
]); ]);
}
if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/AttackTransmuteCore") { if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/AttackTransmuteCore") {
forcedPolarity = "AP_ATTACK"; forcedPolarity = "AP_ATTACK";
} else if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/DefenseTransmuteCore") { } 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 // Based on the table on https://wiki.warframe.com/w/Transmutation
const weights: Record<TRarity, number> = { const weights: Record<TRarity, number> = {
COMMON: counts.COMMON * 95 + counts.UNCOMMON * 15 + counts.RARE * 4, 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, [ addMods(inventory, [
{ {
ItemType: newModType, ItemType: newModType,
@ -145,34 +130,3 @@ interface IAgnosticUpgradeClient {
ItemCount: number; ItemCount: number;
LastAdded: IOid; 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; 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> = {}; const idToNode: Record<string, INode> = {};
guild.DojoComponents.forEach(x => { guild.DojoComponents.forEach(x => {
idToNode[x._id.toString()] = { idToNode[x._id.toString()] = {
@ -49,13 +43,23 @@ export const changeDojoRootController: RequestHandler = async (req, res) => {
newRoot.component.pp = undefined; newRoot.component.pp = undefined;
newRoot.parent = 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]; const stack: INode[] = [newRoot];
let i = 0;
const idMap: Record<string, Types.ObjectId> = {};
while (stack.length != 0) { while (stack.length != 0) {
const top = stack.shift()!; 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)); 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)); logger.debug("New tree:\n" + treeToString(newRoot));

View File

@ -11,16 +11,15 @@ import {
getInventory, getInventory,
updateCurrency, updateCurrency,
addItem, addItem,
addMiscItems,
addRecipes, addRecipes,
occupySlot, occupySlot
combineInventoryChanges
} from "@/src/services/inventoryService"; } from "@/src/services/inventoryService";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes"; import { IMiscItem, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
import { toOid } from "@/src/helpers/inventoryHelpers";
interface IClaimCompletedRecipeRequest { export interface IClaimCompletedRecipeRequest {
RecipeIds: IOid[]; RecipeIds: IOid[];
} }
@ -52,14 +51,14 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
...updateCurrency(inventory, recipe.buildPrice * -1, false) ...updateCurrency(inventory, recipe.buildPrice * -1, false)
}; };
const equipmentIngredients = new Set(); const nonMiscItemIngredients = new Set();
for (const category of ["LongGuns", "Pistols", "Melee"] as const) { for (const category of ["LongGuns", "Pistols", "Melee"] as const) {
if (pendingRecipe[category]) { if (pendingRecipe[category]) {
pendingRecipe[category].forEach(item => { pendingRecipe[category].forEach(item => {
const index = inventory[category].push(item) - 1; const index = inventory[category].push(item) - 1;
inventoryChanges[category] ??= []; inventoryChanges[category] ??= [];
inventoryChanges[category].push(inventory[category][index].toJSON<IEquipmentClient>()); inventoryChanges[category].push(inventory[category][index].toJSON<IEquipmentClient>());
equipmentIngredients.add(item.ItemType); nonMiscItemIngredients.add(item.ItemType);
occupySlot(inventory, InventorySlot.WEAPONS, false); occupySlot(inventory, InventorySlot.WEAPONS, false);
inventoryChanges.WeaponBin ??= { Slots: 0 }; inventoryChanges.WeaponBin ??= { Slots: 0 };
@ -67,21 +66,20 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
}); });
} }
} }
for (const ingredient of recipe.ingredients) { const miscItemChanges: IMiscItem[] = [];
if (!equipmentIngredients.has(ingredient.ItemType)) { recipe.ingredients.forEach(ingredient => {
combineInventoryChanges( if (!nonMiscItemIngredients.has(ingredient.ItemType)) {
inventoryChanges, miscItemChanges.push(ingredient);
await addItem(inventory, ingredient.ItemType, ingredient.ItemCount)
);
}
} }
});
addMiscItems(inventory, miscItemChanges);
inventoryChanges.MiscItems = miscItemChanges;
await inventory.save(); await inventory.save();
res.json(inventoryChanges); // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root. res.json(inventoryChanges); // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
} else { } else {
logger.debug("Claiming Recipe", { recipe, pendingRecipe }); logger.debug("Claiming Recipe", { recipe, pendingRecipe });
let BrandedSuits: undefined | IOid[];
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") { if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
inventory.PendingSpectreLoadouts ??= []; inventory.PendingSpectreLoadouts ??= [];
inventory.SpectreLoadouts ??= []; inventory.SpectreLoadouts ??= [];
@ -101,15 +99,9 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
inventory.SpectreLoadouts.push(inventory.PendingSpectreLoadouts[pendingLoadoutIndex]); inventory.SpectreLoadouts.push(inventory.PendingSpectreLoadouts[pendingLoadoutIndex]);
inventory.PendingSpectreLoadouts.splice(pendingLoadoutIndex, 1); inventory.PendingSpectreLoadouts.splice(pendingLoadoutIndex, 1);
} }
} else if (recipe.secretIngredientAction == "SIA_UNBRAND") {
inventory.BrandedSuits!.splice(
inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)),
1
);
BrandedSuits = [toOid(pendingRecipe.SuitToUnbrand!)];
} }
let InventoryChanges: IInventoryChanges = {}; let InventoryChanges = {};
if (recipe.consumeOnUse) { if (recipe.consumeOnUse) {
addRecipes(inventory, [ addRecipes(inventory, [
{ {
@ -119,31 +111,16 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
]); ]);
} }
if (req.query.rush) { if (req.query.rush) {
const end = Math.trunc(pendingRecipe.CompletionDate.getTime() / 1000);
const start = end - recipe.buildTime;
const secondsElapsed = Math.trunc(Date.now() / 1000) - start;
const progress = secondsElapsed / recipe.buildTime;
logger.debug(`rushing recipe at ${Math.trunc(progress * 100)}% completion`);
const cost = Math.round(recipe.skipBuildTimePrice * (1 - (progress - 0.5)));
InventoryChanges = { InventoryChanges = {
...InventoryChanges, ...InventoryChanges,
...updateCurrency(inventory, cost, true) ...updateCurrency(inventory, recipe.skipBuildTimePrice, true)
}; };
} }
if (recipe.secretIngredientAction != "SIA_UNBRAND") {
InventoryChanges = { InventoryChanges = {
...InventoryChanges, ...InventoryChanges,
...(await addItem( ...(await addItem(inventory, recipe.resultType, recipe.num, false))
inventory,
recipe.resultType,
recipe.num,
false,
undefined,
pendingRecipe.TargetFingerprint
))
}; };
}
await inventory.save(); await inventory.save();
res.json({ InventoryChanges, BrandedSuits }); res.json({ InventoryChanges });
} }
}; };

View File

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

View File

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

View File

@ -1,41 +0,0 @@
import { getCalendarProgress, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { getWorldState } from "@/src/services/worldStateService";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { RequestHandler } from "express";
// GET request; query parameters: CompletedEventIdx=0&Iteration=4&Version=19&Season=CST_SUMMER
export const completeCalendarEventController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const calendarProgress = getCalendarProgress(inventory);
const currentSeason = getWorldState().KnownCalendarSeasons[0];
let inventoryChanges: IInventoryChanges = {};
let dayIndex = 0;
for (const day of currentSeason.Days) {
if (day.events.length == 0 || day.events[0].type != "CET_CHALLENGE") {
if (dayIndex == calendarProgress.SeasonProgress.LastCompletedDayIdx) {
if (day.events.length != 0) {
const selection = day.events[parseInt(req.query.CompletedEventIdx as string)];
if (selection.type == "CET_REWARD") {
inventoryChanges = (await handleStoreItemAcquisition(selection.reward!, inventory))
.InventoryChanges;
} else if (selection.type == "CET_UPGRADE") {
calendarProgress.YearProgress.Upgrades.push(selection.upgrade!);
} else if (selection.type != "CET_PLOT") {
throw new Error(`unexpected selection type: ${selection.type}`);
}
}
break;
}
++dayIndex;
}
}
calendarProgress.SeasonProgress.LastCompletedDayIdx++;
await inventory.save();
res.json({
InventoryChanges: inventoryChanges,
CalendarProgress: inventory.CalendarProgress
});
};

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,58 +1,26 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Guild, GuildMember } from "@/src/models/guildModel"; import { Guild, GuildMember } from "@/src/models/guildModel";
import { Account } from "@/src/models/loginModel"; import { getGuildClient, updateInventoryForConfirmedGuildJoin } from "@/src/services/guildService";
import { import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
deleteGuild,
getGuildClient,
giveClanKey,
hasGuildPermission,
removeDojoKeyItems
} from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountForRequest, getAccountIdForRequest, getSuffixedName } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
// GET request: A player accepting an invite they got in their inbox. export const confirmGuildInvitationController: RequestHandler = async (req, res) => {
export const confirmGuildInvitationGetController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req); const account = await getAccountForRequest(req);
const invitedGuildMember = await GuildMember.findOne({ const guildMember = await GuildMember.findOne({
accountId: account._id, accountId: account._id,
guildId: req.query.clanId as string 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) { if (guildMember) {
const inventory = await getInventory(account._id.toString(), "LevelKeys Recipes"); guildMember.status = 0;
inventoryChanges = removeDojoKeyItems(inventory); await guildMember.save();
await inventory.save();
if (guildMember.rank == 0) { await updateInventoryForConfirmedGuildJoin(
await deleteGuild(guildMember.guildId); account._id.toString(),
} new Types.ObjectId(req.query.clanId as string)
} );
// 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);
giveClanKey(inventory, inventoryChanges);
await inventory.save();
const guild = (await Guild.findById(req.query.clanId as string))!; const guild = (await Guild.findById(req.query.clanId as string))!;
// Add join to clan log
guild.RosterActivity ??= []; guild.RosterActivity ??= [];
guild.RosterActivity.push({ guild.RosterActivity.push({
dateTime: new Date(), dateTime: new Date(),
@ -63,56 +31,16 @@ export const confirmGuildInvitationGetController: RequestHandler = async (req, r
res.json({ res.json({
...(await getGuildClient(guild, account._id.toString())), ...(await getGuildClient(guild, account._id.toString())),
InventoryChanges: inventoryChanges InventoryChanges: {
Recipes: [
{
ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
ItemCount: 1
}
]
}
}); });
} else { } else {
res.end(); 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 LevelKeys Recipes");
inventory.GuildId = new Types.ObjectId(req.query.clanId as string);
giveClanKey(inventory);
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,8 +1,9 @@
import { toMongoDate } from "@/src/helpers/inventoryHelpers"; import { toMongoDate } from "@/src/helpers/inventoryHelpers";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Guild } from "@/src/models/guildModel"; import { Guild, GuildMember } from "@/src/models/guildModel";
import { checkClanAscensionHasRequiredContributors } from "@/src/services/guildService"; import { config } from "@/src/services/configService";
import { addFusionPoints, getInventory } from "@/src/services/inventoryService"; import { createMessage } from "@/src/services/inboxService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
@ -30,13 +31,49 @@ export const contributeGuildClassController: RequestHandler = async (req, res) =
guild.CeremonyContributors.push(new Types.ObjectId(accountId)); 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(); await guild.save();
// Either way, endo is given to the contributor. // Either way, endo is given to the contributor.
const inventory = await getInventory(accountId, "FusionPoints"); const inventory = await getInventory(accountId, "FusionPoints");
addFusionPoints(inventory, guild.CeremonyEndo!); inventory.FusionPoints += guild.CeremonyEndo!;
await inventory.save(); await inventory.save();
res.json({ res.json({

View File

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

View File

@ -1,104 +1,56 @@
import { import { GuildMember } from "@/src/models/guildModel";
Alliance, import { getGuildForRequestEx } from "@/src/services/guildService";
Guild, import { addFusionTreasures, addMiscItems, addShipDecorations, getInventory } from "@/src/services/inventoryService";
GuildMember,
TGuildDatabaseDocument,
TGuildMemberDatabaseDocument
} from "@/src/models/guildModel";
import {
addGuildMemberMiscItemContribution,
addGuildMemberShipDecoContribution,
addVaultFusionTreasures,
addVaultMiscItems,
addVaultShipDecos,
getGuildForRequestEx
} from "@/src/services/guildService";
import {
addFusionTreasures,
addMiscItems,
addShipDecorations,
getInventory,
updateCurrency
} from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { IFusionTreasure, IMiscItem, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes"; import { IFusionTreasure, IMiscItem, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
export const contributeToVaultController: RequestHandler = async (req, res) => { export const contributeToVaultController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "GuildId RegularCredits MiscItems ShipDecorations FusionTreasures"); const inventory = await getInventory(accountId);
const request = JSON.parse(String(req.body)) as IContributeToVaultRequest;
if (request.Alliance) {
const guild = await getGuildForRequestEx(req, inventory); const guild = await getGuildForRequestEx(req, inventory);
const alliance = (await Alliance.findById(guild.AllianceId!))!; const guildMember = (await GuildMember.findOne(
alliance.VaultRegularCredits ??= 0;
alliance.VaultRegularCredits += request.RegularCredits;
if (request.FromVault) {
guild.VaultRegularCredits! -= request.RegularCredits;
await Promise.all([guild.save(), alliance.save()]);
} else {
updateCurrency(inventory, request.RegularCredits, false);
await Promise.all([inventory.save(), alliance.save()]);
}
res.end();
return;
}
let guild: TGuildDatabaseDocument;
let guildMember: TGuildMemberDatabaseDocument | undefined;
if (request.GuildVault) {
guild = (await Guild.findById(request.GuildVault))!;
} else {
guild = await getGuildForRequestEx(req, inventory);
guildMember = (await GuildMember.findOne(
{ accountId, guildId: guild._id }, { accountId, guildId: guild._id },
"RegularCreditsContributed MiscItemsContributed ShipDecorationsContributed" "RegularCreditsContributed MiscItemsContributed ShipDecorationsContributed"
))!; ))!;
} const request = JSON.parse(String(req.body)) as IContributeToVaultRequest;
if (request.RegularCredits) { if (request.RegularCredits) {
updateCurrency(inventory, request.RegularCredits, false);
guild.VaultRegularCredits ??= 0; guild.VaultRegularCredits ??= 0;
guild.VaultRegularCredits += request.RegularCredits; guild.VaultRegularCredits += request.RegularCredits;
if (guildMember) {
guildMember.RegularCreditsContributed ??= 0; guildMember.RegularCreditsContributed ??= 0;
guildMember.RegularCreditsContributed += request.RegularCredits; guildMember.RegularCreditsContributed += request.RegularCredits;
} }
}
if (request.MiscItems.length) { if (request.MiscItems.length) {
addVaultMiscItems(guild, request.MiscItems); guild.VaultMiscItems ??= [];
guildMember.MiscItemsContributed ??= [];
for (const item of request.MiscItems) { for (const item of request.MiscItems) {
if (guildMember) { guild.VaultMiscItems.push(item);
addGuildMemberMiscItemContribution(guildMember, item); guildMember.MiscItemsContributed.push(item);
}
addMiscItems(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]); addMiscItems(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
} }
} }
if (request.ShipDecorations.length) { if (request.ShipDecorations.length) {
addVaultShipDecos(guild, request.ShipDecorations); guild.VaultShipDecorations ??= [];
guildMember.ShipDecorationsContributed ??= [];
for (const item of request.ShipDecorations) { for (const item of request.ShipDecorations) {
if (guildMember) { guild.VaultShipDecorations.push(item);
addGuildMemberShipDecoContribution(guildMember, item); guildMember.ShipDecorationsContributed.push(item);
}
addShipDecorations(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]); addShipDecorations(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
} }
} }
if (request.FusionTreasures.length) { if (request.FusionTreasures.length) {
addVaultFusionTreasures(guild, request.FusionTreasures); guild.VaultFusionTreasures ??= [];
for (const item of request.FusionTreasures) { for (const item of request.FusionTreasures) {
guild.VaultFusionTreasures.push(item);
addFusionTreasures(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]); addFusionTreasures(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
} }
} }
const promises: Promise<unknown>[] = [guild.save(), inventory.save()]; await guild.save();
if (guildMember) { await inventory.save();
promises.push(guildMember.save()); await guildMember.save();
}
await Promise.all(promises);
res.end(); res.end();
}; };
@ -107,7 +59,4 @@ interface IContributeToVaultRequest {
MiscItems: IMiscItem[]; MiscItems: IMiscItem[];
ShipDecorations: ITypeCount[]; ShipDecorations: ITypeCount[];
FusionTreasures: IFusionTreasure[]; 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,17 +2,16 @@ import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Guild, GuildMember } from "@/src/models/guildModel"; import { Guild, GuildMember } from "@/src/models/guildModel";
import { createUniqueClanName, getGuildClient, giveClanKey } from "@/src/services/guildService"; import {
import { getInventory } from "@/src/services/inventoryService"; createUniqueClanName,
import { IInventoryChanges } from "@/src/types/purchaseTypes"; getGuildClient,
updateInventoryForConfirmedGuildJoin
} from "@/src/services/guildService";
export const createGuildController: RequestHandler = async (req, res) => { export const createGuildController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const payload = getJSONfromString<ICreateGuildRequest>(String(req.body)); const payload = getJSONfromString<ICreateGuildRequest>(String(req.body));
// Remove pending applications for this account
await GuildMember.deleteMany({ accountId, status: 1 });
// Create guild on database // Create guild on database
const guild = new Guild({ const guild = new Guild({
Name: await createUniqueClanName(payload.guildName) Name: await createUniqueClanName(payload.guildName)
@ -27,16 +26,9 @@ export const createGuildController: RequestHandler = async (req, res) => {
rank: 0 rank: 0
}); });
const inventory = await getInventory(accountId, "GuildId LevelKeys Recipes"); await updateInventoryForConfirmedGuildJoin(accountId, guild._id);
inventory.GuildId = guild._id;
const inventoryChanges: IInventoryChanges = {};
giveClanKey(inventory, inventoryChanges);
await inventory.save();
res.json({ res.json(await getGuildClient(guild, accountId));
...(await getGuildClient(guild, accountId)),
InventoryChanges: inventoryChanges
});
}; };
interface ICreateGuildRequest { interface ICreateGuildRequest {

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

View File

@ -1,11 +1,5 @@
import { RequestHandler } from "express"; 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) => { export const dojoController: RequestHandler = (_req, res) => {
res.json("-1"); // Tell client to use authorised request. 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()
: new Date(Date.now() + getRandomInt(3 * 3600 * 1000, 4 * 3600 * 1000)); : new Date(Date.now() + getRandomInt(3 * 3600 * 1000, 4 * 3600 * 1000));
drone.PendingDamage = drone.PendingDamage =
!config.noResourceExtractorDronesDamage && Math.random() < system.damageChance Math.random() < system.damageChance
? getRandomInt(system.droneDamage.minValue, system.droneDamage.maxValue) ? getRandomInt(system.droneDamage.minValue, system.droneDamage.maxValue)
: 0; : 0;
const resource = getRandomWeightedRewardUc(system.resources, droneMeta.probabilities)!; const resource = getRandomWeightedRewardUc(system.resources, droneMeta.probabilities)!;

View File

@ -21,12 +21,10 @@ export const entratiLabConquestModeController: RequestHandler = async (req, res)
inventory.EntratiVaultCountResetDate = new Date(weekEnd); inventory.EntratiVaultCountResetDate = new Date(weekEnd);
if (inventory.EntratiLabConquestUnlocked) { if (inventory.EntratiLabConquestUnlocked) {
inventory.EntratiLabConquestUnlocked = 0; inventory.EntratiLabConquestUnlocked = 0;
inventory.EntratiLabConquestCacheScoreMission = 0;
inventory.EntratiLabConquestActiveFrameVariants = []; inventory.EntratiLabConquestActiveFrameVariants = [];
} }
if (inventory.EchoesHexConquestUnlocked) { if (inventory.EchoesHexConquestUnlocked) {
inventory.EchoesHexConquestUnlocked = 0; inventory.EchoesHexConquestUnlocked = 0;
inventory.EchoesHexConquestCacheScoreMission = 0;
inventory.EchoesHexConquestActiveFrameVariants = []; inventory.EchoesHexConquestActiveFrameVariants = [];
inventory.EchoesHexConquestActiveStickers = []; inventory.EchoesHexConquestActiveStickers = [];
} }

View File

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

View File

@ -1,9 +1,10 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers"; 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 { getAccountIdForRequest } from "@/src/services/loginService";
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes"; import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import { RequestHandler } from "express"; 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) => { export const fishmongerController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); 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 }); miscItemChanges.push({ ItemType: fish.ItemType, ItemCount: fish.ItemCount * -1 });
} }
addMiscItems(inventory, miscItemChanges); addMiscItems(inventory, miscItemChanges);
let affiliationMod; if (gainedStanding && syndicateTag) {
if (gainedStanding && syndicateTag) affiliationMod = addStanding(inventory, syndicateTag, gainedStanding); 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(); await inventory.save();
res.json({ res.json({
InventoryChanges: { InventoryChanges: {
MiscItems: miscItemChanges MiscItems: miscItemChanges
}, },
SyndicateTag: syndicateTag, SyndicateTag: syndicateTag,
StandingChange: affiliationMod?.Standing || 0 StandingChange: gainedStanding
}); });
}; };

View File

@ -18,8 +18,8 @@ export const focusController: RequestHandler = async (req, res) => {
case FocusOperation.InstallLens: { case FocusOperation.InstallLens: {
const request = JSON.parse(String(req.body)) as ILensInstallRequest; const request = JSON.parse(String(req.body)) as ILensInstallRequest;
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
const item = inventory[request.Category].id(request.WeaponId); for (const item of inventory[request.Category]) {
if (item) { if (item._id.toString() == request.WeaponId) {
item.FocusLens = request.LensType; item.FocusLens = request.LensType;
addMiscItems(inventory, [ addMiscItems(inventory, [
{ {
@ -27,6 +27,8 @@ export const focusController: RequestHandler = async (req, res) => {
ItemCount: -1 ItemCount: -1
} satisfies IMiscItem } satisfies IMiscItem
]); ]);
break;
}
} }
await inventory.save(); await inventory.save();
res.json({ res.json({
@ -104,14 +106,13 @@ export const focusController: RequestHandler = async (req, res) => {
} }
case FocusOperation.SentTrainingAmplifier: { case FocusOperation.SentTrainingAmplifier: {
const request = JSON.parse(String(req.body)) as ISentTrainingAmplifierRequest; const request = JSON.parse(String(req.body)) as ISentTrainingAmplifierRequest;
const inventory = await getInventory(accountId); const parts: string[] = [
const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, {
ModularParts: [
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingGrip", "/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingGrip",
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingChassis", "/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingChassis",
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingBarrel" "/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingBarrel"
] ];
}); const inventory = await getInventory(accountId);
const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, parts);
occupySlot(inventory, InventorySlot.AMPS, false); occupySlot(inventory, InventorySlot.AMPS, false);
await inventory.save(); await inventory.save();
res.json((inventoryChanges.OperatorAmps as IEquipmentClient[])[0]); res.json((inventoryChanges.OperatorAmps as IEquipmentClient[])[0]);

View File

@ -1,84 +0,0 @@
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { addMiscItem, getInventory } from "@/src/services/inventoryService";
import { toStoreItem } from "@/src/services/itemDataService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
import { IMongoDate } from "@/src/types/commonTypes";
import { IMissionReward } from "@/src/types/missionTypes";
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { IGardeningClient } from "@/src/types/shipTypes";
import { RequestHandler } from "express";
import { dict_en, ExportResources } from "warframe-public-export-plus";
export const gardeningController: RequestHandler = async (req, res) => {
const data = getJSONfromString<IGardeningRequest>(String(req.body));
if (data.Mode != "HarvestAll") {
throw new Error(`unexpected gardening mode: ${data.Mode}`);
}
const accountId = await getAccountIdForRequest(req);
const [inventory, personalRooms] = await Promise.all([
getInventory(accountId, "MiscItems"),
getPersonalRooms(accountId, "Apartment")
]);
// Harvest plants
const inventoryChanges: IInventoryChanges = {};
const rewards: Record<string, IMissionReward[][]> = {};
for (const planter of personalRooms.Apartment.Gardening.Planters) {
rewards[planter.Name] = [];
for (const plant of planter.Plants) {
const itemType =
"/Lotus/Types/Gameplay/Duviri/Resource/DuviriPlantItem" +
plant.PlantType.substring(plant.PlantType.length - 1);
const itemCount = Math.random() < 0.775 ? 2 : 4;
addMiscItem(inventory, itemType, itemCount, inventoryChanges);
rewards[planter.Name].push([
{
StoreItem: toStoreItem(itemType),
TypeName: itemType,
ItemCount: itemCount,
DailyCooldown: false,
Rarity: itemCount == 2 ? 0.7743589743589744 : 0.22564102564102564,
TweetText: `${itemCount}x ${dict_en[ExportResources[itemType].name]} (Resource)`,
ProductCategory: "MiscItems"
}
]);
}
}
// Refresh garden
personalRooms.Apartment.Gardening = createGarden();
await Promise.all([inventory.save(), personalRooms.save()]);
const planter = personalRooms.Apartment.Gardening.Planters[personalRooms.Apartment.Gardening.Planters.length - 1];
const plant = planter.Plants[planter.Plants.length - 1];
res.json({
GardenTagName: planter.Name,
PlantType: plant.PlantType,
PlotIndex: plant.PlotIndex,
EndTime: toMongoDate(plant.EndTime),
InventoryChanges: inventoryChanges,
Gardening: personalRooms.toJSON<IPersonalRoomsClient>().Apartment.Gardening,
Rewards: rewards
} satisfies IGardeningResponse);
};
interface IGardeningRequest {
Mode: string;
}
interface IGardeningResponse {
GardenTagName: string;
PlantType: string;
PlotIndex: number;
EndTime: IMongoDate;
InventoryChanges: IInventoryChanges;
Gardening: IGardeningClient;
Rewards: Record<string, IMissionReward[][]>;
}

View File

@ -1,26 +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"; import { RequestHandler } from "express";
export const getAllianceController: RequestHandler = async (req, res) => { const getAllianceController: RequestHandler = (_req, res) => {
const accountId = await getAccountIdForRequest(req); res.sendStatus(200);
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();
}; };
// POST request since U27 export { getAllianceController };
/*interface IGetAllianceRequest {
memberCount: number;
clanLeaderName: string;
clanLeaderId: string;
}*/

View File

@ -1,7 +1,6 @@
import { GuildMember } from "@/src/models/guildModel"; import { GuildMember } from "@/src/models/guildModel";
import { getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { IGuildMemberClient } from "@/src/types/guildTypes";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
export const getGuildContributionsController: RequestHandler = async (req, res) => { 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 guildId = (await getInventory(accountId, "GuildId")).GuildId;
const guildMember = (await GuildMember.findOne({ guildId, accountId: req.query.buddyId }))!; const guildMember = (await GuildMember.findOne({ guildId, accountId: req.query.buddyId }))!;
res.json({ res.json({
_id: { $oid: req.query.buddyId as string }, _id: { $oid: req.query.buddyId },
RegularCreditsContributed: guildMember.RegularCreditsContributed, RegularCreditsContributed: guildMember.RegularCreditsContributed,
PremiumCreditsContributed: guildMember.PremiumCreditsContributed, PremiumCreditsContributed: guildMember.PremiumCreditsContributed,
MiscItemsContributed: guildMember.MiscItemsContributed, MiscItemsContributed: guildMember.MiscItemsContributed,
ConsumablesContributed: [], // ??? ConsumablesContributed: [], // ???
ShipDecorationsContributed: guildMember.ShipDecorationsContributed 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 { getInventory } from "@/src/services/inventoryService";
import { createUniqueClanName, getGuildClient } from "@/src/services/guildService"; 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 accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "GuildId"); const inventory = await getInventory(accountId, "GuildId");
if (inventory.GuildId) { if (inventory.GuildId) {
@ -28,5 +28,7 @@ export const getGuildController: RequestHandler = async (req, res) => {
return; 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"; import { RequestHandler } from "express";
export const getIgnoredUsersController: RequestHandler = async (req, res) => { const getIgnoredUsersController: RequestHandler = (_req, res) => {
const accountId = await getAccountIdForRequest(req); res.writeHead(200, {
const ignores = await Ignore.find({ ignorer: accountId }); "Content-Type": "text/html",
const ignoredUsers: IFriendInfo[] = []; "Content-Length": "3"
await parallelForeach(ignores, async ignore => {
const ignoreeAccount = (await Account.findById(ignore.ignoree, "DisplayName"))!;
ignoredUsers.push({
_id: toOid(ignore.ignoree),
DisplayName: ignoreeAccount.DisplayName + ""
}); });
}); res.end(
res.json({ IgnoredUsers: ignoredUsers }); 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 { Inventory } from "@/src/models/inventoryModels/inventoryModel";
import { generateRewardSeed } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
export const getNewRewardSeedController: RequestHandler = async (req, res) => { export const getNewRewardSeedController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const rewardSeed = generateRewardSeed(); const rewardSeed = generateRewardSeed();
logger.debug(`generated new reward seed: ${rewardSeed}`);
await Inventory.updateOne( await Inventory.updateOne(
{ {
accountOwnerId: accountId accountOwnerId: accountId
@ -17,3 +18,9 @@ export const getNewRewardSeedController: RequestHandler = async (req, res) => {
); );
res.json({ rewardSeed: rewardSeed }); 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

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

View File

@ -1,92 +0,0 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Account } from "@/src/models/loginModel";
import { createMessage } from "@/src/services/inboxService";
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
import { IOid } from "@/src/types/commonTypes";
import { IPurchaseParams } from "@/src/types/purchaseTypes";
import { RequestHandler } from "express";
import { ExportFlavour } from "warframe-public-export-plus";
export const giftingController: RequestHandler = async (req, res) => {
const data = getJSONfromString<IGiftingRequest>(String(req.body));
if (data.PurchaseParams.Source != 0 || !data.PurchaseParams.UsePremium) {
throw new Error(`unexpected purchase params in gifting request: ${String(req.body)}`);
}
const account = await Account.findOne(
data.RecipientId ? { _id: data.RecipientId.$oid } : { DisplayName: data.Recipient }
);
if (!account) {
res.status(400).send("9").end();
return;
}
const inventory = await getInventory(account._id.toString(), "Suits Settings");
// Cannot gift items to players that have not completed the tutorial.
if (inventory.Suits.length == 0) {
res.status(400).send("14").end();
return;
}
// Cannot gift to players who have gifting disabled.
// TODO: Also consider GIFT_MODE_FRIENDS once friends are implemented
if (inventory.Settings?.GiftMode == "GIFT_MODE_NONE") {
res.status(400).send("17").end();
return;
}
// TODO: Cannot gift items with mastery requirement to players who are too low level. (Code 2)
// TODO: Cannot gift archwing items to players that have not completed the archwing quest. (Code 7)
// TODO: Cannot gift necramechs to players that have not completed heart of deimos. (Code 20)
const senderAccount = await getAccountForRequest(req);
const senderInventory = await getInventory(
senderAccount._id.toString(),
"PremiumCredits PremiumCreditsFree ActiveAvatarImageType GiftsRemaining"
);
if (senderInventory.GiftsRemaining == 0) {
res.status(400).send("10").end();
return;
}
senderInventory.GiftsRemaining -= 1;
updateCurrency(senderInventory, data.PurchaseParams.ExpectedPrice, true);
await senderInventory.save();
const senderName = getSuffixedName(senderAccount);
await createMessage(account._id, [
{
sndr: senderName,
msg: data.Message || "/Lotus/Language/Menu/GiftReceivedBody_NoCustomMessage",
arg: [
{
Key: "GIFTER_NAME",
Tag: senderName
},
{
Key: "GIFT_QUANTITY",
Tag: data.PurchaseParams.Quantity
}
],
sub: "/Lotus/Language/Menu/GiftReceivedSubject",
icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
gifts: [
{
GiftType: data.PurchaseParams.StoreItem
}
]
}
]);
res.end();
};
interface IGiftingRequest {
PurchaseParams: IPurchaseParams;
Message?: string;
Recipient?: string;
RecipientId?: IOid;
buildLabel: string;
}

View File

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

View File

@ -2,8 +2,8 @@ import { RequestHandler } from "express";
import { parseString } from "@/src/helpers/general"; import { parseString } from "@/src/helpers/general";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
import { IGroup } from "@/src/types/loginTypes";
import { giveKeyChainItem } from "@/src/services/questService"; import { giveKeyChainItem } from "@/src/services/questService";
import { IKeyChainRequest } from "@/src/types/requestTypes";
export const giveKeyChainTriggeredItemsController: RequestHandler = async (req, res) => { export const giveKeyChainTriggeredItemsController: RequestHandler = async (req, res) => {
const accountId = parseString(req.query.accountId); const accountId = parseString(req.query.accountId);
@ -15,3 +15,9 @@ export const giveKeyChainTriggeredItemsController: RequestHandler = async (req,
res.send(inventoryChanges); 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 { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { giveKeyChainMessage } from "@/src/services/questService"; import { giveKeyChainMessage } from "@/src/services/questService";
import { IKeyChainRequest } from "@/src/types/requestTypes";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
export const giveKeyChainTriggeredMessageController: RequestHandler = async (req, res) => { export const giveKeyChainTriggeredMessageController: RequestHandler = async (req, res) => {

View File

@ -16,15 +16,15 @@ export const giveQuestKeyRewardController: RequestHandler = async (req, res) =>
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
const inventoryChanges = await addItem(inventory, reward.ItemType, reward.Amount); const inventoryChanges = await addItem(inventory, reward.ItemType, reward.Amount);
await inventory.save(); await inventory.save();
res.json(inventoryChanges); res.json(inventoryChanges.InventoryChanges);
//TODO: consider whishlist changes //TODO: consider whishlist changes
}; };
interface IQuestKeyRewardRequest { export interface IQuestKeyRewardRequest {
reward: IQuestKeyReward; reward: IQuestKeyReward;
} }
interface IQuestKeyReward { export interface IQuestKeyReward {
RewardType: string; RewardType: string;
CouponType: string; CouponType: string;
Icon: 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 { 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 { 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 { RequestHandler } from "express";
import { HydratedDocument } from "mongoose";
type TPartialStartingGear = Pick<IInventoryClient, "LongGuns" | "Suits" | "Pistols" | "Melee">;
export const giveStartingGearController: RequestHandler = async (req, res) => { export const giveStartingGearController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -14,3 +26,72 @@ export const giveStartingGearController: RequestHandler = async (req, res) => {
res.send(inventoryChanges); res.send(inventoryChanges);
}; };
//TODO: RawUpgrades might need to return a LastAdded
const awakeningRewards = [
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem1",
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem2",
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem3",
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem4",
"/Lotus/Types/Restoratives/LisetAutoHack",
"/Lotus/Upgrades/Mods/Warframe/AvatarShieldMaxMod"
];
export const addStartingGear = async (
inventory: HydratedDocument<IInventoryDatabase, InventoryDocumentProps>,
startingGear: TPartialStartingGear | undefined = undefined
): Promise<IInventoryChanges> => {
const { LongGuns, Pistols, Suits, Melee } = startingGear || {
LongGuns: [{ ItemType: "/Lotus/Weapons/Tenno/Rifle/Rifle" }],
Pistols: [{ ItemType: "/Lotus/Weapons/Tenno/Pistol/Pistol" }],
Suits: [{ ItemType: "/Lotus/Powersuits/Excalibur/Excalibur" }],
Melee: [{ ItemType: "/Lotus/Weapons/Tenno/Melee/LongSword/LongSword" }]
};
//TODO: properly merge weapon bin changes it is currently static here
const inventoryChanges: IInventoryChanges = {};
addEquipment(inventory, "LongGuns", LongGuns[0].ItemType, undefined, inventoryChanges);
addEquipment(inventory, "Pistols", Pistols[0].ItemType, undefined, inventoryChanges);
addEquipment(inventory, "Melee", Melee[0].ItemType, undefined, inventoryChanges);
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 { RequestHandler } from "express";
import { import {
addGuildMemberMiscItemContribution,
getGuildForRequestEx, getGuildForRequestEx,
getGuildVault, getGuildVault,
hasAccessToDojo, hasAccessToDojo,
hasGuildPermission, hasGuildPermission,
processFundedGuildTechProject,
processGuildTechProjectContributionsUpdate,
removePigmentsFromGuildMembers, removePigmentsFromGuildMembers,
scaleRequiredCount, scaleRequiredCount
setGuildTechLogState
} from "@/src/services/guildService"; } 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 { getAccountIdForRequest } from "@/src/services/loginService";
import { import {
addCrewShipWeaponSkin,
addEquipment,
addItem, addItem,
addMiscItems, addMiscItems,
addRecipes, addRecipes,
combineInventoryChanges, combineInventoryChanges,
getInventory, getInventory,
occupySlot,
updateCurrency updateCurrency
} from "@/src/services/inventoryService"; } 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 { IInventoryChanges } from "@/src/types/purchaseTypes";
import { config } from "@/src/services/configService"; import { config } from "@/src/services/configService";
import { GuildPermission, ITechProjectClient } from "@/src/types/guildTypes"; import { GuildPermission, ITechProjectClient, ITechProjectDatabase } from "@/src/types/guildTypes";
import { GuildMember } from "@/src/models/guildModel"; import { GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers"; import { toMongoDate } from "@/src/helpers/inventoryHelpers";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
export const guildTechController: RequestHandler = async (req, res) => { export const guildTechController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
const guild = await getGuildForRequestEx(req, inventory);
const data = JSON.parse(String(req.body)) as TGuildTechRequest; const data = JSON.parse(String(req.body)) as TGuildTechRequest;
if (data.Action == "Sync") { if (data.Action == "Sync") {
let needSave = false; let needSave = false;
const techProjects: ITechProjectClient[] = []; const techProjects: ITechProjectClient[] = [];
const guild = await getGuildForRequestEx(req, inventory);
if (guild.TechProjects) { if (guild.TechProjects) {
for (const project of guild.TechProjects) { for (const project of guild.TechProjects) {
const techProject: ITechProjectClient = { const techProject: ITechProjectClient = {
@ -52,7 +44,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
if (project.CompletionDate) { if (project.CompletionDate) {
techProject.CompletionDate = toMongoDate(project.CompletionDate); techProject.CompletionDate = toMongoDate(project.CompletionDate);
if (Date.now() >= project.CompletionDate.getTime()) { 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); techProjects.push(techProject);
@ -63,8 +55,6 @@ export const guildTechController: RequestHandler = async (req, res) => {
} }
res.json({ TechProjects: techProjects }); res.json({ TechProjects: techProjects });
} else if (data.Action == "Start") { } else if (data.Action == "Start") {
if (data.Mode == "Guild") {
const guild = await getGuildForRequestEx(req, inventory);
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) { if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
res.status(400).send("-1").end(); res.status(400).send("-1").end();
return; return;
@ -76,17 +66,17 @@ export const guildTechController: RequestHandler = async (req, res) => {
guild.TechProjects[ guild.TechProjects[
guild.TechProjects.push({ guild.TechProjects.push({
ItemType: data.RecipeType, ItemType: data.RecipeType,
ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, recipe.price), ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(recipe.price),
ReqItems: recipe.ingredients.map(x => ({ ReqItems: recipe.ingredients.map(x => ({
ItemType: x.ItemType, ItemType: x.ItemType,
ItemCount: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, x.ItemCount) ItemCount: config.noDojoResearchCosts ? 0 : scaleRequiredCount(x.ItemCount)
})), })),
State: 0 State: 0
}) - 1 }) - 1
]; ];
setGuildTechLogState(guild, techProject.ItemType, 5); setTechLogState(guild, techProject.ItemType, 5);
if (config.noDojoResearchCosts) { if (config.noDojoResearchCosts) {
processFundedGuildTechProject(guild, techProject, recipe); processFundedProject(guild, techProject, recipe);
} else { } else {
if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") { if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
guild.ActiveDojoColorResearch = data.RecipeType; guild.ActiveDojoColorResearch = data.RecipeType;
@ -95,109 +85,38 @@ export const guildTechController: RequestHandler = async (req, res) => {
} }
await guild.save(); await guild.save();
res.end(); 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") { } 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)) { if (!hasAccessToDojo(inventory)) {
res.status(400).send("-1").end(); res.status(400).send("-1").end();
return; return;
} }
const guild = await getGuildForRequestEx(req, inventory);
const guildMember = (await GuildMember.findOne( const guildMember = (await GuildMember.findOne(
{ accountId, guildId: guild._id }, { accountId, guildId: guild._id },
"RegularCreditsContributed MiscItemsContributed" "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 (contributions.VaultCredits) {
if (data.VaultCredits > techProject.ReqCredits) { if (contributions.VaultCredits > techProject.ReqCredits) {
data.VaultCredits = techProject.ReqCredits; contributions.VaultCredits = techProject.ReqCredits;
} }
techProject.ReqCredits -= data.VaultCredits; techProject.ReqCredits -= contributions.VaultCredits;
guild.VaultRegularCredits! -= data.VaultCredits; guild.VaultRegularCredits! -= contributions.VaultCredits;
} }
if (data.RegularCredits > techProject.ReqCredits) { if (contributions.RegularCredits > techProject.ReqCredits) {
data.RegularCredits = techProject.ReqCredits; contributions.RegularCredits = techProject.ReqCredits;
} }
techProject.ReqCredits -= data.RegularCredits; techProject.ReqCredits -= contributions.RegularCredits;
guildMember.RegularCreditsContributed ??= 0; guildMember.RegularCreditsContributed ??= 0;
guildMember.RegularCreditsContributed += data.RegularCredits; guildMember.RegularCreditsContributed += contributions.RegularCredits;
if (data.VaultMiscItems.length) { if (contributions.VaultMiscItems.length) {
for (const miscItem of data.VaultMiscItems) { for (const miscItem of contributions.VaultMiscItems) {
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType); const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
if (reqItem) { if (reqItem) {
if (miscItem.ItemCount > reqItem.ItemCount) { if (miscItem.ItemCount > reqItem.ItemCount) {
@ -212,7 +131,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
} }
const miscItemChanges = []; const miscItemChanges = [];
for (const miscItem of data.MiscItems) { for (const miscItem of contributions.MiscItems) {
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType); const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
if (reqItem) { if (reqItem) {
if (miscItem.ItemCount > reqItem.ItemCount) { if (miscItem.ItemCount > reqItem.ItemCount) {
@ -224,33 +143,37 @@ export const guildTechController: RequestHandler = async (req, res) => {
ItemCount: miscItem.ItemCount * -1 ItemCount: miscItem.ItemCount * -1
}); });
addGuildMemberMiscItemContribution(guildMember, miscItem); guildMember.MiscItemsContributed ??= [];
guildMember.MiscItemsContributed.push(miscItem);
} }
} }
addMiscItems(inventory, miscItemChanges); addMiscItems(inventory, miscItemChanges);
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, data.RegularCredits, false); const inventoryChanges: IInventoryChanges = updateCurrency(inventory, contributions.RegularCredits, false);
inventoryChanges.MiscItems = miscItemChanges; inventoryChanges.MiscItems = miscItemChanges;
// Check if research is fully funded now. if (techProject.ReqCredits == 0 && !techProject.ReqItems.find(x => x.ItemCount > 0)) {
await processGuildTechProjectContributionsUpdate(guild, techProject); // 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({ res.json({
InventoryChanges: inventoryChanges, InventoryChanges: inventoryChanges,
Vault: getGuildVault(guild) Vault: getGuildVault(guild)
}); });
}
} else if (data.Action.split(",")[0] == "Buy") { } else if (data.Action.split(",")[0] == "Buy") {
const purchase = data as IGuildTechBuyRequest; if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
if (purchase.Mode == "Guild") {
const guild = await getGuildForRequestEx(req, inventory);
if (
!hasAccessToDojo(inventory) ||
!(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))
) {
res.status(400).send("-1").end(); res.status(400).send("-1").end();
return; return;
} }
const purchase = data as IGuildTechBuyRequest;
const quantity = parseInt(data.Action.split(",")[1]); const quantity = parseInt(data.Action.split(",")[1]);
const recipeChanges = [ const recipeChanges = [
{ {
@ -272,15 +195,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
Recipes: recipeChanges Recipes: recipeChanges
} }
}); });
} else {
const inventoryChanges = claimSalvagedComponent(inventory, purchase.CategoryItemId!);
await inventory.save();
res.json({
inventoryChanges: inventoryChanges
});
}
} else if (data.Action == "Fabricate") { } else if (data.Action == "Fabricate") {
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(); res.status(400).send("-1").end();
return; return;
@ -297,7 +212,6 @@ export const guildTechController: RequestHandler = async (req, res) => {
// Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`. // Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`.
res.json({ inventoryChanges: inventoryChanges }); res.json({ inventoryChanges: inventoryChanges });
} else if (data.Action == "Pause") { } else if (data.Action == "Pause") {
const guild = await getGuildForRequestEx(req, inventory);
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) { if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
res.status(400).send("-1").end(); res.status(400).send("-1").end();
return; return;
@ -309,7 +223,6 @@ export const guildTechController: RequestHandler = async (req, res) => {
await removePigmentsFromGuildMembers(guild._id); await removePigmentsFromGuildMembers(guild._id);
res.end(); res.end();
} else if (data.Action == "Unpause") { } else if (data.Action == "Unpause") {
const guild = await getGuildForRequestEx(req, inventory);
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) { if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
res.status(400).send("-1").end(); res.status(400).send("-1").end();
return; return;
@ -319,139 +232,72 @@ export const guildTechController: RequestHandler = async (req, res) => {
guild.ActiveDojoColorResearch = data.RecipeType; guild.ActiveDojoColorResearch = data.RecipeType;
await guild.save(); await guild.save();
res.end(); 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 { } else {
logger.debug(`data provided to ${req.path}: ${String(req.body)}`); 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 = type TGuildTechRequest =
| { Action: "Sync" | "SomethingElseThatWeMightNotKnowAbout" } | { Action: "Sync" | "SomethingElseThatWeMightNotKnowAbout" }
| IGuildTechBasicRequest | IGuildTechBasicRequest
| IGuildTechContributeRequest; | IGuildTechContributeRequest;
interface IGuildTechBasicRequest { interface IGuildTechBasicRequest {
Action: "Start" | "Fabricate" | "Pause" | "Unpause" | "Cancel" | "Rush" | "InstantFinish"; Action: "Start" | "Fabricate" | "Pause" | "Unpause";
Mode: "Guild" | "Personal"; Mode: "Guild";
RecipeType: string; RecipeType: string;
TechProductCategory?: string;
CategoryItemId?: string;
} }
interface IGuildTechBuyRequest extends Omit<IGuildTechBasicRequest, "Action"> { interface IGuildTechBuyRequest {
Action: string; Action: string;
Mode: "Guild";
RecipeType: string;
} }
interface IGuildTechContributeRequest { interface IGuildTechContributeRequest {
Action: "Contribute"; Action: "Contribute";
ResearchId: string; ResearchId: "";
RecipeType: string; RecipeType: string;
RegularCredits: number; RegularCredits: number;
MiscItems: IMiscItem[]; MiscItems: IMiscItem[];
VaultCredits: number; VaultCredits: number;
VaultMiscItems: IMiscItem[]; 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, {
UpgradeFingerprint: salvageItem.UpgradeFingerprint
})),
...occupySlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS, false)
};
inventoryChanges.RemovedIdItems = [
{
ItemId: { $oid: itemId }
}
];
return inventoryChanges;
};

View File

@ -1,26 +1,21 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { Inbox } from "@/src/models/inboxModel"; import { Inbox } from "@/src/models/inboxModel";
import { import {
createMessage,
createNewEventMessages, createNewEventMessages,
deleteAllMessagesRead, deleteAllMessagesRead,
deleteMessageRead, deleteMessageRead,
getAllMessagesSorted, getAllMessagesSorted,
getMessage getMessage
} from "@/src/services/inboxService"; } from "@/src/services/inboxService";
import { getAccountForRequest, getAccountFromSuffixedName, getSuffixedName } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { addItems, combineInventoryChanges, getInventory } from "@/src/services/inventoryService"; import { addItems, getInventory } from "@/src/services/inventoryService";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { ExportFlavour } from "warframe-public-export-plus"; import { ExportGear } from "warframe-public-export-plus";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { fromStoreItem, isStoreItem } from "@/src/services/itemDataService";
import { IOid } from "@/src/types/commonTypes";
export const inboxController: RequestHandler = async (req, res) => { export const inboxController: RequestHandler = async (req, res) => {
const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query; const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query;
const account = await getAccountForRequest(req); const accountId = await getAccountIdForRequest(req);
const accountId = account._id.toString();
if (deleteId) { if (deleteId) {
if (deleteId === "DeleteAllRead") { if (deleteId === "DeleteAllRead") {
@ -29,17 +24,17 @@ export const inboxController: RequestHandler = async (req, res) => {
return; return;
} }
await deleteMessageRead(parseOid(deleteId as string)); await deleteMessageRead(deleteId as string);
res.status(200).end(); res.status(200).end();
} else if (messageId) { } else if (messageId) {
const message = await getMessage(parseOid(messageId as string)); const message = await getMessage(messageId as string);
message.r = true; message.r = true;
const attachmentItems = message.att;
const attachmentCountedItems = message.countedAtt;
if (!attachmentItems && !attachmentCountedItems) {
await message.save(); await message.save();
const attachmentItems = message.attVisualOnly ? undefined : message.att;
const attachmentCountedItems = message.attVisualOnly ? undefined : message.countedAtt;
if (!attachmentItems && !attachmentCountedItems && !message.gifts) {
res.status(200).end(); res.status(200).end();
return; return;
} }
@ -50,8 +45,8 @@ export const inboxController: RequestHandler = async (req, res) => {
await addItems( await addItems(
inventory, inventory,
attachmentItems.map(attItem => ({ attachmentItems.map(attItem => ({
ItemType: isStoreItem(attItem) ? fromStoreItem(attItem) : attItem, ItemType: attItem,
ItemCount: 1 ItemCount: attItem in ExportGear ? (ExportGear[attItem].purchaseQuantity ?? 1) : 1
})), })),
inventoryChanges inventoryChanges
); );
@ -59,49 +54,15 @@ export const inboxController: RequestHandler = async (req, res) => {
if (attachmentCountedItems) { if (attachmentCountedItems) {
await addItems(inventory, attachmentCountedItems, inventoryChanges); await addItems(inventory, attachmentCountedItems, inventoryChanges);
} }
if (message.gifts) {
const sender = await getAccountFromSuffixedName(message.sndr);
const recipientName = getSuffixedName(account);
const giftQuantity = message.arg!.find(x => x.Key == "GIFT_QUANTITY")!.Tag as number;
for (const gift of message.gifts) {
combineInventoryChanges(
inventoryChanges,
(await handleStoreItemAcquisition(gift.GiftType, inventory, giftQuantity)).InventoryChanges
);
if (sender) {
await createMessage(sender._id, [
{
sndr: recipientName,
msg: "/Lotus/Language/Menu/GiftReceivedConfirmationBody",
arg: [
{
Key: "RECIPIENT_NAME",
Tag: recipientName
},
{
Key: "GIFT_TYPE",
Tag: gift.GiftType
},
{
Key: "GIFT_QUANTITY",
Tag: giftQuantity
}
],
sub: "/Lotus/Language/Menu/GiftReceivedConfirmationSubject",
icon: ExportFlavour[inventory.ActiveAvatarImageType].icon,
highPriority: true
}
]);
}
}
}
await inventory.save(); await inventory.save();
await message.save();
res.json({ InventoryChanges: inventoryChanges }); res.json({ InventoryChanges: inventoryChanges });
} else if (latestClientMessageId) { } else if (latestClientMessageId) {
await createNewEventMessages(req); await createNewEventMessages(req);
const messages = await Inbox.find({ ownerId: accountId }).sort({ date: 1 }); const messages = await Inbox.find({ ownerId: accountId }).sort({ date: 1 });
const latestClientMessage = messages.find(m => m._id.toString() === parseOid(latestClientMessageId as string)); const latestClientMessage = messages.find(m => m._id.toString() === latestClientMessageId);
if (!latestClientMessage) { if (!latestClientMessage) {
logger.debug(`this should only happen after DeleteAllRead `); logger.debug(`this should only happen after DeleteAllRead `);
@ -124,11 +85,3 @@ export const inboxController: RequestHandler = async (req, res) => {
res.json({ Inbox: inbox }); res.json({ Inbox: inbox });
} }
}; };
// 33.6.0 has query arguments like lastMessage={"$oid":"68112baebf192e786d1502bb"} instead of lastMessage=68112baebf192e786d1502bb
const parseOid = (oid: string): string => {
if (oid[0] == "{") {
return (JSON.parse(oid) as IOid).$oid;
}
return oid;
};

View File

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

View File

@ -1,5 +1,5 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { Inventory, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { Inventory, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { config } from "@/src/services/configService"; import { config } from "@/src/services/configService";
import allDialogue from "@/static/fixed_responses/allDialogue.json"; import allDialogue from "@/static/fixed_responses/allDialogue.json";
@ -13,23 +13,14 @@ import {
ExportResources, ExportResources,
ExportVirtuals ExportVirtuals
} from "warframe-public-export-plus"; } from "warframe-public-export-plus";
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "@/src/services/infestedFoundryService"; import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "./infestedFoundryController";
import { import { addMiscItems, allDailyAffiliationKeys, createLibraryDailyTask } from "@/src/services/inventoryService";
addMiscItems,
allDailyAffiliationKeys,
cleanupInventory,
createLibraryDailyTask,
generateRewardSeed
} from "@/src/services/inventoryService";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { catBreadHash } from "@/src/helpers/stringHelpers";
import { Types } from "mongoose";
import { isNemesisCompatibleWithVersion } from "@/src/helpers/nemesisHelpers";
export const inventoryController: RequestHandler = async (request, response) => { export const inventoryController: RequestHandler = async (request, response) => {
const account = await getAccountForRequest(request); const accountId = await getAccountIdForRequest(request);
const inventory = await Inventory.findOne({ accountOwnerId: account._id }); const inventory = await Inventory.findOne({ accountOwnerId: accountId });
if (!inventory) { if (!inventory) {
response.status(400).json({ error: "inventory was undefined" }); response.status(400).json({ error: "inventory was undefined" });
@ -42,8 +33,6 @@ export const inventoryController: RequestHandler = async (request, response) =>
inventory[key] = 16000 + inventory.PlayerLevel * 500; inventory[key] = 16000 + inventory.PlayerLevel * 500;
} }
inventory.DailyFocus = 250000 + inventory.PlayerLevel * 5000; inventory.DailyFocus = 250000 + inventory.PlayerLevel * 5000;
inventory.GiftsRemaining = Math.max(8, inventory.PlayerLevel);
inventory.TradesRemaining = inventory.PlayerLevel;
inventory.LibraryAvailableDailyTaskInfo = createLibraryDailyTask(); inventory.LibraryAvailableDailyTaskInfo = createLibraryDailyTask();
@ -61,11 +50,9 @@ export const inventoryController: RequestHandler = async (request, response) =>
if (numArgonCrystals == 0) { if (numArgonCrystals == 0) {
break; break;
} }
const numStableArgonCrystals = Math.min( const numStableArgonCrystals =
numArgonCrystals,
inventory.FoundToday?.find(x => x.ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal") inventory.FoundToday?.find(x => x.ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal")
?.ItemCount ?? 0 ?.ItemCount ?? 0;
);
const numDecayingArgonCrystals = numArgonCrystals - numStableArgonCrystals; const numDecayingArgonCrystals = numArgonCrystals - numStableArgonCrystals;
const numDecayingArgonCrystalsToRemove = Math.ceil(numDecayingArgonCrystals / 2); const numDecayingArgonCrystalsToRemove = Math.ceil(numDecayingArgonCrystals / 2);
logger.debug(`ticking argon crystals for day ${i + 1} of ${daysPassed}`, { logger.debug(`ticking argon crystals for day ${i + 1} of ${daysPassed}`, {
@ -87,10 +74,8 @@ export const inventoryController: RequestHandler = async (request, response) =>
} }
} }
cleanupInventory(inventory);
inventory.NextRefill = new Date((Math.trunc(Date.now() / 86400000) + 1) * 86400000); inventory.NextRefill = new Date((Math.trunc(Date.now() / 86400000) + 1) * 86400000);
//await inventory.save(); await inventory.save();
} }
if ( if (
@ -99,36 +84,15 @@ export const inventoryController: RequestHandler = async (request, response) =>
new Date() >= inventory.InfestedFoundry.AbilityOverrideUnlockCooldown new Date() >= inventory.InfestedFoundry.AbilityOverrideUnlockCooldown
) { ) {
handleSubsumeCompletion(inventory); handleSubsumeCompletion(inventory);
//await inventory.save();
}
if (inventory.LastInventorySync) {
const lastSyncDuviriMood = Math.trunc(inventory.LastInventorySync.getTimestamp().getTime() / 7200000);
const currentDuviriMood = Math.trunc(Date.now() / 7200000);
if (lastSyncDuviriMood != currentDuviriMood) {
logger.debug(`refreshing duviri seed`);
if (!inventory.DuviriInfo) {
inventory.DuviriInfo = {
Seed: generateRewardSeed(),
NumCompletions: 0
};
} else {
inventory.DuviriInfo.Seed = generateRewardSeed();
}
}
}
inventory.LastInventorySync = new Types.ObjectId();
await inventory.save(); await inventory.save();
}
response.json( response.json(await getInventoryResponse(inventory, "xpBasedLevelCapDisabled" in request.query));
await getInventoryResponse(inventory, "xpBasedLevelCapDisabled" in request.query, account.BuildLabel)
);
}; };
export const getInventoryResponse = async ( export const getInventoryResponse = async (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
xpBasedLevelCapDisabled: boolean, xpBasedLevelCapDisabled: boolean
buildLabel: string | undefined
): Promise<IInventoryClient> => { ): Promise<IInventoryClient> => {
const inventoryWithLoadOutPresets = await inventory.populate<{ LoadOutPresets: ILoadoutDatabase }>( const inventoryWithLoadOutPresets = await inventory.populate<{ LoadOutPresets: ILoadoutDatabase }>(
"LoadOutPresets" "LoadOutPresets"
@ -180,7 +144,7 @@ export const getInventoryResponse = async (
inventoryResponse.ShipDecorations = []; inventoryResponse.ShipDecorations = [];
for (const [uniqueName, item] of Object.entries(ExportResources)) { for (const [uniqueName, item] of Object.entries(ExportResources)) {
if (item.productCategory == "ShipDecorations") { if (item.productCategory == "ShipDecorations") {
inventoryResponse.ShipDecorations.push({ ItemType: uniqueName, ItemCount: 999_999 }); inventoryResponse.ShipDecorations.push({ ItemType: uniqueName, ItemCount: 1 });
} }
} }
} }
@ -233,8 +197,7 @@ export const getInventoryResponse = async (
if (config.universalPolarityEverywhere) { if (config.universalPolarityEverywhere) {
const Polarity: IPolarity[] = []; const Polarity: IPolarity[] = [];
// 12 is needed for necramechs. 15 is needed for plexus/crewshipharness. for (let i = 0; i != 12; ++i) {
for (let i = 0; i != 15; ++i) {
Polarity.push({ Polarity.push({
Slot: i, Slot: i,
Value: ArtifactPolarity.Any Value: ArtifactPolarity.Any
@ -289,33 +252,20 @@ export const getInventoryResponse = async (
} }
} }
if (config.noDailyFocusLimit) {
inventoryResponse.DailyFocus = Math.max(999_999, 250000 + inventoryResponse.PlayerLevel * 5000);
}
if (inventoryResponse.InfestedFoundry) { if (inventoryResponse.InfestedFoundry) {
applyCheatsToInfestedFoundry(inventoryResponse.InfestedFoundry); applyCheatsToInfestedFoundry(inventoryResponse.InfestedFoundry);
} }
// Omitting this field so opening the navigation resyncs the inventory which is more desirable for typical usage. // Omitting this field so opening the navigation resyncs the inventory which is more desirable for typical usage.
inventoryResponse.LastInventorySync = undefined; //inventoryResponse.LastInventorySync = toOid(new Types.ObjectId());
// Set 2FA enabled so trading post can be used // Set 2FA enabled so trading post can be used
inventoryResponse.HWIDProtectEnabled = true; inventoryResponse.HWIDProtectEnabled = true;
// Fix nemesis for older versions
if (
inventoryResponse.Nemesis &&
buildLabel &&
!isNemesisCompatibleWithVersion(inventoryResponse.Nemesis, buildLabel)
) {
inventoryResponse.Nemesis = undefined;
}
return inventoryResponse; return inventoryResponse;
}; };
const addString = (arr: string[], str: string): void => { export const addString = (arr: string[], str: string): void => {
if (!arr.find(x => x == str)) { if (!arr.find(x => x == str)) {
arr.push(str); arr.push(str);
} }
@ -345,3 +295,13 @@ const resourceGetParent = (resourceName: string): string | undefined => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
return ExportVirtuals[resourceName]?.parentName; return ExportVirtuals[resourceName]?.parentName;
}; };
// This is FNV1a-32 except operating under modulus 2^31 because JavaScript is stinky and likes producing negative integers out of nowhere.
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

@ -7,7 +7,6 @@ import { Account } from "@/src/models/loginModel";
import { createAccount, isCorrectPassword, isNameTaken } from "@/src/services/loginService"; import { createAccount, isCorrectPassword, isNameTaken } from "@/src/services/loginService";
import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes"; import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { version_compare } from "@/src/services/worldStateService";
export const loginController: RequestHandler = async (request, response) => { export const loginController: RequestHandler = async (request, response) => {
const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
@ -22,14 +21,10 @@ export const loginController: RequestHandler = async (request, response) => {
const myAddress = request.host.indexOf("warframe.com") == -1 ? request.host : config.myAddress; const myAddress = request.host.indexOf("warframe.com") == -1 ? request.host : config.myAddress;
if ( if (!account && config.autoCreateAccount && loginRequest.ClientType != "webui") {
!account &&
((config.autoCreateAccount && loginRequest.ClientType != "webui") ||
loginRequest.ClientType == "webui-register")
) {
try { try {
const nameFromEmail = loginRequest.email.substring(0, loginRequest.email.indexOf("@")); const nameFromEmail = loginRequest.email.substring(0, loginRequest.email.indexOf("@"));
let name = nameFromEmail || loginRequest.email.substring(1) || "SpaceNinja"; let name = nameFromEmail || "SpaceNinja";
if (await isNameTaken(name)) { if (await isNameTaken(name)) {
let suffix = 0; let suffix = 0;
do { do {
@ -42,13 +37,12 @@ export const loginController: RequestHandler = async (request, response) => {
password: loginRequest.password, password: loginRequest.password,
DisplayName: name, DisplayName: name,
CountryCode: loginRequest.lang.toUpperCase(), CountryCode: loginRequest.lang.toUpperCase(),
ClientType: loginRequest.ClientType == "webui-register" ? "webui" : loginRequest.ClientType, ClientType: loginRequest.ClientType,
CrossPlatformAllowed: true, CrossPlatformAllowed: true,
ForceLogoutVersion: 0, ForceLogoutVersion: 0,
ConsentNeeded: false, ConsentNeeded: false,
TrackedSettings: [], TrackedSettings: [],
Nonce: nonce, Nonce: nonce
BuildLabel: buildLabel
}); });
logger.debug("created new account"); logger.debug("created new account");
response.json(createLoginResponse(myAddress, newAccount, buildLabel)); response.json(createLoginResponse(myAddress, newAccount, buildLabel));
@ -60,17 +54,8 @@ export const loginController: RequestHandler = async (request, response) => {
} }
} }
if (!account) { //email not found or incorrect password
response.status(400).json({ error: "unknown user" }); if (!account || !isCorrectPassword(loginRequest.password, account.password)) {
return;
}
if (loginRequest.ClientType == "webui-register") {
response.status(400).json({ error: "account already exists" });
return;
}
if (!isCorrectPassword(loginRequest.password, account.password)) {
response.status(400).json({ error: "incorrect login data" }); response.status(400).json({ error: "incorrect login data" });
return; return;
} }
@ -82,17 +67,13 @@ export const loginController: RequestHandler = async (request, response) => {
} }
} else { } else {
if (account.Nonce && account.ClientType != "webui" && !account.Dropped && !loginRequest.kick) { if (account.Nonce && account.ClientType != "webui" && !account.Dropped && !loginRequest.kick) {
// U17 seems to handle "nonce still set" like a login failure. response.status(400).json({ error: "nonce still set" });
if (version_compare(buildLabel, "2015.12.05.18.07") >= 0) {
response.status(400).send({ error: "nonce still set" });
return; return;
} }
}
account.ClientType = loginRequest.ClientType; account.ClientType = loginRequest.ClientType;
account.Nonce = nonce; account.Nonce = nonce;
account.CountryCode = loginRequest.lang.toUpperCase(); account.CountryCode = loginRequest.lang.toUpperCase();
account.BuildLabel = buildLabel;
} }
await account.save(); await account.save();
@ -100,43 +81,25 @@ export const loginController: RequestHandler = async (request, response) => {
}; };
const createLoginResponse = (myAddress: string, account: IDatabaseAccountJson, buildLabel: string): ILoginResponse => { const createLoginResponse = (myAddress: string, account: IDatabaseAccountJson, buildLabel: string): ILoginResponse => {
const resp: ILoginResponse = { return {
id: account.id, id: account.id,
DisplayName: account.DisplayName, DisplayName: account.DisplayName,
CountryCode: account.CountryCode, CountryCode: account.CountryCode,
ClientType: account.ClientType,
CrossPlatformAllowed: account.CrossPlatformAllowed,
ForceLogoutVersion: account.ForceLogoutVersion,
AmazonAuthToken: account.AmazonAuthToken, AmazonAuthToken: account.AmazonAuthToken,
AmazonRefreshToken: account.AmazonRefreshToken, AmazonRefreshToken: account.AmazonRefreshToken,
ConsentNeeded: account.ConsentNeeded,
TrackedSettings: account.TrackedSettings,
Nonce: account.Nonce, Nonce: account.Nonce,
Groups: [],
IRC: config.myIrcAddresses ?? [myAddress], IRC: config.myIrcAddresses ?? [myAddress],
platformCDNs: [`https://${myAddress}/`],
HUB: `https://${myAddress}/api/`,
NRS: config.NRS, NRS: config.NRS,
BuildLabel: buildLabel DTLS: 99,
BuildLabel: buildLabel,
MatchmakingBuildId: buildConfig.matchmakingBuildId
}; };
if (version_compare(buildLabel, "2018.11.08.14.45") >= 0) {
// U24 and up
resp.ConsentNeeded = account.ConsentNeeded;
resp.TrackedSettings = account.TrackedSettings;
}
if (version_compare(buildLabel, "2019.08.29.20.01") >= 0) {
// U25.7 and up
resp.ForceLogoutVersion = account.ForceLogoutVersion;
}
if (version_compare(buildLabel, "2019.10.31.22.42") >= 0) {
// U26 and up
resp.Groups = [];
}
if (version_compare(buildLabel, "2021.04.13.19.58") >= 0) {
resp.DTLS = 99;
}
if (version_compare(buildLabel, "2022.04.29.12.53") >= 0) {
resp.ClientType = account.ClientType;
}
if (version_compare(buildLabel, "2022.09.06.19.24") >= 0) {
resp.CrossPlatformAllowed = account.CrossPlatformAllowed;
resp.HUB = `https://${myAddress}/api/`;
resp.MatchmakingBuildId = buildConfig.matchmakingBuildId;
}
if (version_compare(buildLabel, "2023.04.25.23.40") >= 0) {
resp.platformCDNs = [`https://${myAddress}/`];
}
return resp;
}; };

View File

@ -4,8 +4,7 @@ import {
claimLoginReward, claimLoginReward,
getRandomLoginRewards, getRandomLoginRewards,
ILoginRewardsReponse, ILoginRewardsReponse,
isLoginRewardAChoice, isLoginRewardAChoice
setAccountGotLoginRewardToday
} from "@/src/services/loginRewardService"; } from "@/src/services/loginRewardService";
import { getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
@ -45,11 +44,8 @@ export const loginRewardsController: RequestHandler = async (req, res) => {
if (!isMilestoneDay && randomRewards.length == 1) { if (!isMilestoneDay && randomRewards.length == 1) {
response.DailyTributeInfo.HasChosenReward = true; response.DailyTributeInfo.HasChosenReward = true;
response.DailyTributeInfo.ChosenReward = randomRewards[0]; response.DailyTributeInfo.ChosenReward = randomRewards[0];
response.DailyTributeInfo.NewInventory = await claimLoginReward(inventory, randomRewards[0]); response.DailyTributeInfo.NewInventory = await claimLoginReward(account, inventory, randomRewards[0]);
await inventory.save(); await inventory.save();
setAccountGotLoginRewardToday(account);
await account.save();
} }
res.json(response); res.json(response);
}; };

View File

@ -1,9 +1,5 @@
import { getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
import { import { claimLoginReward, getRandomLoginRewards } from "@/src/services/loginRewardService";
claimLoginReward,
getRandomLoginRewards,
setAccountGotLoginRewardToday
} from "@/src/services/loginRewardService";
import { getAccountForRequest } from "@/src/services/loginService"; import { getAccountForRequest } from "@/src/services/loginService";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService"; import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
@ -32,13 +28,9 @@ export const loginRewardsSelectionController: RequestHandler = async (req, res)
} else { } else {
const randomRewards = getRandomLoginRewards(account, inventory); const randomRewards = getRandomLoginRewards(account, inventory);
chosenReward = randomRewards.find(x => x.StoreItemType == body.ChosenReward)!; chosenReward = randomRewards.find(x => x.StoreItemType == body.ChosenReward)!;
inventoryChanges = await claimLoginReward(inventory, chosenReward); inventoryChanges = await claimLoginReward(account, inventory, chosenReward);
} }
await inventory.save(); await inventory.save();
setAccountGotLoginRewardToday(account);
await account.save();
res.json({ res.json({
DailyTributeInfo: { DailyTributeInfo: {
NewInventory: inventoryChanges, NewInventory: inventoryChanges,

View File

@ -1,27 +0,0 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
export const maturePetController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "KubrowPets");
const data = getJSONfromString<IMaturePetRequest>(String(req.body));
const details = inventory.KubrowPets.id(data.petId)!.Details!;
details.IsPuppy = data.revert;
await inventory.save();
res.json({
petId: data.petId,
updateCollar: true,
armorSkins: ["", "", ""],
furPatterns: data.revert
? ["", "", ""]
: [details.DominantTraits.FurPattern, details.DominantTraits.FurPattern, details.DominantTraits.FurPattern],
unmature: data.revert
});
};
interface IMaturePetRequest {
petId: string;
revert: boolean;
}

View File

@ -1,12 +1,11 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getAccountForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { IMissionInventoryUpdateRequest } from "@/src/types/requestTypes"; import { IMissionInventoryUpdateRequest } from "@/src/types/requestTypes";
import { addMissionInventoryUpdates, addMissionRewards } from "@/src/services/missionInventoryUpdateService"; import { addMissionInventoryUpdates, addMissionRewards } from "@/src/services/missionInventoryUpdateService";
import { generateRewardSeed, getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
import { getInventoryResponse } from "./inventoryController"; import { getInventoryResponse } from "./inventoryController";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { IMissionInventoryUpdateResponse } from "@/src/types/missionTypes";
/* /*
**** INPUT **** **** INPUT ****
@ -48,26 +47,18 @@ import { IMissionInventoryUpdateResponse } from "@/src/types/missionTypes";
- [ ] FpsSamples - [ ] FpsSamples
*/ */
//move credit calc in here, return MissionRewards: [] if no reward info //move credit calc in here, return MissionRewards: [] if no reward info
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const missionInventoryUpdateController: RequestHandler = async (req, res): Promise<void> => { export const missionInventoryUpdateController: RequestHandler = async (req, res): Promise<void> => {
const account = await getAccountForRequest(req); const accountId = await getAccountIdForRequest(req);
const missionReport = getJSONfromString<IMissionInventoryUpdateRequest>((req.body as string).toString()); const missionReport = getJSONfromString<IMissionInventoryUpdateRequest>((req.body as string).toString());
logger.debug("mission report:", missionReport); logger.debug("mission report:", missionReport);
const inventory = await getInventory(account._id.toString()); const inventory = await getInventory(accountId);
const firstCompletion = missionReport.SortieId
? inventory.CompletedSorties.indexOf(missionReport.SortieId) == -1
: false;
const inventoryUpdates = await addMissionInventoryUpdates(inventory, missionReport); const inventoryUpdates = await addMissionInventoryUpdates(inventory, missionReport);
if ( if (missionReport.MissionStatus !== "GS_SUCCESS") {
missionReport.MissionStatus !== "GS_SUCCESS" &&
!(missionReport.RewardInfo?.jobId || missionReport.RewardInfo?.challengeMissionId)
) {
if (missionReport.EndOfMatchUpload) {
inventory.RewardSeed = generateRewardSeed();
}
await inventory.save(); await inventory.save();
const inventoryResponse = await getInventoryResponse(inventory, true, account.BuildLabel); const inventoryResponse = await getInventoryResponse(inventory, true);
res.json({ res.json({
InventoryJson: JSON.stringify(inventoryResponse), InventoryJson: JSON.stringify(inventoryResponse),
MissionRewards: [] MissionRewards: []
@ -75,20 +66,10 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
return; return;
} }
const { const { MissionRewards, inventoryChanges, credits } = await addMissionRewards(inventory, missionReport);
MissionRewards,
inventoryChanges,
credits,
AffiliationMods,
SyndicateXPItemReward,
ConquestCompletedMissionsCount
} = await addMissionRewards(inventory, missionReport, firstCompletion);
if (missionReport.EndOfMatchUpload) {
inventory.RewardSeed = generateRewardSeed();
}
await inventory.save(); await inventory.save();
const inventoryResponse = await getInventoryResponse(inventory, true, account.BuildLabel); const inventoryResponse = await getInventoryResponse(inventory, true);
//TODO: figure out when to send inventory. it is needed for many cases. //TODO: figure out when to send inventory. it is needed for many cases.
res.json({ res.json({
@ -97,11 +78,8 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
MissionRewards, MissionRewards,
...credits, ...credits,
...inventoryUpdates, ...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. FusionPoints: inventoryChanges?.FusionPoints
SyndicateXPItemReward, });
AffiliationMods,
ConquestCompletedMissionsCount
} satisfies IMissionInventoryUpdateResponse);
}; };
/* /*

View File

@ -8,22 +8,15 @@ import {
addMiscItems, addMiscItems,
applyDefaultUpgrades, applyDefaultUpgrades,
occupySlot, occupySlot,
productCategoryToInventoryBin, productCategoryToInventoryBin
combineInventoryChanges,
addSpecialItem
} from "@/src/services/inventoryService"; } from "@/src/services/inventoryService";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { getDefaultUpgrades } from "@/src/services/itemDataService"; import { getDefaultUpgrades } from "@/src/services/itemDataService";
import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper"; import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { getRandomInt } from "@/src/services/rngService";
import { ExportSentinels, ExportWeapons, IDefaultUpgrade } from "warframe-public-export-plus";
import { Status } from "@/src/types/inventoryTypes/inventoryTypes";
interface IModularCraftRequest { interface IModularCraftRequest {
WeaponType: string; WeaponType: string;
Parts: string[]; Parts: string[];
isWebUi?: boolean;
} }
export const modularWeaponCraftingController: RequestHandler = async (req, res) => { export const modularWeaponCraftingController: RequestHandler = async (req, res) => {
@ -35,158 +28,34 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
const category = modularWeaponTypes[data.WeaponType]; const category = modularWeaponTypes[data.WeaponType];
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
let defaultUpgrades: IDefaultUpgrade[] | undefined; const defaultUpgrades = getDefaultUpgrades(data.Parts);
const defaultOverwrites: Partial<IEquipmentDatabase> = { const configs = applyDefaultUpgrades(inventory, defaultUpgrades);
ModularParts: data.Parts const inventoryChanges: IInventoryChanges = {
...addEquipment(inventory, category, data.WeaponType, data.Parts, {}, { Configs: configs }),
...occupySlot(inventory, productCategoryToInventoryBin(category)!, false)
}; };
const inventoryChanges: IInventoryChanges = {};
if (category == "KubrowPets") {
const traits = {
"/Lotus/Types/Friendly/Pets/CreaturePets/ArmoredInfestedCatbrowPetPowerSuit": {
BaseColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorRareBase",
SecondaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorRareSecondary",
TertiaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorRareTertiary",
AccentColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorRareAccent",
EyeColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorRareEyes",
FurPattern: "/Lotus/Types/Game/InfestedKavatPet/Patterns/InfestedCritterPatternDefault",
Personality: data.WeaponType,
BodyType: "/Lotus/Types/Game/CatbrowPet/BodyTypes/InfestedCatbrowPetRegularBodyType",
Head: "/Lotus/Types/Game/InfestedKavatPet/Heads/InfestedCritterHeadC"
},
"/Lotus/Types/Friendly/Pets/CreaturePets/HornedInfestedCatbrowPetPowerSuit": {
BaseColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorUncommonBase",
SecondaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorUncommonSecondary",
TertiaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorUncommonTertiary",
AccentColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorUncommonAccent",
EyeColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorUncommonEyes",
FurPattern: "/Lotus/Types/Game/InfestedKavatPet/Patterns/InfestedCritterPatternDefault",
Personality: data.WeaponType,
BodyType: "/Lotus/Types/Game/CatbrowPet/BodyTypes/InfestedCatbrowPetRegularBodyType",
Head: "/Lotus/Types/Game/InfestedKavatPet/Heads/InfestedCritterHeadB"
},
"/Lotus/Types/Friendly/Pets/CreaturePets/MedjayPredatorKubrowPetPowerSuit": {
BaseColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorRareBase",
SecondaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorRareSecondary",
TertiaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorRareTertiary",
AccentColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorRareAccent",
EyeColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorRareEyes",
FurPattern: "/Lotus/Types/Game/InfestedPredatorPet/Patterns/InfestedPredatorPatternDefault",
Personality: data.WeaponType,
BodyType: "/Lotus/Types/Game/KubrowPet/BodyTypes/InfestedKubrowPetBodyType",
Head: "/Lotus/Types/Game/InfestedPredatorPet/Heads/InfestedPredatorHeadA"
},
"/Lotus/Types/Friendly/Pets/CreaturePets/PharaohPredatorKubrowPetPowerSuit": {
BaseColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorUncommonBase",
SecondaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorUncommonSecondary",
TertiaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorUncommonTertiary",
AccentColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorUncommonAccent",
EyeColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorUncommonEyes",
FurPattern: "/Lotus/Types/Game/InfestedPredatorPet/Patterns/InfestedPredatorPatternDefault",
Personality: data.WeaponType,
BodyType: "/Lotus/Types/Game/KubrowPet/BodyTypes/InfestedKubrowPetBodyType",
Head: "/Lotus/Types/Game/InfestedPredatorPet/Heads/InfestedPredatorHeadB"
},
"/Lotus/Types/Friendly/Pets/CreaturePets/VizierPredatorKubrowPetPowerSuit": {
BaseColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorCommonBase",
SecondaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorCommonSecondary",
TertiaryColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorCommonTertiary",
AccentColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorCommonAccent",
EyeColor: "/Lotus/Types/Game/InfestedPredatorPet/Colors/InfestedPredatorColorCommonEyes",
FurPattern: "/Lotus/Types/Game/InfestedPredatorPet/Patterns/InfestedPredatorPatternDefault",
Personality: data.WeaponType,
BodyType: "/Lotus/Types/Game/KubrowPet/BodyTypes/InfestedKubrowPetBodyType",
Head: "/Lotus/Types/Game/InfestedPredatorPet/Heads/InfestedPredatorHeadC"
},
"/Lotus/Types/Friendly/Pets/CreaturePets/VulpineInfestedCatbrowPetPowerSuit": {
BaseColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorCommonBase",
SecondaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorCommonSecondary",
TertiaryColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorCommonTertiary",
AccentColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorCommonAccent",
EyeColor: "/Lotus/Types/Game/InfestedKavatPet/Colors/InfestedKavatColorCommonEyes",
FurPattern: "/Lotus/Types/Game/InfestedKavatPet/Patterns/InfestedCritterPatternDefault",
Personality: data.WeaponType,
BodyType: "/Lotus/Types/Game/CatbrowPet/BodyTypes/InfestedCatbrowPetRegularBodyType",
Head: "/Lotus/Types/Game/InfestedKavatPet/Heads/InfestedCritterHeadA"
}
}[data.WeaponType];
if (!traits) {
throw new Error(`unknown KubrowPets type: ${data.WeaponType}`);
}
defaultOverwrites.Details = {
Name: "",
IsPuppy: false,
HasCollar: true,
PrintsRemaining: 2,
Status: Status.StatusStasis,
HatchDate: new Date(Math.trunc(Date.now() / 86400000) * 86400000),
IsMale: !!getRandomInt(0, 1),
Size: getRandomInt(70, 100) / 100,
DominantTraits: traits,
RecessiveTraits: traits
};
// Only save mutagen & antigen in the ModularParts.
defaultOverwrites.ModularParts = [data.Parts[1], data.Parts[2]];
const meta = ExportSentinels[data.WeaponType];
for (const specialItem of meta.exalted!) {
addSpecialItem(inventory, specialItem, inventoryChanges);
}
defaultUpgrades = meta.defaultUpgrades;
} else {
defaultUpgrades = getDefaultUpgrades(data.Parts);
}
if (category == "MoaPets") {
const weapon = ExportSentinels[data.WeaponType].defaultWeapon;
if (weapon) {
const category = ExportWeapons[weapon].productCategory;
addEquipment(inventory, category, weapon, undefined, inventoryChanges);
combineInventoryChanges(
inventoryChanges,
occupySlot(inventory, productCategoryToInventoryBin(category)!, !!data.isWebUi)
);
}
}
defaultOverwrites.Configs = applyDefaultUpgrades(inventory, defaultUpgrades);
addEquipment(inventory, category, data.WeaponType, defaultOverwrites, inventoryChanges);
combineInventoryChanges(
inventoryChanges,
occupySlot(inventory, productCategoryToInventoryBin(category)!, !!data.isWebUi)
);
if (defaultUpgrades) { if (defaultUpgrades) {
inventoryChanges.RawUpgrades = defaultUpgrades.map(x => ({ ItemType: x.ItemType, ItemCount: 1 })); inventoryChanges.RawUpgrades = defaultUpgrades.map(x => ({ ItemType: x.ItemType, ItemCount: 1 }));
} }
// Remove credits & parts // Remove credits & parts
const miscItemChanges = []; const miscItemChanges = [];
let currencyChanges = {};
if (!data.isWebUi) {
for (const part of data.Parts) { for (const part of data.Parts) {
miscItemChanges.push({ miscItemChanges.push({
ItemType: part, ItemType: part,
ItemCount: -1 ItemCount: -1
}); });
} }
currencyChanges = updateCurrency( const currencyChanges = updateCurrency(
inventory, inventory,
category == "Hoverboards" || category == "Hoverboards" || category == "MoaPets" || category == "LongGuns" || category == "Pistols"
category == "MoaPets" ||
category == "LongGuns" ||
category == "Pistols" ||
category == "KubrowPets"
? 5000 ? 5000
: 4000, // Definitely correct for Melee & OperatorAmps : 4000, // Definitely correct for Melee & OperatorAmps
false false
); );
addMiscItems(inventory, miscItemChanges); addMiscItems(inventory, miscItemChanges);
}
await inventory.save(); await inventory.save();
// Tell client what we did // Tell client what we did
res.json({ res.json({
InventoryChanges: { InventoryChanges: {

View File

@ -21,11 +21,8 @@ import { IInventoryChanges } from "@/src/types/purchaseTypes";
export const modularWeaponSaleController: RequestHandler = async (req, res) => { export const modularWeaponSaleController: RequestHandler = async (req, res) => {
const partTypeToParts: Record<string, string[]> = {}; const partTypeToParts: Record<string, string[]> = {};
for (const [uniqueName, data] of Object.entries(ExportWeapons)) { for (const [uniqueName, data] of Object.entries(ExportWeapons)) {
if ( if (data.partType && data.premiumPrice) {
data.partType && // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
data.premiumPrice &&
!data.excludeFromCodex // exclude pvp variants
) {
partTypeToParts[data.partType] ??= []; partTypeToParts[data.partType] ??= [];
partTypeToParts[data.partType].push(uniqueName); partTypeToParts[data.partType].push(uniqueName);
} }
@ -45,18 +42,24 @@ export const modularWeaponSaleController: RequestHandler = async (req, res) => {
const defaultUpgrades = getDefaultUpgrades(weaponInfo.ModularParts); const defaultUpgrades = getDefaultUpgrades(weaponInfo.ModularParts);
const configs = applyDefaultUpgrades(inventory, defaultUpgrades); const configs = applyDefaultUpgrades(inventory, defaultUpgrades);
const inventoryChanges: IInventoryChanges = { const inventoryChanges: IInventoryChanges = {
...addEquipment(inventory, category, weaponInfo.ItemType, { ...addEquipment(
inventory,
category,
weaponInfo.ItemType,
weaponInfo.ModularParts,
{},
{
Features: EquipmentFeatures.DOUBLE_CAPACITY | EquipmentFeatures.GILDED, Features: EquipmentFeatures.DOUBLE_CAPACITY | EquipmentFeatures.GILDED,
ItemName: payload.ItemName, ItemName: payload.ItemName,
Configs: configs, Configs: configs,
ModularParts: weaponInfo.ModularParts,
Polarity: [ Polarity: [
{ {
Slot: payload.PolarizeSlot, Slot: payload.PolarizeSlot,
Value: payload.PolarizeValue Value: payload.PolarizeValue
} }
] ]
}), }
),
...occupySlot(inventory, productCategoryToInventoryBin(category)!, true), ...occupySlot(inventory, productCategoryToInventoryBin(category)!, true),
...updateCurrency(inventory, weaponInfo.PremiumPrice, true) ...updateCurrency(inventory, weaponInfo.PremiumPrice, true)
}; };
@ -141,7 +144,7 @@ const getModularWeaponSale = (
getItemType: (parts: string[]) => string getItemType: (parts: string[]) => string
): IModularWeaponSaleInfo => { ): IModularWeaponSaleInfo => {
const rng = new CRng(day); const rng = new CRng(day);
const parts = partTypes.map(partType => rng.randomElement(partTypeToParts[partType])!); const parts = partTypes.map(partType => rng.randomElement(partTypeToParts[partType]));
let partsCost = 0; let partsCost = 0;
for (const part of parts) { for (const part of parts) {
partsCost += ExportWeapons[part].premiumPrice!; partsCost += ExportWeapons[part].premiumPrice!;

View File

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

View File

@ -1,31 +1,10 @@
import { import { getInfNodes, getNemesisPasscode } from "@/src/helpers/nemesisHelpers";
consumeModCharge,
encodeNemesisGuess,
getInfNodes,
getKnifeUpgrade,
getNemesisPasscode,
getNemesisPasscodeModTypes,
getWeaponsForManifest,
IKnifeResponse,
showdownNodes
} from "@/src/helpers/nemesisHelpers";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
import { freeUpSlot, getInventory } from "@/src/services/inventoryService"; import { freeUpSlot, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { SRng } from "@/src/services/rngService"; import { SRng } from "@/src/services/rngService";
import { IMongoDate, IOid } from "@/src/types/commonTypes"; import { IMongoDate, IOid } from "@/src/types/commonTypes";
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { IInnateDamageFingerprint, InventorySlot, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import {
IInnateDamageFingerprint,
IInventoryClient,
INemesisClient,
InventorySlot,
IUpgradeClient,
IWeaponSkinClient,
LoadoutIndex,
TEquipmentKey
} from "@/src/types/inventoryTypes/inventoryTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
@ -39,7 +18,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
const destFingerprint = JSON.parse(destWeapon.UpgradeFingerprint!) as IInnateDamageFingerprint; const destFingerprint = JSON.parse(destWeapon.UpgradeFingerprint!) as IInnateDamageFingerprint;
const sourceFingerprint = JSON.parse(sourceWeapon.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) { if (body.UseSourceDmgType) {
destFingerprint.buffs[0].Tag = sourceFingerprint.buffs[0].Tag; destFingerprint.buffs[0].Tag = sourceFingerprint.buffs[0].Tag;
} }
@ -48,7 +27,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
const destDamage = 0.25 + (destFingerprint.buffs[0].Value / 0x3fffffff) * (0.6 - 0.25); 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); const sourceDamage = 0.25 + (sourceFingerprint.buffs[0].Value / 0x3fffffff) * (0.6 - 0.25);
let newDamage = Math.max(destDamage, sourceDamage) * 1.1; let newDamage = Math.max(destDamage, sourceDamage) * 1.1;
if (newDamage >= 0.5794998) { if (newDamage >= 0.58) {
newDamage = 0.6; newDamage = 0.6;
} }
destFingerprint.buffs[0].Value = Math.trunc(((newDamage - 0.25) / (0.6 - 0.25)) * 0x3fffffff); destFingerprint.buffs[0].Value = Math.trunc(((newDamage - 0.25) / (0.6 - 0.25)) * 0x3fffffff);
@ -63,14 +42,13 @@ export const nemesisController: RequestHandler = async (req, res) => {
await inventory.save(); await inventory.save();
res.json({ res.json({
InventoryChanges: { InventoryChanges: {
[body.Category]: [destWeapon.toJSON()], [body.Category]: [destWeapon.toJSON()]
RemovedIdItems: [{ ItemId: body.SourceWeapon }]
} }
}); });
} else if ((req.query.mode as string) == "p") { } else if ((req.query.mode as string) == "p") {
const inventory = await getInventory(accountId, "Nemesis"); const inventory = await getInventory(accountId, "Nemesis");
const body = getJSONfromString<INemesisPrespawnCheckRequest>(String(req.body)); const body = getJSONfromString<INemesisPrespawnCheckRequest>(String(req.body));
const passcode = getNemesisPasscode(inventory.Nemesis!); const passcode = getNemesisPasscode(inventory.Nemesis!.fp, inventory.Nemesis!.Faction);
let guessResult = 0; let guessResult = 0;
if (inventory.Nemesis!.Faction == "FC_INFESTATION") { if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
for (let i = 0; i != 3; ++i) { for (let i = 0; i != 3; ++i) {
@ -87,83 +65,6 @@ export const nemesisController: RequestHandler = async (req, res) => {
} }
} }
res.json({ GuessResult: guessResult }); 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 if correct antivirus mod is installed
const response: IKnifeResponse = {};
if (result1 == 0 || result2 == 0 || result3 == 0) {
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!;
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 = 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") { } else if ((req.query.mode as string) == "s") {
const inventory = await getInventory(accountId, "Nemesis"); const inventory = await getInventory(accountId, "Nemesis");
const body = getJSONfromString<INemesisStartRequest>(String(req.body)); const body = getJSONfromString<INemesisStartRequest>(String(req.body));
@ -171,7 +72,18 @@ export const nemesisController: RequestHandler = async (req, res) => {
let weaponIdx = -1; let weaponIdx = -1;
if (body.target.Faction != "FC_INFESTATION") { if (body.target.Faction != "FC_INFESTATION") {
const weapons = getWeaponsForManifest(body.target.manifest); let weapons: readonly string[];
if (body.target.manifest == "/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionSix") {
weapons = kuvaLichVersionSixWeapons;
} else if (
body.target.manifest == "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionFour" ||
body.target.manifest == "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionThree"
) {
weapons = corpusVersionThreeWeapons;
} else {
throw new Error(`unknown nemesis manifest: ${body.target.manifest}`);
}
const initialWeaponIdx = new SRng(body.target.fp).randomInt(0, weapons.length - 1); const initialWeaponIdx = new SRng(body.target.fp).randomInt(0, weapons.length - 1);
weaponIdx = initialWeaponIdx; weaponIdx = initialWeaponIdx;
do { do {
@ -213,38 +125,6 @@ export const nemesisController: RequestHandler = async (req, res) => {
res.json({ res.json({
target: inventory.toJSON().Nemesis target: inventory.toJSON().Nemesis
}); });
} else if ((req.query.mode as string) == "w") {
const inventory = await getInventory(
accountId,
"Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades"
);
//const body = getJSONfromString<INemesisWeakenRequest>(String(req.body));
inventory.Nemesis!.InfNodes = [
{
Node: showdownNodes[inventory.Nemesis!.Faction],
Influence: 1
}
];
inventory.Nemesis!.Weakened = true;
const response: IKnifeResponse & { target: INemesisClient } = {
target: inventory.toJSON<IInventoryClient>().Nemesis!
};
// Consume charge of the correct requiem mod(s)
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 modTypes = getNemesisPasscodeModTypes(inventory.Nemesis!);
for (const modType of modTypes) {
const upgrade = getKnifeUpgrade(inventory, dataknifeUpgrades, modType);
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
}
await inventory.save();
res.json(response);
} else { } else {
logger.debug(`data provided to ${req.path}: ${String(req.body)}`); logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
throw new Error(`unknown nemesis mode: ${String(req.query.mode)}`); throw new Error(`unknown nemesis mode: ${String(req.query.mode)}`);
@ -292,23 +172,38 @@ interface INemesisPrespawnCheckRequest {
potency?: number[]; potency?: number[];
} }
interface INemesisRequiemRequest { const kuvaLichVersionSixWeapons = [
guess: number; // grn/crp: 4 bits | coda: 3x 4 bits "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Drakgoon/KuvaDrakgoon",
position: number; // grn/crp: 0-2 | coda: 0 "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Karak/KuvaKarak",
// knife field provided for coda only "/Lotus/Weapons/Grineer/Melee/GrnKuvaLichScythe/GrnKuvaLichScytheWeapon",
knife?: IKnife; "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Kohm/KuvaKohm",
} "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Ogris/KuvaOgris",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Quartakk/KuvaQuartakk",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Tonkor/KuvaTonkor",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Brakk/KuvaBrakk",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Kraken/KuvaKraken",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Seer/KuvaSeer",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Stubba/KuvaStubba",
"/Lotus/Weapons/Grineer/HeavyWeapons/GrnHeavyGrenadeLauncher",
"/Lotus/Weapons/Grineer/LongGuns/GrnKuvaLichRifle/GrnKuvaLichRifleWeapon",
"/Lotus/Weapons/Grineer/Bows/GrnBow/GrnBowWeapon",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hind/KuvaHind",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Nukor/KuvaNukor",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hek/KuvaHekWeapon",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Zarr/KuvaZarr",
"/Lotus/Weapons/Grineer/KuvaLich/HeavyWeapons/Grattler/KuvaGrattler",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Sobek/KuvaSobek"
];
// interface INemesisWeakenRequest { const corpusVersionThreeWeapons = [
// target: INemesisClient; "/Lotus/Weapons/Corpus/LongGuns/CrpBriefcaseLauncher/CrpBriefcaseLauncher",
// knife: IKnife; "/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEArcaPlasmor/CrpBEArcaPlasmor",
// } "/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEFluxRifle/CrpBEFluxRifle",
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBETetra/CrpBETetra",
interface IKnife { "/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBECycron/CrpBECycron",
Item: IEquipmentClient; "/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEDetron/CrpBEDetron",
Skins: IWeaponSkinClient[]; "/Lotus/Weapons/Corpus/Pistols/CrpIgniterPistol/CrpIgniterPistol",
ModSlot: number; "/Lotus/Weapons/Corpus/Pistols/CrpBriefcaseAkimbo/CrpBriefcaseAkimboPistol",
CustSlot: number; "/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEPlinx/CrpBEPlinxWeapon",
AttachedUpgrades: IUpgradeClient[]; "/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEGlaxion/CrpBEGlaxion"
HiddenWhenHolstered: boolean; ];
}

View File

@ -1,19 +1,10 @@
import { import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
getDojoClient,
getGuildForRequestEx,
getVaultMiscItemCount,
hasAccessToDojo,
hasGuildPermission,
processDojoBuildMaterialsGathered,
scaleRequiredCount
} from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes"; import { GuildPermission } from "@/src/types/guildTypes";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { ExportDojoRecipes, ExportResources } from "warframe-public-export-plus"; import { ExportDojoRecipes } from "warframe-public-export-plus";
import { config } from "@/src/services/configService";
export const placeDecoInComponentController: RequestHandler = async (req, res) => { export const placeDecoInComponentController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -33,12 +24,6 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
} }
component.Decos ??= []; component.Decos ??= [];
if (request.MoveId) {
const deco = component.Decos.find(x => x._id.equals(request.MoveId))!;
deco.Pos = request.Pos;
deco.Rot = request.Rot;
deco.Scale = request.Scale;
} else {
const deco = const deco =
component.Decos[ component.Decos[
component.Decos.push({ component.Decos.push({
@ -46,62 +31,17 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
Type: request.Type, Type: request.Type,
Pos: request.Pos, Pos: request.Pos,
Rot: request.Rot, Rot: request.Rot,
Scale: request.Scale, Name: request.Name
Name: request.Name,
Sockets: request.Sockets
}) - 1 }) - 1
]; ];
const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == request.Type); const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == request.Type);
if (meta) { if (meta) {
if (meta.capacityCost) { if (meta.capacityCost) {
component.DecoCapacity -= meta.capacityCost; component.DecoCapacity -= meta.capacityCost;
} }
} else { if (meta.price == 0 && meta.ingredients.length == 0) {
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) {
deco.CompletionTime = new Date(); 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);
}
}
}
} }
} }
@ -115,10 +55,5 @@ interface IPlaceDecoInComponentRequest {
Type: string; Type: string;
Pos: number[]; Pos: number[];
Rot: number[]; Rot: number[];
Scale?: number;
Name?: string; Name?: string;
Sockets?: number;
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

@ -1,25 +0,0 @@
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
// Basic shim handling action=sync to login on U21
export const questControlController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const quests: IQuestState[] = [];
for (const quest of inventory.QuestKeys) {
quests.push({
quest: quest.ItemType,
state: 3 // COMPLETE
});
}
res.json({
QuestState: quests
});
};
interface IQuestState {
quest: string;
state: number;
task?: string;
}

View File

@ -16,7 +16,7 @@ export const queueDojoComponentDestructionController: RequestHandler = async (re
const componentId = req.query.componentId as string; const componentId = req.query.componentId as string;
guild.DojoComponents.id(componentId)!.DestructionTime = new Date( 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(); 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,6 @@
import { GuildMember } from "@/src/models/guildModel"; import { GuildMember } from "@/src/models/guildModel";
import { Inbox } from "@/src/models/inboxModel";
import { Account } from "@/src/models/loginModel"; import { Account } from "@/src/models/loginModel";
import { deleteGuild, getGuildForRequest, hasGuildPermission, removeDojoKeyItems } from "@/src/services/guildService"; import { getGuildForRequest, hasGuildPermission } from "@/src/services/guildService";
import { createMessage } from "@/src/services/inboxService";
import { getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService"; import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes"; import { GuildPermission } from "@/src/types/guildTypes";
@ -19,41 +17,26 @@ export const removeFromGuildController: RequestHandler = async (req, res) => {
} }
const guildMember = (await GuildMember.findOne({ accountId: payload.userId, guildId: guild._id }))!; const guildMember = (await GuildMember.findOne({ accountId: payload.userId, guildId: guild._id }))!;
if (guildMember.rank == 0) {
await deleteGuild(guild._id);
} else {
if (guildMember.status == 0) { if (guildMember.status == 0) {
const inventory = await getInventory(payload.userId, "GuildId LevelKeys Recipes"); const inventory = await getInventory(payload.userId);
inventory.GuildId = undefined; 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(itemIndex, 1);
}
}
await inventory.save(); await inventory.save();
} else if (guildMember.status == 1) {
// TOVERIFY: Is this inbox message actually sent on live? // TODO: Handle clan leader kicking themselves (guild should be deleted in this case, I think)
await createMessage(guildMember.accountId, [
{
sndr: "/Lotus/Language/Bosses/Ordis",
msg: "/Lotus/Language/Clan/RejectedFromClan",
sub: "/Lotus/Language/Clan/RejectedFromClanHeader",
arg: [
{
Key: "PLAYER_NAME",
Tag: (await Account.findOne({ _id: guildMember.accountId }, "DisplayName"))!.DisplayName
},
{
Key: "CLAN_NAME",
Tag: guild.Name
}
]
// TOVERIFY: If this message is sent on live, is it highPriority?
}
]);
} else if (guildMember.status == 2) { } else if (guildMember.status == 2) {
// Delete the inbox message for the invite // TODO: Maybe the inbox message for the sent invite should be deleted?
await Inbox.deleteOne({
ownerId: guildMember.accountId,
contextInfo: guild._id.toString(),
acceptAction: "GUILD_INVITE"
});
} }
await GuildMember.deleteOne({ _id: guildMember._id }); await GuildMember.deleteOne({ _id: guildMember._id });
@ -73,7 +56,6 @@ export const removeFromGuildController: RequestHandler = async (req, res) => {
}); });
} }
await guild.save(); await guild.save();
}
res.json({ res.json({
_id: payload.userId, _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,33 +0,0 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { Status } from "@/src/types/inventoryTypes/inventoryTypes";
import { RequestHandler } from "express";
export const retrievePetFromStasisController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "KubrowPets");
const data = getJSONfromString<IRetrievePetFromStasisRequest>(String(req.body));
let oldPetId: string | undefined;
for (const pet of inventory.KubrowPets) {
if (pet.Details!.Status == Status.StatusAvailable) {
pet.Details!.Status = Status.StatusStasis;
oldPetId = pet._id.toString();
break;
}
}
inventory.KubrowPets.id(data.petId)!.Details!.Status = Status.StatusAvailable;
await inventory.save();
res.json({
petId: data.petId,
oldPetId,
status: Status.StatusAvailable
});
};
interface IRetrievePetFromStasisRequest {
petId: string;
}

View File

@ -1,41 +1,53 @@
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { getInventory } from "@/src/services/inventoryService";
import { config } from "@/src/services/configService";
import { addEmailItem, getInventory, updateCurrency } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { ICompletedDialogue, IDialogueDatabase } from "@/src/types/inventoryTypes/inventoryTypes"; import { ICompletedDialogue } from "@/src/types/inventoryTypes/inventoryTypes";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
export const saveDialogueController: RequestHandler = async (req, res) => { export const saveDialogueController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const request = JSON.parse(String(req.body)) as SaveDialogueRequest; const request = JSON.parse(String(req.body)) as SaveDialogueRequest;
if ("YearIteration" in request) { if ("YearIteration" in request) {
const inventory = await getInventory(accountId, "DialogueHistory"); const inventory = await getInventory(accountId);
inventory.DialogueHistory ??= {}; if (inventory.DialogueHistory) {
inventory.DialogueHistory.YearIteration = request.YearIteration; inventory.DialogueHistory.YearIteration = request.YearIteration;
} else {
inventory.DialogueHistory = { YearIteration: request.YearIteration };
}
await inventory.save(); await inventory.save();
res.end(); res.end();
} else { } else {
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
const inventoryChanges: IInventoryChanges = {}; if (!inventory.DialogueHistory) {
const tomorrowAt0Utc = config.noKimCooldowns throw new Error("bad inventory state");
? Date.now() }
: (Math.trunc(Date.now() / 86400_000) + 1) * 86400_000; if (request.QueuedDialogues.length != 0 || request.OtherDialogueInfos.length != 0) {
inventory.DialogueHistory ??= {}; logger.error(`saveDialogue request not fully handled: ${String(req.body)}`);
}
inventory.DialogueHistory.Dialogues ??= []; 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.Rank = request.Rank;
dialogue.Chemistry = request.Chemistry; dialogue.Chemistry = request.Chemistry;
dialogue.QueuedDialogues = request.QueuedDialogues; //dialogue.QueuedDialogues = request.QueuedDialogues;
for (const bool of request.Booleans) { for (const bool of request.Booleans) {
dialogue.Booleans.push(bool); dialogue.Booleans.push(bool);
if (bool == "LizzieShawzin") {
await addEmailItem(
inventory,
"/Lotus/Types/Items/EmailItems/LizzieShawzinSkinEmailItem",
inventoryChanges
);
}
} }
for (const bool of request.ResetBooleans) { for (const bool of request.ResetBooleans) {
const index = dialogue.Booleans.findIndex(x => x == bool); const index = dialogue.Booleans.findIndex(x => x == bool);
@ -43,38 +55,14 @@ export const saveDialogueController: RequestHandler = async (req, res) => {
dialogue.Booleans.splice(index, 1); dialogue.Booleans.splice(index, 1);
} }
} }
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
}
if (request.Data) {
dialogue.Completed.push(request.Data); dialogue.Completed.push(request.Data);
const tomorrowAt0Utc = (Math.trunc(Date.now() / (86400 * 1000)) + 1) * 86400 * 1000;
dialogue.AvailableDate = new Date(tomorrowAt0Utc); dialogue.AvailableDate = new Date(tomorrowAt0Utc);
await inventory.save(); await inventory.save();
res.json({ res.json({
InventoryChanges: inventoryChanges, InventoryChanges: [],
AvailableDate: { $date: { $numberLong: tomorrowAt0Utc.toString() } } 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 {
res.end();
}
} }
}; };
@ -89,17 +77,11 @@ interface SaveCompletedDialogueRequest {
Rank: number; Rank: number;
Chemistry: number; Chemistry: number;
CompletionType: number; CompletionType: number;
QueuedDialogues: string[]; QueuedDialogues: string[]; // unsure
Gift?: {
Item: string;
GainedChemistry: number;
Cost: number;
GiftedQuantity: number;
};
Booleans: string[]; Booleans: string[];
ResetBooleans: string[]; ResetBooleans: string[];
Data?: ICompletedDialogue; Data: ICompletedDialogue;
OtherDialogueInfos: IOtherDialogueInfo[]; OtherDialogueInfos: IOtherDialogueInfo[]; // unsure
} }
interface IOtherDialogueInfo { interface IOtherDialogueInfo {
@ -107,26 +89,3 @@ interface IOtherDialogueInfo {
Tag: string; Tag: string;
Value: number; 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,71 +6,20 @@ import {
addRecipes, addRecipes,
addMiscItems, addMiscItems,
addConsumables, addConsumables,
freeUpSlot, freeUpSlot
combineInventoryChanges,
addCrewShipRawSalvage,
addFusionPoints
} from "@/src/services/inventoryService"; } from "@/src/services/inventoryService";
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes"; 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) => { export const sellController: RequestHandler = async (req, res) => {
const payload = JSON.parse(String(req.body)) as ISellRequest; const payload = JSON.parse(String(req.body)) as ISellRequest;
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const requiredFields = new Set<keyof TInventoryDatabaseDocument>(); const inventory = await getInventory(accountId);
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 || payload.Items.MoaPets) {
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(" "));
// Give currency // Give currency
if (payload.SellCurrency == "SC_RegularCredits") { if (payload.SellCurrency == "SC_RegularCredits") {
inventory.RegularCredits += payload.SellPrice; inventory.RegularCredits += payload.SellPrice;
} else if (payload.SellCurrency == "SC_FusionPoints") { } else if (payload.SellCurrency == "SC_FusionPoints") {
addFusionPoints(inventory, payload.SellPrice); inventory.FusionPoints += payload.SellPrice;
} else if (payload.SellCurrency == "SC_PrimeBucks") { } else if (payload.SellCurrency == "SC_PrimeBucks") {
addMiscItems(inventory, [ addMiscItems(inventory, [
{ {
@ -85,14 +34,10 @@ export const sellController: RequestHandler = async (req, res) => {
ItemCount: payload.SellPrice ItemCount: payload.SellPrice
} }
]); ]);
} else if (payload.SellCurrency == "SC_Resources") {
// Will add appropriate MiscItems from CrewShipWeapons or CrewShipWeaponSkins
} else { } else {
throw new Error("Unknown SellCurrency: " + payload.SellCurrency); throw new Error("Unknown SellCurrency: " + payload.SellCurrency);
} }
const inventoryChanges: IInventoryChanges = {};
// Remove item(s) // Remove item(s)
if (payload.Items.Suits) { if (payload.Items.Suits) {
payload.Items.Suits.forEach(sellItem => { payload.Items.Suits.forEach(sellItem => {
@ -148,12 +93,6 @@ export const sellController: RequestHandler = async (req, res) => {
freeUpSlot(inventory, InventorySlot.SENTINELS); freeUpSlot(inventory, InventorySlot.SENTINELS);
}); });
} }
if (payload.Items.MoaPets) {
payload.Items.MoaPets.forEach(sellItem => {
inventory.MoaPets.pull({ _id: sellItem.String });
freeUpSlot(inventory, InventorySlot.SENTINELS);
});
}
if (payload.Items.OperatorAmps) { if (payload.Items.OperatorAmps) {
payload.Items.OperatorAmps.forEach(sellItem => { payload.Items.OperatorAmps.forEach(sellItem => {
inventory.OperatorAmps.pull({ _id: sellItem.String }); inventory.OperatorAmps.pull({ _id: sellItem.String });
@ -171,56 +110,6 @@ export const sellController: RequestHandler = async (req, res) => {
inventory.Drones.pull({ _id: sellItem.String }); 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) { if (payload.Items.Consumables) {
const consumablesChanges = []; const consumablesChanges = [];
for (const sellItem of payload.Items.Consumables) { for (const sellItem of payload.Items.Consumables) {
@ -267,9 +156,7 @@ export const sellController: RequestHandler = async (req, res) => {
} }
await inventory.save(); await inventory.save();
res.json({ res.json({});
inventoryChanges: inventoryChanges // "inventoryChanges" for this response instead of the usual "InventoryChanges"
});
}; };
interface ISellRequest { interface ISellRequest {
@ -287,12 +174,9 @@ interface ISellRequest {
SpaceMelee?: ISellItem[]; SpaceMelee?: ISellItem[];
Sentinels?: ISellItem[]; Sentinels?: ISellItem[];
SentinelWeapons?: ISellItem[]; SentinelWeapons?: ISellItem[];
MoaPets?: ISellItem[];
OperatorAmps?: ISellItem[]; OperatorAmps?: ISellItem[];
Hoverboards?: ISellItem[]; Hoverboards?: ISellItem[];
Drones?: ISellItem[]; Drones?: ISellItem[];
CrewShipWeapons?: ISellItem[];
CrewShipWeaponSkins?: ISellItem[];
}; };
SellPrice: number; SellPrice: number;
SellCurrency: SellCurrency:
@ -309,33 +193,3 @@ interface ISellItem {
String: string; // oid or uniqueName String: string; // oid or uniqueName
Count: number; 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,46 +1,22 @@
import { Alliance, Guild, GuildMember } from "@/src/models/guildModel"; import { Guild } from "@/src/models/guildModel";
import { hasGuildPermissionEx } from "@/src/services/guildService"; import { hasGuildPermission } from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService"; import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
import { version_compare } from "@/src/services/worldStateService"; import { GuildPermission } from "@/src/types/guildTypes";
import { GuildPermission, ILongMOTD } from "@/src/types/guildTypes";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
export const setGuildMotdController: RequestHandler = async (req, res) => { export const setGuildMotdController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req); const account = await getAccountForRequest(req);
const inventory = await getInventory(account._id.toString(), "GuildId"); const inventory = await getInventory(account._id.toString(), "GuildId");
const guild = (await Guild.findById(inventory.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 IsLongMOTD = "longMOTD" in req.query;
const MOTD = req.body ? String(req.body) : undefined; 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 (IsLongMOTD) {
if (MOTD) { if (MOTD) {
guild.LongMOTD = { guild.LongMOTD = {
@ -54,11 +30,6 @@ export const setGuildMotdController: RequestHandler = async (req, res) => {
guild.MOTD = MOTD ?? ""; guild.MOTD = MOTD ?? "";
} }
await guild.save(); await guild.save();
}
if (!account.BuildLabel || version_compare(account.BuildLabel, "2020.03.24.20.24") > 0) {
res.json({ IsLongMOTD, MOTD }); res.json({ IsLongMOTD, MOTD });
} else {
res.send(MOTD).end();
}
}; };

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 { getAccountIdForRequest } from "@/src/services/loginService";
import { IPictureFrameInfo, ISetPlacedDecoInfoRequest } from "@/src/types/shipTypes"; import { ISetPlacedDecoInfoRequest } from "@/src/types/shipTypes";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { handleSetPlacedDecoInfo } from "@/src/services/shipCustomizationsService"; import { handleSetPlacedDecoInfo } from "@/src/services/shipCustomizationsService";
@ -7,17 +7,5 @@ export const setPlacedDecoInfoController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const payload = JSON.parse(req.body as string) as ISetPlacedDecoInfoRequest; const payload = JSON.parse(req.body as string) as ISetPlacedDecoInfoRequest;
await handleSetPlacedDecoInfo(accountId, payload); await handleSetPlacedDecoInfo(accountId, payload);
res.json({ res.end();
DecoId: payload.DecoId,
IsPicture: true,
PictureFrameInfo: payload.PictureFrameInfo,
BootLocation: payload.BootLocation
} satisfies ISetPlacedDecoInfoResponse);
}; };
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 { getPersonalRooms } from "@/src/services/personalRoomsService";
import { IOid } from "@/src/types/commonTypes"; import { IOid } from "@/src/types/commonTypes";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { IFavouriteLoadoutDatabase, TBootLocation } from "@/src/types/shipTypes";
export const setShipFavouriteLoadoutController: RequestHandler = async (req, res) => { export const setShipFavouriteLoadoutController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const personalRooms = await getPersonalRooms(accountId); const personalRooms = await getPersonalRooms(accountId);
const body = JSON.parse(String(req.body)) as ISetShipFavouriteLoadoutRequest; const body = JSON.parse(String(req.body)) as ISetShipFavouriteLoadoutRequest;
if (body.BootLocation == "LISET") { if (body.BootLocation != "SHOP") {
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);
throw new Error(`unexpected BootLocation: ${body.BootLocation}`); throw new Error(`unexpected BootLocation: ${body.BootLocation}`);
} }
await personalRooms.save(); const display = personalRooms.TailorShop.FavouriteLoadouts.find(x => x.Tag == body.TagName);
res.json(body);
};
interface ISetShipFavouriteLoadoutRequest {
BootLocation: TBootLocation;
FavouriteLoadoutId: IOid;
TagName?: string;
}
const updateTaggedDisplay = (arr: IFavouriteLoadoutDatabase[], body: ISetShipFavouriteLoadoutRequest): void => {
const display = arr.find(x => x.Tag == body.TagName!);
if (display) { if (display) {
display.LoadoutId = new Types.ObjectId(body.FavouriteLoadoutId.$oid); display.LoadoutId = new Types.ObjectId(body.FavouriteLoadoutId.$oid);
} else { } else {
arr.push({ personalRooms.TailorShop.FavouriteLoadouts.push({
Tag: body.TagName!, Tag: body.TagName,
LoadoutId: new Types.ObjectId(body.FavouriteLoadoutId.$oid) LoadoutId: new Types.ObjectId(body.FavouriteLoadoutId.$oid)
}); });
} }
await personalRooms.save();
res.json({});
}; };
interface ISetShipFavouriteLoadoutRequest {
BootLocation: string;
FavouriteLoadoutId: IOid;
TagName: string;
}

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

View File

@ -3,7 +3,7 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getRecipe } from "@/src/services/itemDataService"; import { getRecipe } from "@/src/services/itemDataService";
import { addItem, freeUpSlot, getInventory, updateCurrency } from "@/src/services/inventoryService"; import { addMiscItems, freeUpSlot, getInventory, updateCurrency } from "@/src/services/inventoryService";
import { unixTimesInMs } from "@/src/constants/timeConstants"; import { unixTimesInMs } from "@/src/constants/timeConstants";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { InventorySlot, ISpectreLoadout } from "@/src/types/inventoryTypes/inventoryTypes"; import { InventorySlot, ISpectreLoadout } from "@/src/types/inventoryTypes/inventoryTypes";
@ -55,7 +55,12 @@ export const startRecipeController: RequestHandler = async (req, res) => {
inventory[category].splice(equipmentIndex, 1); inventory[category].splice(equipmentIndex, 1);
freeUpSlot(inventory, InventorySlot.WEAPONS); freeUpSlot(inventory, InventorySlot.WEAPONS);
} else { } else {
await addItem(inventory, recipe.ingredients[i].ItemType, recipe.ingredients[i].ItemCount * -1); addMiscItems(inventory, [
{
ItemType: recipe.ingredients[i].ItemType,
ItemCount: recipe.ingredients[i].ItemCount * -1
}
]);
} }
} }
@ -90,6 +95,7 @@ export const startRecipeController: RequestHandler = async (req, res) => {
spectreLoadout.LongGuns = item.ItemType; spectreLoadout.LongGuns = item.ItemType;
spectreLoadout.LongGunsModularParts = item.ModularParts; spectreLoadout.LongGunsModularParts = item.ModularParts;
} else { } else {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
console.assert(type == "/Lotus/Types/Game/LotusMeleeWeapon"); console.assert(type == "/Lotus/Types/Game/LotusMeleeWeapon");
const item = inventory.Melee.id(oid)!; const item = inventory.Melee.id(oid)!;
spectreLoadout.Melee = item.ItemType; spectreLoadout.Melee = item.ItemType;
@ -110,8 +116,6 @@ export const startRecipeController: RequestHandler = async (req, res) => {
inventory.PendingSpectreLoadouts.push(spectreLoadout); inventory.PendingSpectreLoadouts.push(spectreLoadout);
logger.debug("pending spectre loadout", 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(); await inventory.save();

View File

@ -3,12 +3,15 @@ import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { ExportNightwave, ExportSyndicates, ISyndicateSacrifice } from "warframe-public-export-plus"; import { ExportNightwave, ExportSyndicates, ISyndicateSacrifice } from "warframe-public-export-plus";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService"; 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 { IInventoryChanges } from "@/src/types/purchaseTypes";
import { isStoreItem, toStoreItem } from "@/src/services/itemDataService"; import { fromStoreItem, isStoreItem } from "@/src/services/itemDataService";
import { logger } from "@/src/utils/logger";
const nightwaveCredsItemType = ExportNightwave.rewards[ExportNightwave.rewards.length - 1].uniqueName;
export const syndicateSacrificeController: RequestHandler = async (request, response) => { export const syndicateSacrificeController: RequestHandler = async (request, response) => {
const accountId = await getAccountIdForRequest(request); const accountId = await getAccountIdForRequest(request);
@ -54,7 +57,7 @@ export const syndicateSacrificeController: RequestHandler = async (request, resp
syndicate.Title ??= 0; syndicate.Title ??= 0;
syndicate.Title += 1; 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 ??= []; syndicate.FreeFavorsEarned ??= [];
if (!syndicate.FreeFavorsEarned.includes(syndicate.Title)) { if (!syndicate.FreeFavorsEarned.includes(syndicate.Title)) {
syndicate.FreeFavorsEarned.push(syndicate.Title); syndicate.FreeFavorsEarned.push(syndicate.Title);
@ -74,17 +77,10 @@ export const syndicateSacrificeController: RequestHandler = async (request, resp
res.NewEpisodeReward = true; res.NewEpisodeReward = true;
const reward = ExportNightwave.rewards[index]; const reward = ExportNightwave.rewards[index];
let rewardType = reward.uniqueName; let rewardType = reward.uniqueName;
if (!isStoreItem(rewardType)) { if (isStoreItem(rewardType)) {
rewardType = toStoreItem(rewardType); rewardType = fromStoreItem(rewardType);
} }
const rewardInventoryChanges = (await handleStoreItemAcquisition(rewardType, inventory, reward.itemCount)) combineInventoryChanges(res.InventoryChanges, await addItem(inventory, rewardType, reward.itemCount ?? 1));
.InventoryChanges;
if (Object.keys(rewardInventoryChanges).length == 0) {
logger.debug(`nightwave rank up reward did not seem to get added, giving 50 creds instead`);
rewardInventoryChanges.MiscItems = [{ ItemType: nightwaveCredsItemType, ItemCount: 50 }];
addMiscItems(inventory, rewardInventoryChanges.MiscItems);
}
combineInventoryChanges(res.InventoryChanges, rewardInventoryChanges);
} }
} }

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