Compare commits

..

No commits in common. "f796f9a85168b4bbd8d2cedee9e25a2894f87998" and "1a2d8ab19a07671e9f4e4a542755f839e6ed0356" have entirely different histories.

132 changed files with 1664 additions and 6568 deletions

View File

@ -1,12 +1,10 @@
{ {
"plugins": ["@typescript-eslint", "prettier", "import"],
"extends": [ "extends": [
"eslint:recommended", "eslint:recommended",
"plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking", "plugin:@typescript-eslint/recommended-requiring-type-checking"
"plugin:import/recommended",
"plugin:import/typescript"
], ],
"plugins": ["@typescript-eslint", "prettier"],
"env": { "env": {
"browser": true, "browser": true,
"es6": true, "es6": true,
@ -28,19 +26,11 @@
"no-case-declarations": "error", "no-case-declarations": "error",
"prettier/prettier": "error", "prettier/prettier": "error",
"no-mixed-spaces-and-tabs": "error", "no-mixed-spaces-and-tabs": "error",
"@typescript-eslint/require-await": "error", "require-await": "off",
"import/no-named-as-default-member": "off", "@typescript-eslint/require-await": "error"
"import/no-cycle": "warn"
}, },
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"parserOptions": { "parserOptions": {
"project": "./tsconfig.json" "project": "./tsconfig.json"
},
"settings": {
"import/extensions": [ ".ts" ],
"import/resolver": {
"typescript": true,
"node": true
}
} }
} }

View File

@ -19,7 +19,6 @@ jobs:
- run: npm run lint:ci - run: npm run lint:ci
- run: npm run prettier - run: npm run prettier
- run: npm run update-translations - run: npm run update-translations
- run: npm run fix-imports
- name: Fail if there are uncommitted changes - name: Fail if there are uncommitted changes
run: | run: |
if [[ -n "$(git status --porcelain)" ]]; then if [[ -n "$(git status --porcelain)" ]]; then

View File

@ -60,7 +60,6 @@
"unlockAllSimarisResearchEntries": false, "unlockAllSimarisResearchEntries": false,
"disableDailyTribute": false, "disableDailyTribute": false,
"spoofMasteryRank": -1, "spoofMasteryRank": -1,
"relicRewardItemCountMultiplier": 1,
"nightwaveStandingMultiplier": 1, "nightwaveStandingMultiplier": 1,
"unfaithfulBugFixes": { "unfaithfulBugFixes": {
"ignore1999LastRegionPlayed": false, "ignore1999LastRegionPlayed": false,
@ -78,9 +77,7 @@
"nightwaveOverride": "", "nightwaveOverride": "",
"allTheFissures": "", "allTheFissures": "",
"circuitGameModes": null, "circuitGameModes": null,
"darvoStockMultiplier": 1, "darvoStockMultiplier": 1
"varziaOverride": "",
"varziaFullyStocked": false
}, },
"dev": { "dev": {
"keepVendorsExpired": false "keepVendorsExpired": false

2196
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -19,9 +19,7 @@
"lint:ci": "eslint --ext .ts --rule \"prettier/prettier: off\" .", "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"
"fix-imports": "cd scripts && node fix-imports.js",
"fix": "npm run update-translations && npm run fix-imports && npm run prettier"
}, },
"license": "GNU", "license": "GNU",
"dependencies": { "dependencies": {
@ -39,7 +37,7 @@
"ncp": "^2.0.0", "ncp": "^2.0.0",
"typescript": "^5.5", "typescript": "^5.5",
"undici": "^7.10.0", "undici": "^7.10.0",
"warframe-public-export-plus": "^0.5.78", "warframe-public-export-plus": "^0.5.74",
"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",
@ -49,8 +47,6 @@
"@typescript-eslint/eslint-plugin": "^8.28.0", "@typescript-eslint/eslint-plugin": "^8.28.0",
"@typescript-eslint/parser": "^8.28.0", "@typescript-eslint/parser": "^8.28.0",
"eslint": "^8", "eslint": "^8",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-prettier": "^5.2.5", "eslint-plugin-prettier": "^5.2.5",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"tree-kill": "^1.2.2" "tree-kill": "^1.2.2"

View File

@ -1,46 +0,0 @@
/* eslint-disable */
const fs = require("fs");
const path = require("path");
const root = path.join(process.cwd(), "..");
function listFiles(dir) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
let results = [];
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
results = results.concat(listFiles(fullPath));
} else {
results.push(fullPath);
}
}
return results;
}
const files = listFiles(path.join(root, "src"));
for (const file of files) {
let content;
try {
content = fs.readFileSync(file, "utf8");
} catch (e) {
continue;
}
const dir = path.dirname(file);
const fixedContent = content.replaceAll(/} from "([^"]+)";/g, (sub, importPath) => {
if (!importPath.startsWith("@/")) {
const fullImportPath = path.resolve(dir, importPath);
if (fs.existsSync(fullImportPath + ".ts")) {
const relative = path.relative(root, fullImportPath).replace(/\\/g, "/");
const fixedPath = "@/" + relative;
console.log(`${importPath} -> ${fixedPath}`);
return sub.split(importPath).join(fixedPath);
}
}
return sub;
});
if (content != fixedContent) {
fs.writeFileSync(file, fixedContent, "utf8");
}
}

View File

@ -31,7 +31,7 @@ fs.readdirSync("../static/webui/translations").forEach(file => {
const strings = extractStrings(line); const strings = extractStrings(line);
if (Object.keys(strings).length > 0) { if (Object.keys(strings).length > 0) {
Object.entries(strings).forEach(([key, value]) => { Object.entries(strings).forEach(([key, value]) => {
if (targetStrings.hasOwnProperty(key) && !targetStrings[key].startsWith("[UNTRANSLATED] ")) { if (targetStrings.hasOwnProperty(key)) {
fs.writeSync(fileHandle, ` ${key}: \`${targetStrings[key]}\`,\n`); fs.writeSync(fileHandle, ` ${key}: \`${targetStrings[key]}\`,\n`);
} else { } else {
fs.writeSync(fileHandle, ` ${key}: \`[UNTRANSLATED] ${value}\`,\n`); fs.writeSync(fileHandle, ` ${key}: \`[UNTRANSLATED] ${value}\`,\n`);

View File

@ -24,6 +24,7 @@ export const artifactsController: RequestHandler = async (req, res) => {
if (itemIndex !== -1) { if (itemIndex !== -1) {
Upgrades[itemIndex].UpgradeFingerprint = stringifiedUpgradeFingerprint; Upgrades[itemIndex].UpgradeFingerprint = stringifiedUpgradeFingerprint;
inventory.markModified(`Upgrades.${itemIndex}.UpgradeFingerprint`);
} else { } else {
itemIndex = itemIndex =
Upgrades.push({ Upgrades.push({

View File

@ -14,17 +14,15 @@ import {
addRecipes, addRecipes,
occupySlot, occupySlot,
combineInventoryChanges, combineInventoryChanges,
addKubrowPetPrint, addKubrowPetPrint
addPowerSuit,
addEquipment
} from "@/src/services/inventoryService"; } from "@/src/services/inventoryService";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { InventorySlot, IPendingRecipeDatabase } from "@/src/types/inventoryTypes/inventoryTypes"; import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { InventorySlot, IPendingRecipeDatabase, Status } from "@/src/types/inventoryTypes/inventoryTypes";
import { toOid2 } from "@/src/helpers/inventoryHelpers"; import { toOid2 } from "@/src/helpers/inventoryHelpers";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { IRecipe } from "warframe-public-export-plus"; import { IRecipe } from "warframe-public-export-plus";
import { config } from "@/src/services/configService"; import { config } from "@/src/services/configService";
import { EquipmentFeatures, IEquipmentClient, Status } from "@/src/types/equipmentTypes";
interface IClaimCompletedRecipeRequest { interface IClaimCompletedRecipeRequest {
RecipeIds: IOid[]; RecipeIds: IOid[];
@ -126,122 +124,17 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
const pet = inventory.KubrowPets.id(pendingRecipe.KubrowPet!)!; const pet = inventory.KubrowPets.id(pendingRecipe.KubrowPet!)!;
addKubrowPetPrint(inventory, pet, InventoryChanges); addKubrowPetPrint(inventory, pet, InventoryChanges);
} else if (recipe.secretIngredientAction != "SIA_UNBRAND") { } else if (recipe.secretIngredientAction != "SIA_UNBRAND") {
if (recipe.resultType == "/Lotus/Powersuits/Excalibur/ExcaliburUmbra") { InventoryChanges = {
// Quite the special case here... ...InventoryChanges,
// We don't just get Umbra, but also Skiajati and Umbra Mods. Both items are max rank, potatoed, and with the mods are pre-installed. ...(await addItem(
// Source: https://wiki.warframe.com/w/The_Sacrifice, https://wiki.warframe.com/w/Excalibur/Umbra, https://wiki.warframe.com/w/Skiajati
const umbraModA = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModA",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
const umbraModB = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModB",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
const umbraModC = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModC",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
const sacrificeModA = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Sacrifice/MeleeSacrificeModA",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
const sacrificeModB = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Sacrifice/MeleeSacrificeModB",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
InventoryChanges.Upgrades ??= [];
InventoryChanges.Upgrades.push(umbraModA, umbraModB, umbraModC, sacrificeModA, sacrificeModB);
await addPowerSuit(
inventory, inventory,
"/Lotus/Powersuits/Excalibur/ExcaliburUmbra", recipe.resultType,
{ recipe.num,
Configs: [ false,
{ undefined,
Upgrades: [ pendingRecipe.TargetFingerprint
"", ))
"", };
"",
"",
"",
umbraModA.ItemId.$oid,
umbraModB.ItemId.$oid,
umbraModC.ItemId.$oid
]
}
],
XP: 900_000,
Features: EquipmentFeatures.DOUBLE_CAPACITY
},
InventoryChanges
);
inventory.XPInfo.push({
ItemType: "/Lotus/Powersuits/Excalibur/ExcaliburUmbra",
XP: 900_000
});
addEquipment(
inventory,
"Melee",
"/Lotus/Weapons/Tenno/Melee/Swords/UmbraKatana/UmbraKatana",
{
Configs: [
{ Upgrades: ["", "", "", "", "", "", sacrificeModA.ItemId.$oid, sacrificeModB.ItemId.$oid] }
],
XP: 450_000,
Features: EquipmentFeatures.DOUBLE_CAPACITY
},
InventoryChanges
);
inventory.XPInfo.push({
ItemType: "/Lotus/Weapons/Tenno/Melee/Swords/UmbraKatana/UmbraKatana",
XP: 450_000
});
} else {
InventoryChanges = {
...InventoryChanges,
...(await addItem(
inventory,
recipe.resultType,
recipe.num,
false,
undefined,
pendingRecipe.TargetFingerprint
))
};
}
} }
if ( if (
config.claimingBlueprintRefundsIngredients && config.claimingBlueprintRefundsIngredients &&

View File

@ -1,4 +1,4 @@
import { checkCalendarAutoAdvance, getCalendarProgress, getInventory } from "@/src/services/inventoryService"; import { checkCalendarChallengeCompletion, getCalendarProgress, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService"; import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { getWorldState } from "@/src/services/worldStateService"; import { getWorldState } from "@/src/services/worldStateService";
@ -28,7 +28,7 @@ export const completeCalendarEventController: RequestHandler = async (req, res)
} }
} }
calendarProgress.SeasonProgress.LastCompletedDayIdx = dayIndex; calendarProgress.SeasonProgress.LastCompletedDayIdx = dayIndex;
checkCalendarAutoAdvance(inventory, currentSeason); checkCalendarChallengeCompletion(calendarProgress, currentSeason);
await inventory.save(); await inventory.save();
res.json({ res.json({
InventoryChanges: inventoryChanges, InventoryChanges: inventoryChanges,

View File

@ -21,8 +21,7 @@ import {
updateCurrency updateCurrency
} from "@/src/services/inventoryService"; } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { ITypeCount } from "@/src/types/commonTypes"; import { IFusionTreasure, IMiscItem, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
import { IFusionTreasure, IMiscItem } 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) => {

View File

@ -4,15 +4,9 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
export const creditsController: RequestHandler = async (req, res) => { export const creditsController: RequestHandler = async (req, res) => {
const inventory = ( const accountId = await getAccountIdForRequest(req);
await Promise.all([
getAccountIdForRequest(req), const inventory = await getInventory(accountId, "RegularCredits TradesRemaining PremiumCreditsFree PremiumCredits");
getInventory(
req.query.accountId as string,
"RegularCredits TradesRemaining PremiumCreditsFree PremiumCredits"
)
])
)[1];
const response = { const response = {
RegularCredits: inventory.RegularCredits, RegularCredits: inventory.RegularCredits,

View File

@ -12,7 +12,7 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { getRandomInt } from "@/src/services/rngService"; import { getRandomInt } from "@/src/services/rngService";
import { IFingerprintStat } from "@/src/helpers/rivenHelper"; import { IFingerprintStat } from "@/src/helpers/rivenHelper";
import { IEquipmentDatabase } from "@/src/types/equipmentTypes"; import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
export const crewShipIdentifySalvageController: RequestHandler = async (req, res) => { export const crewShipIdentifySalvageController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);

View File

@ -1,15 +1,12 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Guild } from "@/src/models/guildModel"; import { Guild } from "@/src/models/guildModel";
import { hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService"; import { getAccountForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountForRequest, getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
export const customObstacleCourseLeaderboardController: RequestHandler = async (req, res) => { export const customObstacleCourseLeaderboardController: RequestHandler = async (req, res) => {
const data = getJSONfromString<ICustomObstacleCourseLeaderboardRequest>(String(req.body)); const data = getJSONfromString<ICustomObstacleCourseLeaderboardRequest>(String(req.body));
const guild = (await Guild.findById(data.g, "DojoComponents Ranks"))!; const guild = (await Guild.findById(data.g, "DojoComponents"))!;
const component = guild.DojoComponents.id(data.c)!; const component = guild.DojoComponents.id(data.c)!;
if (req.query.act == "f") { if (req.query.act == "f") {
res.json({ res.json({
@ -37,19 +34,6 @@ export const customObstacleCourseLeaderboardController: RequestHandler = async (
entry.r = ++r; entry.r = ++r;
} }
await guild.save(); await guild.save();
res.status(200).end();
} else if (req.query.act == "c") {
// TOVERIFY: What clan permission is actually needed for this?
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "GuildId LevelKeys");
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Decorator))) {
res.status(400).end();
return;
}
component.Leaderboard = undefined;
await guild.save();
res.status(200).end(); res.status(200).end();
} else { } else {
logger.debug(`data provided to ${req.path}: ${String(req.body)}`); logger.debug(`data provided to ${req.path}: ${String(req.body)}`);

View File

@ -3,7 +3,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
import { addMiscItems, getInventory } from "@/src/services/inventoryService"; import { addMiscItems, getInventory } from "@/src/services/inventoryService";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getRecipe, WeaponTypeInternal } from "@/src/services/itemDataService"; import { getRecipe, WeaponTypeInternal } from "@/src/services/itemDataService";
import { EquipmentFeatures } from "@/src/types/equipmentTypes"; import { EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
export const evolveWeaponController: RequestHandler = async (req, res) => { export const evolveWeaponController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);

View File

@ -4,6 +4,7 @@ import { getInventory, addMiscItems, addEquipment, occupySlot } from "@/src/serv
import { IMiscItem, TFocusPolarity, TEquipmentKey, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes"; import { IMiscItem, TFocusPolarity, TEquipmentKey, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { ExportFocusUpgrades } from "warframe-public-export-plus"; import { ExportFocusUpgrades } from "warframe-public-export-plus";
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { Inventory } from "@/src/models/inventoryModels/inventoryModel"; import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
export const focusController: RequestHandler = async (req, res) => { export const focusController: RequestHandler = async (req, res) => {
@ -115,7 +116,7 @@ export const focusController: RequestHandler = async (req, res) => {
}); });
occupySlot(inventory, InventorySlot.AMPS, false); occupySlot(inventory, InventorySlot.AMPS, false);
await inventory.save(); await inventory.save();
res.json(inventoryChanges.OperatorAmps![0]); res.json((inventoryChanges.OperatorAmps as IEquipmentClient[])[0]);
break; break;
} }
case FocusOperation.UnbindUpgrade: { case FocusOperation.UnbindUpgrade: {

View File

@ -6,8 +6,9 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService"; import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
import { IMongoDate } from "@/src/types/commonTypes"; import { IMongoDate } from "@/src/types/commonTypes";
import { IMissionReward } from "@/src/types/missionTypes"; import { IMissionReward } from "@/src/types/missionTypes";
import { IGardeningClient, IPersonalRoomsClient } from "@/src/types/personalRoomsTypes"; import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { IGardeningClient } from "@/src/types/shipTypes";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { dict_en, ExportResources } from "warframe-public-export-plus"; import { dict_en, ExportResources } from "warframe-public-export-plus";

View File

@ -1,6 +1,6 @@
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 { generateRewardSeed } from "@/src/services/rngService";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
export const getNewRewardSeedController: RequestHandler = async (req, res) => { export const getNewRewardSeedController: RequestHandler = async (req, res) => {

View File

@ -3,9 +3,10 @@ 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 { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
import { IGetShipResponse, IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
import { getLoadout } from "@/src/services/loadoutService";
import { toOid } from "@/src/helpers/inventoryHelpers"; import { toOid } from "@/src/helpers/inventoryHelpers";
import { IGetShipResponse } from "@/src/types/shipTypes";
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
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);
@ -25,7 +26,15 @@ export const getShipController: RequestHandler = async (req, res) => {
LoadOutInventory: { LoadOutPresets: loadout.toJSON() }, LoadOutInventory: { LoadOutPresets: loadout.toJSON() },
Ship: { Ship: {
...personalRooms.Ship, ...personalRooms.Ship,
ShipId: toOid(personalRoomsDb.activeShipId) ShipId: toOid(personalRoomsDb.activeShipId),
ShipInterior: {
Colors: personalRooms.ShipInteriorColors,
ShipAttachments: { HOOD_ORNAMENT: "" },
SkinFlavourItem: ""
},
FavouriteLoadoutId: personalRooms.Ship.FavouriteLoadoutId
? toOid(personalRooms.Ship.FavouriteLoadoutId)
: undefined
}, },
Apartment: personalRooms.Apartment, Apartment: personalRooms.Apartment,
TailorShop: personalRooms.TailorShop TailorShop: personalRooms.TailorShop

View File

@ -3,10 +3,9 @@ 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 { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import { ArtifactPolarity } 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";
import { EquipmentFeatures, IEquipmentClient } from "@/src/types/equipmentTypes";
interface IGildWeaponRequest { interface IGildWeaponRequest {
ItemName: string; ItemName: string;

View File

@ -1,8 +1,7 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { addLoreFragmentScans, addShipDecorations, getInventory } from "@/src/services/inventoryService"; import { addLoreFragmentScans, addShipDecorations, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { ITypeCount } from "@/src/types/commonTypes"; import { ILoreFragmentScan, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
import { ILoreFragmentScan } from "@/src/types/inventoryTypes/inventoryTypes";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
export const giveShipDecoAndLoreFragmentController: RequestHandler = async (req, res) => { export const giveShipDecoAndLoreFragmentController: RequestHandler = async (req, res) => {

View File

@ -5,7 +5,6 @@ import {
getGuildVault, getGuildVault,
hasAccessToDojo, hasAccessToDojo,
hasGuildPermission, hasGuildPermission,
processCompletedGuildTechProject,
processFundedGuildTechProject, processFundedGuildTechProject,
processGuildTechProjectContributionsUpdate, processGuildTechProjectContributionsUpdate,
removePigmentsFromGuildMembers, removePigmentsFromGuildMembers,
@ -52,12 +51,8 @@ export const guildTechController: RequestHandler = async (req, res) => {
}; };
if (project.CompletionDate) { if (project.CompletionDate) {
techProject.CompletionDate = toMongoDate(project.CompletionDate); techProject.CompletionDate = toMongoDate(project.CompletionDate);
if ( if (Date.now() >= project.CompletionDate.getTime()) {
Date.now() >= project.CompletionDate.getTime() && needSave ||= setGuildTechLogState(guild, project.ItemType, 4, project.CompletionDate);
setGuildTechLogState(guild, project.ItemType, 4, project.CompletionDate)
) {
processCompletedGuildTechProject(guild, project.ItemType);
needSave = true;
} }
} }
techProjects.push(techProject); techProjects.push(techProject);

View File

@ -5,25 +5,15 @@ import { config } from "@/src/services/configService";
import allDialogue from "@/static/fixed_responses/allDialogue.json"; import allDialogue from "@/static/fixed_responses/allDialogue.json";
import { ILoadoutDatabase } from "@/src/types/saveLoadoutTypes"; import { ILoadoutDatabase } from "@/src/types/saveLoadoutTypes";
import { IInventoryClient, IShipInventory, equipmentKeys } from "@/src/types/inventoryTypes/inventoryTypes"; import { IInventoryClient, IShipInventory, equipmentKeys } from "@/src/types/inventoryTypes/inventoryTypes";
import { IPolarity, ArtifactPolarity } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { IPolarity, ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { import { ExportCustoms, ExportFlavour, ExportResources, ExportVirtuals } from "warframe-public-export-plus";
eFaction,
ExportCustoms,
ExportFlavour,
ExportResources,
ExportVirtuals,
ICountedItem
} from "warframe-public-export-plus";
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "@/src/services/infestedFoundryService"; import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "@/src/services/infestedFoundryService";
import { import {
addEmailItem,
addItem,
addMiscItems, addMiscItems,
allDailyAffiliationKeys, allDailyAffiliationKeys,
checkCalendarAutoAdvance,
cleanupInventory, cleanupInventory,
createLibraryDailyTask, createLibraryDailyTask,
getCalendarProgress generateRewardSeed
} from "@/src/services/inventoryService"; } from "@/src/services/inventoryService";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { addString, catBreadHash } from "@/src/helpers/stringHelpers"; import { addString, catBreadHash } from "@/src/helpers/stringHelpers";
@ -36,10 +26,6 @@ import { toLegacyOid, toOid, version_compare } from "@/src/helpers/inventoryHelp
import { Inbox } from "@/src/models/inboxModel"; import { Inbox } from "@/src/models/inboxModel";
import { unixTimesInMs } from "@/src/constants/timeConstants"; import { unixTimesInMs } from "@/src/constants/timeConstants";
import { DailyDeal } from "@/src/models/worldStateModel"; import { DailyDeal } from "@/src/models/worldStateModel";
import { EquipmentFeatures } from "@/src/types/equipmentTypes";
import { generateRewardSeed } from "@/src/services/rngService";
import { getInvasionByOid, getWorldState } from "@/src/services/worldStateService";
import { createMessage } from "@/src/services/inboxService";
export const inventoryController: RequestHandler = async (request, response) => { export const inventoryController: RequestHandler = async (request, response) => {
const account = await getAccountForRequest(request); const account = await getAccountForRequest(request);
@ -122,64 +108,6 @@ export const inventoryController: RequestHandler = async (request, response) =>
} }
} }
// TODO: Setup CalendarProgress as part of 1999 mission completion?
const previousYearIteration = inventory.CalendarProgress?.Iteration;
// We need to do the following to ensure the in-game calendar does not break:
getCalendarProgress(inventory); // Keep the CalendarProgress up-to-date (at least for the current year iteration) (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2364)
checkCalendarAutoAdvance(inventory, getWorldState().KnownCalendarSeasons[0]); // Skip birthday events for characters if we do not have them unlocked yet (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2424)
// also handle sending of kiss cinematic at year rollover
if (
inventory.CalendarProgress!.Iteration != previousYearIteration &&
inventory.DialogueHistory &&
inventory.DialogueHistory.Dialogues
) {
let kalymos = false;
for (const { dialogueName, kissEmail } of [
{
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/ArthurDialogue_rom.dialogue",
kissEmail: "/Lotus/Types/Items/EmailItems/ArthurKissEmailItem"
},
{
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/EleanorDialogue_rom.dialogue",
kissEmail: "/Lotus/Types/Items/EmailItems/EleanorKissEmailItem"
},
{
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/LettieDialogue_rom.dialogue",
kissEmail: "/Lotus/Types/Items/EmailItems/LettieKissEmailItem"
},
{
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/JabirDialogue_rom.dialogue",
kissEmail: "/Lotus/Types/Items/EmailItems/AmirKissEmailItem"
},
{
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/AoiDialogue_rom.dialogue",
kissEmail: "/Lotus/Types/Items/EmailItems/AoiKissEmailItem"
},
{
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/QuincyDialogue_rom.dialogue",
kissEmail: "/Lotus/Types/Items/EmailItems/QuincyKissEmailItem"
}
]) {
const dialogue = inventory.DialogueHistory.Dialogues.find(x => x.DialogueName == dialogueName);
if (dialogue) {
if (dialogue.Rank == 7) {
await addEmailItem(inventory, kissEmail);
kalymos = false;
break;
}
if (dialogue.Rank == 6) {
kalymos = true;
}
}
}
if (kalymos) {
await addEmailItem(inventory, "/Lotus/Types/Items/EmailItems/KalymosKissEmailItem");
}
}
cleanupInventory(inventory); cleanupInventory(inventory);
inventory.NextRefill = new Date((today + 1) * 86400000); // tomorrow at 0 UTC inventory.NextRefill = new Date((today + 1) * 86400000); // tomorrow at 0 UTC
@ -195,63 +123,6 @@ export const inventoryController: RequestHandler = async (request, response) =>
//await inventory.save(); //await inventory.save();
} }
for (let i = 0; i != inventory.QualifyingInvasions.length; ) {
const qi = inventory.QualifyingInvasions[i];
const invasion = getInvasionByOid(qi.invasionId.toString());
if (!invasion) {
logger.debug(`removing QualifyingInvasions entry for unknown invasion: ${qi.invasionId.toString()}`);
inventory.QualifyingInvasions.splice(i, 1);
continue;
}
if (invasion.Completed) {
let factionSidedWith: string | undefined;
let battlePay: ICountedItem[] | undefined;
if (qi.AttackerScore >= 3) {
factionSidedWith = invasion.Faction;
battlePay = invasion.AttackerReward.countedItems;
logger.debug(`invasion pay from ${factionSidedWith}`, { battlePay });
} else if (qi.DefenderScore >= 3) {
factionSidedWith = invasion.DefenderFaction;
battlePay = invasion.DefenderReward.countedItems;
logger.debug(`invasion pay from ${factionSidedWith}`, { battlePay });
}
if (factionSidedWith) {
if (battlePay) {
// Decoupling rewards from the inbox message because it may delete itself without being read
for (const item of battlePay) {
await addItem(inventory, item.ItemType, item.ItemCount);
}
await createMessage(account._id, [
{
sndr: eFaction.find(x => x.tag == factionSidedWith)?.name ?? factionSidedWith, // TOVERIFY
msg: `/Lotus/Language/G1Quests/${factionSidedWith}_InvasionThankyouMessageBody`,
sub: `/Lotus/Language/G1Quests/${factionSidedWith}_InvasionThankyouMessageSubject`,
countedAtt: battlePay,
attVisualOnly: true,
icon:
factionSidedWith == "FC_GRINEER"
? "/Lotus/Interface/Icons/Npcs/EliteRifleLancerAvatar.png" // Source: https://www.reddit.com/r/Warframe/comments/1aj4usx/battle_pay_worth_10_plat/, https://www.youtube.com/watch?v=XhNZ6ai6BOY
: "/Lotus/Interface/Icons/Npcs/CrewmanNormal.png", // My best source for this is https://www.youtube.com/watch?v=rxrCCFm73XE around 1:37
// TOVERIFY: highPriority?
endDate: new Date(Date.now() + 86400_000) // TOVERIFY: This type of inbox message seems to automatically delete itself. We'll just delete it after 24 hours, but it's not clear if this is correct.
}
]);
}
if (invasion.Faction != "FC_INFESTATION") {
// Sided with grineer -> opposed corpus -> send zanuka (harvester)
// Sided with corpus -> opposed grineer -> send g3 (death squad)
inventory[factionSidedWith != "FC_GRINEER" ? "DeathSquadable" : "Harvestable"] = true;
// TOVERIFY: Should this happen earlier?
// TOVERIFY: Should this send an (ephemeral) email?
}
}
logger.debug(`removing QualifyingInvasions entry for completed invasion: ${qi.invasionId.toString()}`);
inventory.QualifyingInvasions.splice(i, 1);
continue;
}
++i;
}
if (inventory.LastInventorySync) { if (inventory.LastInventorySync) {
const lastSyncDuviriMood = Math.trunc(inventory.LastInventorySync.getTimestamp().getTime() / 7200000); const lastSyncDuviriMood = Math.trunc(inventory.LastInventorySync.getTimestamp().getTime() / 7200000);
const currentDuviriMood = Math.trunc(Date.now() / 7200000); const currentDuviriMood = Math.trunc(Date.now() / 7200000);

View File

@ -1,8 +1,9 @@
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory, updateCurrency, updateSlots } from "@/src/services/inventoryService"; import { getInventory, updateCurrency } from "@/src/services/inventoryService";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { updateSlots } from "@/src/services/inventoryService";
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes"; import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
import { exhaustive } from "@/src/utils/ts-utils"; import { logger } from "@/src/utils/logger";
/* /*
loadout slots are additionally purchased slots only loadout slots are additionally purchased slots only
@ -22,44 +23,13 @@ export const inventorySlotsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const body = JSON.parse(req.body as string) as IInventorySlotsRequest; const body = JSON.parse(req.body as string) as IInventorySlotsRequest;
let price; if (body.Bin != InventorySlot.SUITS && body.Bin != InventorySlot.PVE_LOADOUTS) {
let amount; logger.warn(`unexpected slot purchase of type ${body.Bin}, account may be overcharged`);
switch (body.Bin) {
case InventorySlot.SUITS:
case InventorySlot.MECHSUITS:
case InventorySlot.PVE_LOADOUTS:
case InventorySlot.CREWMEMBERS:
price = 20;
amount = 1;
break;
case InventorySlot.SPACESUITS:
price = 12;
amount = 1;
break;
case InventorySlot.WEAPONS:
case InventorySlot.SPACEWEAPONS:
case InventorySlot.SENTINELS:
case InventorySlot.RJ_COMPONENT_AND_ARMAMENTS:
case InventorySlot.AMPS:
price = 12;
amount = 2;
break;
case InventorySlot.RIVENS:
price = 60;
amount = 3;
break;
default:
exhaustive(body.Bin);
throw new Error(`unexpected slot purchase of type ${body.Bin as string}`);
} }
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
const currencyChanges = updateCurrency(inventory, price, true); const currencyChanges = updateCurrency(inventory, 20, true);
updateSlots(inventory, body.Bin, amount, amount); updateSlots(inventory, body.Bin, 1, 1);
await inventory.save(); await inventory.save();
res.json({ InventoryChanges: currencyChanges }); res.json({ InventoryChanges: currencyChanges });

View File

@ -8,7 +8,7 @@ import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } f
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/helpers/inventoryHelpers"; import { version_compare } from "@/src/helpers/inventoryHelpers";
import { sendWsBroadcastTo } from "@/src/services/wsService"; import { sendWsBroadcastTo } from "@/src/services/webService";
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

View File

@ -9,7 +9,6 @@ import {
} from "@/src/services/loginRewardService"; } from "@/src/services/loginRewardService";
import { getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
import { config } from "@/src/services/configService"; import { config } from "@/src/services/configService";
import { sendWsBroadcastTo } from "@/src/services/wsService";
export const loginRewardsController: RequestHandler = async (req, res) => { export const loginRewardsController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req); const account = await getAccountForRequest(req);
@ -48,10 +47,10 @@ export const loginRewardsController: RequestHandler = async (req, res) => {
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(inventory, randomRewards[0]);
setAccountGotLoginRewardToday(account); await inventory.save();
await Promise.all([inventory.save(), account.save()]);
sendWsBroadcastTo(account._id.toString(), { update_inventory: true }); setAccountGotLoginRewardToday(account);
await account.save();
} }
res.json(response); res.json(response);
}; };

View File

@ -6,7 +6,6 @@ import {
} from "@/src/services/loginRewardService"; } 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 { sendWsBroadcastTo } from "@/src/services/wsService";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
@ -35,10 +34,11 @@ export const loginRewardsSelectionController: RequestHandler = async (req, res)
chosenReward = randomRewards.find(x => x.StoreItemType == body.ChosenReward)!; chosenReward = randomRewards.find(x => x.StoreItemType == body.ChosenReward)!;
inventoryChanges = await claimLoginReward(inventory, chosenReward); inventoryChanges = await claimLoginReward(inventory, chosenReward);
} }
setAccountGotLoginRewardToday(account); await inventory.save();
await Promise.all([inventory.save(), account.save()]);
setAccountGotLoginRewardToday(account);
await account.save();
sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
res.json({ res.json({
DailyTributeInfo: { DailyTributeInfo: {
NewInventory: inventoryChanges, NewInventory: inventoryChanges,

View File

@ -1,6 +1,6 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { Account } from "@/src/models/loginModel"; import { Account } from "@/src/models/loginModel";
import { sendWsBroadcastTo } from "@/src/services/wsService"; import { sendWsBroadcastTo } from "@/src/services/webService";
export const logoutController: RequestHandler = async (req, res) => { export const logoutController: RequestHandler = async (req, res) => {
if (!req.query.accountId) { if (!req.query.accountId) {

View File

@ -3,12 +3,11 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getAccountForRequest } from "@/src/services/loginService"; import { getAccountForRequest } 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 { getInventory } from "@/src/services/inventoryService"; import { generateRewardSeed, getInventory } from "@/src/services/inventoryService";
import { getInventoryResponse } from "@/src/controllers/api/inventoryController"; import { getInventoryResponse } from "./inventoryController";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { IMissionInventoryUpdateResponse } from "@/src/types/missionTypes"; import { IMissionInventoryUpdateResponse } from "@/src/types/missionTypes";
import { sendWsBroadcastTo } from "@/src/services/wsService"; import { sendWsBroadcastTo } from "@/src/services/webService";
import { generateRewardSeed } from "@/src/services/rngService";
/* /*
**** INPUT **** **** INPUT ****

View File

@ -15,9 +15,10 @@ import {
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 { getRandomInt } from "@/src/services/rngService";
import { ExportSentinels, ExportWeapons, IDefaultUpgrade } from "warframe-public-export-plus"; import { ExportSentinels, ExportWeapons, IDefaultUpgrade } from "warframe-public-export-plus";
import { IEquipmentDatabase, Status } from "@/src/types/equipmentTypes"; import { Status } from "@/src/types/inventoryTypes/inventoryTypes";
interface IModularCraftRequest { interface IModularCraftRequest {
WeaponType: string; WeaponType: string;

View File

@ -3,7 +3,7 @@ import { ExportWeapons } from "warframe-public-export-plus";
import { IMongoDate } from "@/src/types/commonTypes"; import { IMongoDate } from "@/src/types/commonTypes";
import { toMongoDate } from "@/src/helpers/inventoryHelpers"; import { toMongoDate } from "@/src/helpers/inventoryHelpers";
import { SRng } from "@/src/services/rngService"; import { SRng } from "@/src/services/rngService";
import { ArtifactPolarity } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { import {
addEquipment, addEquipment,
@ -17,7 +17,6 @@ import { getDefaultUpgrades } from "@/src/services/itemDataService";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper"; import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { EquipmentFeatures } from "@/src/types/equipmentTypes";
export const modularWeaponSaleController: RequestHandler = async (req, res) => { export const modularWeaponSaleController: RequestHandler = async (req, res) => {
const partTypeToParts: Record<string, string[]> = {}; const partTypeToParts: Record<string, string[]> = {};

View File

@ -3,7 +3,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory, updateCurrency } from "@/src/services/inventoryService"; import { getInventory, updateCurrency } from "@/src/services/inventoryService";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes"; import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import { sendWsBroadcastTo } from "@/src/services/wsService"; import { sendWsBroadcastTo } from "@/src/services/webService";
interface INameWeaponRequest { interface INameWeaponRequest {
ItemName: string; ItemName: string;

View File

@ -1,6 +1,7 @@
import { version_compare } from "@/src/helpers/inventoryHelpers"; import { version_compare } from "@/src/helpers/inventoryHelpers";
import { import {
antivirusMods, antivirusMods,
consumeModCharge,
decodeNemesisGuess, decodeNemesisGuess,
encodeNemesisGuess, encodeNemesisGuess,
getInfNodes, getInfNodes,
@ -16,13 +17,12 @@ import {
parseUpgrade parseUpgrade
} from "@/src/helpers/nemesisHelpers"; } from "@/src/helpers/nemesisHelpers";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { Loadout } from "@/src/models/inventoryModels/loadoutModel"; import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
import { addMods, freeUpSlot, getInventory } from "@/src/services/inventoryService"; import { freeUpSlot, getInventory } from "@/src/services/inventoryService";
import { getAccountForRequest } from "@/src/services/loginService"; import { getAccountForRequest } 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/equipmentTypes"; import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { import {
IInnateDamageFingerprint, IInnateDamageFingerprint,
IInventoryClient, IInventoryClient,
@ -36,7 +36,6 @@ import {
} from "@/src/types/inventoryTypes/inventoryTypes"; } 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";
import { Types } from "mongoose";
export const nemesisController: RequestHandler = async (req, res) => { export const nemesisController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req); const account = await getAccountForRequest(req);
@ -392,54 +391,3 @@ interface IKnife {
AttachedUpgrades: IUpgradeClient[]; AttachedUpgrades: IUpgradeClient[];
HiddenWhenHolstered: boolean; HiddenWhenHolstered: boolean;
} }
const consumeModCharge = (
response: IKnifeResponse,
inventory: TInventoryDatabaseDocument,
upgrade: { ItemId: IOid; ItemType: string },
dataknifeUpgrades: string[]
): void => {
response.UpgradeIds ??= [];
response.UpgradeTypes ??= [];
response.UpgradeFingerprints ??= [];
response.UpgradeNew ??= [];
response.HasKnife = true;
if (upgrade.ItemId.$oid != "000000000000000000000000") {
const dbUpgrade = inventory.Upgrades.id(upgrade.ItemId.$oid)!;
const fingerprint = JSON.parse(dbUpgrade.UpgradeFingerprint!) as { lvl: number };
fingerprint.lvl += 1;
dbUpgrade.UpgradeFingerprint = JSON.stringify(fingerprint);
response.UpgradeIds.push(upgrade.ItemId.$oid);
response.UpgradeTypes.push(upgrade.ItemType);
response.UpgradeFingerprints.push(fingerprint);
response.UpgradeNew.push(false);
} else {
const id = new Types.ObjectId();
inventory.Upgrades.push({
_id: id,
ItemType: upgrade.ItemType,
UpgradeFingerprint: `{"lvl":1}`
});
addMods(inventory, [
{
ItemType: upgrade.ItemType,
ItemCount: -1
}
]);
const dataknifeRawUpgradeIndex = dataknifeUpgrades.indexOf(upgrade.ItemType);
if (dataknifeRawUpgradeIndex != -1) {
dataknifeUpgrades[dataknifeRawUpgradeIndex] = id.toString();
} else {
logger.warn(`${upgrade.ItemType} not found in dataknife config`);
}
response.UpgradeIds.push(id.toString());
response.UpgradeTypes.push(upgrade.ItemType);
response.UpgradeFingerprints.push({ lvl: 1 });
response.UpgradeNew.push(true);
}
};

View File

@ -57,12 +57,8 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
component.DecoCapacity -= meta.capacityCost; component.DecoCapacity -= meta.capacityCost;
} }
} else { } else {
const entry = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type); const [itemType, meta] = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)!;
if (!entry) { if (!itemType || meta.dojoCapacityCost === undefined) {
throw new Error(`unknown deco type: ${deco.Type}`);
}
const [itemType, meta] = entry;
if (meta.dojoCapacityCost === undefined) {
throw new Error(`unknown deco type: ${deco.Type}`); throw new Error(`unknown deco type: ${deco.Type}`);
} }
component.DecoCapacity -= meta.dojoCapacityCost; component.DecoCapacity -= meta.dojoCapacityCost;
@ -79,13 +75,7 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
if (meta) { if (meta) {
processDojoBuildMaterialsGathered(guild, meta); processDojoBuildMaterialsGathered(guild, meta);
} }
} else if ( } else if (guild.AutoContributeFromVault && guild.VaultRegularCredits && guild.VaultMiscItems) {
deco.Type.startsWith("/Lotus/Objects/Tenno/Dojo/NpcPlaceables/") ||
(guild.AutoContributeFromVault && guild.VaultRegularCredits && guild.VaultMiscItems)
) {
if (!guild.VaultRegularCredits || !guild.VaultMiscItems) {
throw new Error(`dojo visitor placed without anything in vault?!`);
}
if (guild.VaultRegularCredits >= scaleRequiredCount(guild.Tier, meta.price)) { if (guild.VaultRegularCredits >= scaleRequiredCount(guild.Tier, meta.price)) {
let enoughMiscItems = true; let enoughMiscItems = true;
for (const ingredient of meta.ingredients) { for (const ingredient of meta.ingredients) {

View File

@ -1,39 +1,25 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { addConsumables, getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { IPlayerSkills } from "@/src/types/inventoryTypes/inventoryTypes"; import { IPlayerSkills } from "@/src/types/inventoryTypes/inventoryTypes";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
export const playerSkillsController: RequestHandler = async (req, res) => { export const playerSkillsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "PlayerSkills Consumables"); const inventory = await getInventory(accountId, "PlayerSkills");
const request = getJSONfromString<IPlayerSkillsRequest>(String(req.body)); const request = getJSONfromString<IPlayerSkillsRequest>(String(req.body));
const oldRank: number = inventory.PlayerSkills[request.Skill as keyof IPlayerSkills]; const oldRank: number = inventory.PlayerSkills[request.Skill as keyof IPlayerSkills];
const cost = (request.Pool == "LPP_DRIFTER" ? drifterCosts[oldRank] : 1 << oldRank) * 1000; const cost = (request.Pool == "LPP_DRIFTER" ? drifterCosts[oldRank] : 1 << oldRank) * 1000;
inventory.PlayerSkills[request.Pool as keyof IPlayerSkills] -= cost; inventory.PlayerSkills[request.Pool as keyof IPlayerSkills] -= cost;
inventory.PlayerSkills[request.Skill as keyof IPlayerSkills]++; inventory.PlayerSkills[request.Skill as keyof IPlayerSkills]++;
const inventoryChanges: IInventoryChanges = {};
if (request.Skill == "LPS_COMMAND" && inventory.PlayerSkills.LPS_COMMAND == 9) {
const consumablesChanges = [
{
ItemType: "/Lotus/Types/Restoratives/Consumable/CrewmateBall",
ItemCount: 1
}
];
addConsumables(inventory, consumablesChanges);
inventoryChanges.Consumables = consumablesChanges;
}
await inventory.save(); await inventory.save();
res.json({ res.json({
Pool: request.Pool, Pool: request.Pool,
PoolInc: -cost, PoolInc: -cost,
Skill: request.Skill, Skill: request.Skill,
Rank: oldRank + 1, Rank: oldRank + 1
InventoryChanges: inventoryChanges
}); });
}; };

View File

@ -3,7 +3,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
import { IPurchaseRequest } from "@/src/types/purchaseTypes"; import { IPurchaseRequest } from "@/src/types/purchaseTypes";
import { handlePurchase } from "@/src/services/purchaseService"; import { handlePurchase } from "@/src/services/purchaseService";
import { getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
import { sendWsBroadcastTo } from "@/src/services/wsService"; import { sendWsBroadcastTo } from "@/src/services/webService";
export const purchaseController: RequestHandler = async (req, res) => { export const purchaseController: RequestHandler = async (req, res) => {
const purchaseRequest = JSON.parse(String(req.body)) as IPurchaseRequest; const purchaseRequest = JSON.parse(String(req.body)) as IPurchaseRequest;

View File

@ -1,7 +1,7 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
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";
import { sendWsBroadcastTo } from "@/src/services/wsService"; import { sendWsBroadcastTo } from "@/src/services/webService";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { RequestHandler } from "express"; import { RequestHandler } from "express";

View File

@ -1,5 +0,0 @@
import { RequestHandler } from "express";
export const resetQuestProgressController: RequestHandler = (_req, res) => {
res.send("1").end();
};

View File

@ -1,7 +1,7 @@
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 { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { Status } from "@/src/types/equipmentTypes"; import { Status } from "@/src/types/inventoryTypes/inventoryTypes";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
export const retrievePetFromStasisController: RequestHandler = async (req, res) => { export const retrievePetFromStasisController: RequestHandler = async (req, res) => {

View File

@ -2,7 +2,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { ISettings } from "@/src/types/inventoryTypes/inventoryTypes"; import { ISettings } from "../../types/inventoryTypes/inventoryTypes";
interface ISaveSettingsRequest { interface ISaveSettingsRequest {
Settings: ISettings; Settings: ISettings;

View File

@ -15,11 +15,10 @@ import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
import { ExportDojoRecipes } from "warframe-public-export-plus"; import { ExportDojoRecipes } from "warframe-public-export-plus";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { sendWsBroadcastTo } from "@/src/services/wsService"; import { sendWsBroadcastTo } from "@/src/services/webService";
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;
//console.log(JSON.stringify(payload, null, 2));
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const requiredFields = new Set<keyof TInventoryDatabaseDocument>(); const requiredFields = new Set<keyof TInventoryDatabaseDocument>();
if (payload.SellCurrency == "SC_RegularCredits") { if (payload.SellCurrency == "SC_RegularCredits") {
@ -59,9 +58,6 @@ export const sellController: RequestHandler = async (req, res) => {
if (payload.Items.Hoverboards) { if (payload.Items.Hoverboards) {
requiredFields.add(InventorySlot.SPACESUITS); requiredFields.add(InventorySlot.SPACESUITS);
} }
if (payload.Items.CrewMembers) {
requiredFields.add(InventorySlot.CREWMEMBERS);
}
if (payload.Items.CrewShipWeapons || payload.Items.CrewShipWeaponSkins) { if (payload.Items.CrewShipWeapons || payload.Items.CrewShipWeaponSkins) {
requiredFields.add(InventorySlot.RJ_COMPONENT_AND_ARMAMENTS); requiredFields.add(InventorySlot.RJ_COMPONENT_AND_ARMAMENTS);
requiredFields.add("CrewShipRawSalvage"); requiredFields.add("CrewShipRawSalvage");
@ -185,17 +181,6 @@ export const sellController: RequestHandler = async (req, res) => {
inventory.Drones.pull({ _id: sellItem.String }); inventory.Drones.pull({ _id: sellItem.String });
}); });
} }
if (payload.Items.KubrowPetPrints) {
payload.Items.KubrowPetPrints.forEach(sellItem => {
inventory.KubrowPetPrints.pull({ _id: sellItem.String });
});
}
if (payload.Items.CrewMembers) {
payload.Items.CrewMembers.forEach(sellItem => {
inventory.CrewMembers.pull({ _id: sellItem.String });
freeUpSlot(inventory, InventorySlot.CREWMEMBERS);
});
}
if (payload.Items.CrewShipWeapons) { if (payload.Items.CrewShipWeapons) {
payload.Items.CrewShipWeapons.forEach(sellItem => { payload.Items.CrewShipWeapons.forEach(sellItem => {
if (sellItem.String[0] == "/") { if (sellItem.String[0] == "/") {
@ -318,8 +303,6 @@ interface ISellRequest {
OperatorAmps?: ISellItem[]; OperatorAmps?: ISellItem[];
Hoverboards?: ISellItem[]; Hoverboards?: ISellItem[];
Drones?: ISellItem[]; Drones?: ISellItem[];
KubrowPetPrints?: ISellItem[];
CrewMembers?: ISellItem[];
CrewShipWeapons?: ISellItem[]; CrewShipWeapons?: ISellItem[];
CrewShipWeaponSkins?: ISellItem[]; CrewShipWeaponSkins?: ISellItem[];
}; };

View File

@ -1,7 +1,7 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { getPersonalRooms } from "@/src/services/personalRoomsService"; import { getPersonalRooms } from "@/src/services/personalRoomsService";
import { TBootLocation } from "@/src/types/personalRoomsTypes"; import { TBootLocation } from "@/src/types/shipTypes";
import { getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
export const setBootLocationController: RequestHandler = async (req, res) => { export const setBootLocationController: RequestHandler = async (req, res) => {

View File

@ -1,5 +1,5 @@
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { IPictureFrameInfo, ISetPlacedDecoInfoRequest } from "@/src/types/personalRoomsTypes"; import { IPictureFrameInfo, 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";

View File

@ -1,6 +1,6 @@
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { setShipCustomizations } from "@/src/services/shipCustomizationsService"; import { setShipCustomizations } from "@/src/services/shipCustomizationsService";
import { ISetShipCustomizationsRequest } from "@/src/types/personalRoomsTypes"; import { ISetShipCustomizationsRequest } from "@/src/types/shipTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express"; import { RequestHandler } from "express";

View File

@ -3,7 +3,7 @@ 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/personalRoomsTypes"; 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);

View File

@ -2,7 +2,7 @@ import { fromMongoDate, fromOid } from "@/src/helpers/inventoryHelpers";
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 { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { IEquipmentClient } from "@/src/types/equipmentTypes"; import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
export const setSuitInfectionController: RequestHandler = async (req, res) => { export const setSuitInfectionController: RequestHandler = async (req, res) => {

View File

@ -1,5 +1,5 @@
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { IShipDecorationsRequest } from "@/src/types/personalRoomsTypes"; import { IShipDecorationsRequest } from "@/src/types/shipTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { handleSetShipDecorations } from "@/src/services/shipCustomizationsService"; import { handleSetShipDecorations } from "@/src/services/shipCustomizationsService";

View File

@ -6,7 +6,7 @@ import { IOid } from "@/src/types/commonTypes";
import { ExportSyndicates, ExportWeapons } from "warframe-public-export-plus"; import { ExportSyndicates, ExportWeapons } from "warframe-public-export-plus";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { IAffiliationMods, IInventoryChanges } from "@/src/types/purchaseTypes"; import { IAffiliationMods, IInventoryChanges } from "@/src/types/purchaseTypes";
import { EquipmentFeatures } from "@/src/types/equipmentTypes"; import { EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
export const syndicateStandingBonusController: RequestHandler = async (req, res) => { export const syndicateStandingBonusController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);

View File

@ -2,7 +2,7 @@ import { fromMongoDate, fromOid } from "@/src/helpers/inventoryHelpers";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { addMiscItem, getInventory } from "@/src/services/inventoryService"; import { addMiscItem, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { IEquipmentClient } from "@/src/types/equipmentTypes"; import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
export const umbraController: RequestHandler = async (req, res) => { export const umbraController: RequestHandler = async (req, res) => {

View File

@ -1,6 +1,11 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { IUpgradesRequest } from "@/src/types/requestTypes"; import { IUpgradesRequest } from "@/src/types/requestTypes";
import { ArtifactPolarity, IAbilityOverride } from "@/src/types/inventoryTypes/commonInventoryTypes"; import {
ArtifactPolarity,
IEquipmentDatabase,
EquipmentFeatures,
IAbilityOverride
} from "@/src/types/inventoryTypes/commonInventoryTypes";
import { IInventoryClient, IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes"; import { IInventoryClient, IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { addMiscItems, addRecipes, getInventory, updateCurrency } from "@/src/services/inventoryService"; import { addMiscItems, addRecipes, getInventory, updateCurrency } from "@/src/services/inventoryService";
@ -8,8 +13,6 @@ import { getRecipeByResult } from "@/src/services/itemDataService";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { addInfestedFoundryXP, applyCheatsToInfestedFoundry } from "@/src/services/infestedFoundryService"; import { addInfestedFoundryXP, applyCheatsToInfestedFoundry } from "@/src/services/infestedFoundryService";
import { config } from "@/src/services/configService"; import { config } from "@/src/services/configService";
import { sendWsBroadcastTo } from "@/src/services/wsService";
import { EquipmentFeatures, IEquipmentDatabase } from "@/src/types/equipmentTypes";
export const upgradesController: RequestHandler = async (req, res) => { export const upgradesController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -117,7 +120,6 @@ export const upgradesController: RequestHandler = async (req, res) => {
setSlotPolarity(item, operation.PolarizeSlot, operation.PolarizeValue); setSlotPolarity(item, operation.PolarizeSlot, operation.PolarizeValue);
item.Polarized ??= 0; item.Polarized ??= 0;
item.Polarized += 1; item.Polarized += 1;
sendWsBroadcastTo(accountId, { update_inventory: true }); // webui may need to to re-add "max rank" button
break; break;
} }
case "/Lotus/Types/Items/MiscItems/ModSlotUnlocker": { case "/Lotus/Types/Items/MiscItems/ModSlotUnlocker": {

View File

@ -7,7 +7,7 @@ export const addItemsController: RequestHandler = async (req, res) => {
const requests = req.body as IAddItemRequest[]; const requests = req.body as IAddItemRequest[];
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
for (const request of requests) { for (const request of requests) {
await addItem(inventory, request.ItemType, request.ItemCount, true, undefined, request.Fingerprint, true); await addItem(inventory, request.ItemType, request.ItemCount, true, undefined, undefined, true);
} }
await inventory.save(); await inventory.save();
res.end(); res.end();
@ -16,5 +16,4 @@ export const addItemsController: RequestHandler = async (req, res) => {
interface IAddItemRequest { interface IAddItemRequest {
ItemType: string; ItemType: string;
ItemCount: number; ItemCount: number;
Fingerprint?: string;
} }

View File

@ -1,7 +1,7 @@
import { applyClientEquipmentUpdates, getInventory } from "@/src/services/inventoryService"; import { applyClientEquipmentUpdates, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { IOid } from "@/src/types/commonTypes"; import { IOid } from "@/src/types/commonTypes";
import { IEquipmentClient } from "@/src/types/equipmentTypes"; import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes"; import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { ExportMisc } from "warframe-public-export-plus"; import { ExportMisc } from "warframe-public-export-plus";

View File

@ -1,8 +1,8 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { config } from "@/src/services/configService"; import { config } from "@/src/services/configService";
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService"; import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
import { saveConfig } from "@/src/services/configWriterService"; import { saveConfig } from "@/src/services/configWatcherService";
import { sendWsBroadcastExcept } from "@/src/services/wsService"; import { sendWsBroadcastExcept } from "@/src/services/webService";
export const getConfigController: RequestHandler = async (req, res) => { export const getConfigController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req); const account = await getAccountForRequest(req);
@ -37,8 +37,6 @@ const configIdToIndexable = (id: string): [Record<string, boolean | string | num
let obj = config as unknown as Record<string, never>; let obj = config as unknown as Record<string, never>;
const arr = id.split("."); const arr = id.split(".");
while (arr.length > 1) { while (arr.length > 1) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
obj[arr[0]] ??= {} as never;
obj = obj[arr[0]]; obj = obj[arr[0]];
arr.splice(0, 1); arr.splice(0, 1);
} }

View File

@ -21,7 +21,6 @@ import {
TRelicQuality TRelicQuality
} from "warframe-public-export-plus"; } from "warframe-public-export-plus";
import allIncarnons from "@/static/fixed_responses/allIncarnonList.json"; import allIncarnons from "@/static/fixed_responses/allIncarnonList.json";
import varzia from "@/static/fixed_responses/worldState/varzia.json";
interface ListedItem { interface ListedItem {
uniqueName: string; uniqueName: string;
@ -56,7 +55,6 @@ interface ItemLists {
EvolutionProgress: ListedItem[]; EvolutionProgress: ListedItem[];
mods: ListedItem[]; mods: ListedItem[];
Boosters: ListedItem[]; Boosters: ListedItem[];
VarziaOffers: ListedItem[];
//circuitGameModes: ListedItem[]; //circuitGameModes: ListedItem[];
} }
@ -93,8 +91,7 @@ const getItemListsController: RequestHandler = (req, response) => {
KubrowPets: [], KubrowPets: [],
EvolutionProgress: [], EvolutionProgress: [],
mods: [], mods: [],
Boosters: [], Boosters: []
VarziaOffers: []
/*circuitGameModes: [ /*circuitGameModes: [
{ {
uniqueName: "Survival", uniqueName: "Survival",
@ -341,13 +338,6 @@ const getItemListsController: RequestHandler = (req, response) => {
}); });
} }
for (const item of Object.values(varzia.primeDualPacks)) {
res.VarziaOffers.push({
uniqueName: item.ItemType,
name: getString(getItemName(item.ItemType) || "", lang)
});
}
response.json(res); response.json(res);
}; };

View File

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

View File

@ -1,5 +1,5 @@
import { args } from "@/src/helpers/commandLineArguments"; import { args } from "@/src/helpers/commandLineArguments";
import { sendWsBroadcast } from "@/src/services/wsService"; import { sendWsBroadcast } from "@/src/services/webService";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
export const webuiFileChangeDetectedController: RequestHandler = (req, res) => { export const webuiFileChangeDetectedController: RequestHandler = (req, res) => {

View File

@ -6,11 +6,13 @@ import { Account } from "@/src/models/loginModel";
import { Stats, TStatsDatabaseDocument } from "@/src/models/statsModel"; import { Stats, TStatsDatabaseDocument } from "@/src/models/statsModel";
import { allDailyAffiliationKeys } from "@/src/services/inventoryService"; import { allDailyAffiliationKeys } from "@/src/services/inventoryService";
import { IMongoDate, IOid } from "@/src/types/commonTypes"; import { IMongoDate, IOid } from "@/src/types/commonTypes";
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { import {
IAffiliation, IAffiliation,
IAlignment, IAlignment,
IChallengeProgress, IChallengeProgress,
IDailyAffiliations, IDailyAffiliations,
ILoadoutConfigClient,
IMission, IMission,
IPlayerSkills, IPlayerSkills,
ITypeXPItem ITypeXPItem
@ -21,8 +23,6 @@ import { ExportCustoms, ExportDojoRecipes } from "warframe-public-export-plus";
import { IStatsClient } from "@/src/types/statTypes"; import { IStatsClient } from "@/src/types/statTypes";
import { toStoreItem } from "@/src/services/itemDataService"; import { toStoreItem } from "@/src/services/itemDataService";
import { FlattenMaps } from "mongoose"; import { FlattenMaps } from "mongoose";
import { IEquipmentClient } from "@/src/types/equipmentTypes";
import { ILoadoutConfigClient } from "@/src/types/saveLoadoutTypes";
const getProfileViewingDataByPlayerIdImpl = async (playerId: string): Promise<IProfileViewingData | undefined> => { const getProfileViewingDataByPlayerIdImpl = async (playerId: string): Promise<IProfileViewingData | undefined> => {
const account = await Account.findById(playerId, "DisplayName"); const account = await Account.findById(playerId, "DisplayName");

View File

@ -1,7 +1,7 @@
import { IAccountCreation } from "@/src/types/customTypes"; import { IAccountCreation } from "@/src/types/customTypes";
import { IDatabaseAccountRequiredFields } from "@/src/types/loginTypes"; import { IDatabaseAccountRequiredFields } from "@/src/types/loginTypes";
import crypto from "crypto"; import crypto from "crypto";
import { isString, parseEmail, parseString } from "@/src/helpers/general"; import { isString, parseEmail, parseString } from "../general";
const getWhirlpoolHash = (rawPassword: string): string => { const getWhirlpoolHash = (rawPassword: string): string => {
const whirlpool = crypto.createHash("whirlpool"); const whirlpool = crypto.createHash("whirlpool");

View File

@ -9,11 +9,11 @@ export const isEmptyObject = (obj: object): boolean => {
}; };
*/ */
export const isString = (text: unknown): text is string => { const isString = (text: unknown): text is string => {
return typeof text === "string" || text instanceof String; return typeof text === "string" || text instanceof String;
}; };
export const parseString = (data: unknown): string => { const parseString = (data: unknown): string => {
if (!isString(data)) { if (!isString(data)) {
throw new Error("data is not a string"); throw new Error("data is not a string");
} }
@ -21,11 +21,11 @@ export const parseString = (data: unknown): string => {
return data; return data;
}; };
export const isNumber = (number: unknown): number is number => { const isNumber = (number: unknown): number is number => {
return typeof number === "number" && !isNaN(number); return typeof number === "number" && !isNaN(number);
}; };
export const parseNumber = (data: unknown): number => { const parseNumber = (data: unknown): number => {
if (!isNumber(data)) { if (!isNumber(data)) {
throw new Error("data is not a number"); throw new Error("data is not a number");
} }
@ -33,11 +33,11 @@ export const parseNumber = (data: unknown): number => {
return Number(data); return Number(data);
}; };
export const isDate = (date: string): boolean => { const isDate = (date: string): boolean => {
return Date.parse(date) != 0; return Date.parse(date) != 0;
}; };
export const parseDateNumber = (date: unknown): string => { const parseDateNumber = (date: unknown): string => {
if (!isString(date) || !isDate(date)) { if (!isString(date) || !isDate(date)) {
throw new Error("date could not be parsed"); throw new Error("date could not be parsed");
} }
@ -45,18 +45,18 @@ export const parseDateNumber = (date: unknown): string => {
return date; return date;
}; };
export const parseEmail = (email: unknown): string => { const parseEmail = (email: unknown): string => {
if (!isString(email)) { if (!isString(email)) {
throw new Error("incorrect email"); throw new Error("incorrect email");
} }
return email; return email;
}; };
export const isBoolean = (booleanCandidate: unknown): booleanCandidate is boolean => { const isBoolean = (booleanCandidate: unknown): booleanCandidate is boolean => {
return typeof booleanCandidate === "boolean"; return typeof booleanCandidate === "boolean";
}; };
export const parseBoolean = (booleanCandidate: unknown): boolean => { const parseBoolean = (booleanCandidate: unknown): boolean => {
if (!isBoolean(booleanCandidate)) { if (!isBoolean(booleanCandidate)) {
throw new Error("argument was not a boolean"); throw new Error("argument was not a boolean");
} }
@ -70,3 +70,5 @@ export const isObject = (objectCandidate: unknown): objectCandidate is Record<st
!Array.isArray(objectCandidate) !Array.isArray(objectCandidate)
); );
}; };
export { isString, isNumber, parseString, parseNumber, parseDateNumber, parseBoolean, parseEmail };

View File

@ -51,11 +51,6 @@ export const fromMongoDate = (date: IMongoDate): Date => {
return new Date(parseInt(date.$date.$numberLong)); return new Date(parseInt(date.$date.$numberLong));
}; };
export type TTraitsPool = Record<
"Colors" | "EyeColors" | "FurPatterns" | "BodyTypes" | "Heads" | "Tails",
{ type: string; rarity: TRarity }[]
>;
export const kubrowWeights: Record<TRarity, number> = { export const kubrowWeights: Record<TRarity, number> = {
COMMON: 6, COMMON: 6,
UNCOMMON: 4, UNCOMMON: 4,
@ -70,126 +65,126 @@ export const kubrowFurPatternsWeights: Record<TRarity, number> = {
LEGENDARY: 1 LEGENDARY: 1
}; };
export const catbrowDetails: TTraitsPool = { export const catbrowDetails = {
Colors: [ Colors: [
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseA", rarity: "COMMON" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseA", rarity: "COMMON" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseB", rarity: "COMMON" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseB", rarity: "COMMON" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseC", rarity: "COMMON" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseC", rarity: "COMMON" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseD", rarity: "COMMON" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseD", rarity: "COMMON" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryA", rarity: "UNCOMMON" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryA", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryB", rarity: "UNCOMMON" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryB", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryC", rarity: "UNCOMMON" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryC", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryD", rarity: "UNCOMMON" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryD", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryA", rarity: "RARE" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryA", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryB", rarity: "RARE" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryB", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryC", rarity: "RARE" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryC", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryD", rarity: "RARE" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryD", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsA", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsA", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsB", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsB", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsC", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsC", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsD", rarity: "LEGENDARY" } { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsD", rarity: "LEGENDARY" as TRarity }
], ],
EyeColors: [ EyeColors: [
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesA", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesA", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesB", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesB", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesC", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesC", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesD", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesD", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesE", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesE", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesF", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesF", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesG", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesG", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesH", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesH", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesI", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesI", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesJ", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesJ", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesK", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesK", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesL", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesL", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesM", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesM", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesN", rarity: "LEGENDARY" } { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesN", rarity: "LEGENDARY" as TRarity }
], ],
FurPatterns: [{ type: "/Lotus/Types/Game/CatbrowPet/Patterns/CatbrowPetPatternA", rarity: "COMMON" }], FurPatterns: [{ type: "/Lotus/Types/Game/CatbrowPet/Patterns/CatbrowPetPatternA", rarity: "COMMON" as TRarity }],
BodyTypes: [ BodyTypes: [
{ type: "/Lotus/Types/Game/CatbrowPet/BodyTypes/CatbrowPetRegularBodyType", rarity: "UNCOMMON" }, { type: "/Lotus/Types/Game/CatbrowPet/BodyTypes/CatbrowPetRegularBodyType", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/BodyTypes/CatbrowPetRegularBodyType", rarity: "LEGENDARY" } { type: "/Lotus/Types/Game/CatbrowPet/BodyTypes/CatbrowPetRegularBodyType", rarity: "LEGENDARY" as TRarity }
], ],
Heads: [ Heads: [
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadA", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadA", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadB", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadB", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadC", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadC", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadD", rarity: "LEGENDARY" } { type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadD", rarity: "LEGENDARY" as TRarity }
], ],
Tails: [ Tails: [
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailA", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailA", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailB", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailB", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailC", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailC", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailD", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailD", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailE", rarity: "LEGENDARY" } { type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailE", rarity: "LEGENDARY" as TRarity }
] ]
}; };
export const kubrowDetails: TTraitsPool = { export const kubrowDetails = {
Colors: [ Colors: [
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneA", rarity: "UNCOMMON" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneA", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneB", rarity: "UNCOMMON" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneB", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneC", rarity: "UNCOMMON" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneC", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneD", rarity: "UNCOMMON" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneD", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneE", rarity: "UNCOMMON" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneE", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneF", rarity: "UNCOMMON" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneF", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneG", rarity: "UNCOMMON" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneG", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneH", rarity: "UNCOMMON" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneH", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidA", rarity: "RARE" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidA", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidB", rarity: "RARE" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidB", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidC", rarity: "RARE" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidC", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidD", rarity: "RARE" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidD", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidE", rarity: "RARE" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidE", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidF", rarity: "RARE" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidF", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidG", rarity: "RARE" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidG", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidH", rarity: "RARE" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidH", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantA", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantA", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantB", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantB", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantC", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantC", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantD", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantD", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantE", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantE", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantF", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantF", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantG", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantG", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantH", rarity: "LEGENDARY" } { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantH", rarity: "LEGENDARY" as TRarity }
], ],
EyeColors: [ EyeColors: [
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesA", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesA", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesB", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesB", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesC", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesC", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesD", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesD", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesE", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesE", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesF", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesF", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesG", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesG", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesH", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesH", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesI", rarity: "LEGENDARY" } { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesI", rarity: "LEGENDARY" as TRarity }
], ],
FurPatterns: [ FurPatterns: [
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternB", rarity: "UNCOMMON" }, { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternB", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternA", rarity: "UNCOMMON" }, { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternA", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternC", rarity: "RARE" }, { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternC", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternD", rarity: "RARE" }, { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternD", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternE", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternE", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternF", rarity: "LEGENDARY" } { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternF", rarity: "LEGENDARY" as TRarity }
], ],
BodyTypes: [ BodyTypes: [
{ type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetRegularBodyType", rarity: "UNCOMMON" }, { type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetRegularBodyType", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetHeavyBodyType", rarity: "LEGENDARY" }, { type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetHeavyBodyType", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetThinBodyType", rarity: "LEGENDARY" } { type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetThinBodyType", rarity: "LEGENDARY" as TRarity }
], ],
Heads: [], Heads: [],

View File

@ -1,4 +1,4 @@
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes"; import { TEquipmentKey } from "../types/inventoryTypes/inventoryTypes";
export const modularWeaponTypes: Record<string, TEquipmentKey> = { export const modularWeaponTypes: Record<string, TEquipmentKey> = {
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary": "LongGuns", "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary": "LongGuns",

View File

@ -1,9 +1,12 @@
import { ExportRegions, ExportWarframes } from "warframe-public-export-plus"; import { ExportRegions, ExportWarframes } from "warframe-public-export-plus";
import { IInfNode, TNemesisFaction } from "@/src/types/inventoryTypes/inventoryTypes"; import { IInfNode, TNemesisFaction } from "@/src/types/inventoryTypes/inventoryTypes";
import { generateRewardSeed, getRewardAtPercentage, SRng } from "@/src/services/rngService"; import { getRewardAtPercentage, SRng } from "@/src/services/rngService";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
import { IOid } from "@/src/types/commonTypes"; import { logger } from "../utils/logger";
import { isArchwingMission } from "@/src/services/worldStateService"; import { IOid } from "../types/commonTypes";
import { Types } from "mongoose";
import { addMods, generateRewardSeed } from "../services/inventoryService";
import { isArchwingMission } from "../services/worldStateService";
type TInnateDamageTag = type TInnateDamageTag =
| "InnateElectricityDamage" | "InnateElectricityDamage"
@ -361,6 +364,57 @@ export const parseUpgrade = (
} }
}; };
export const consumeModCharge = (
response: IKnifeResponse,
inventory: TInventoryDatabaseDocument,
upgrade: { ItemId: IOid; ItemType: string },
dataknifeUpgrades: string[]
): void => {
response.UpgradeIds ??= [];
response.UpgradeTypes ??= [];
response.UpgradeFingerprints ??= [];
response.UpgradeNew ??= [];
response.HasKnife = true;
if (upgrade.ItemId.$oid != "000000000000000000000000") {
const dbUpgrade = inventory.Upgrades.id(upgrade.ItemId.$oid)!;
const fingerprint = JSON.parse(dbUpgrade.UpgradeFingerprint!) as { lvl: number };
fingerprint.lvl += 1;
dbUpgrade.UpgradeFingerprint = JSON.stringify(fingerprint);
response.UpgradeIds.push(upgrade.ItemId.$oid);
response.UpgradeTypes.push(upgrade.ItemType);
response.UpgradeFingerprints.push(fingerprint);
response.UpgradeNew.push(false);
} else {
const id = new Types.ObjectId();
inventory.Upgrades.push({
_id: id,
ItemType: upgrade.ItemType,
UpgradeFingerprint: `{"lvl":1}`
});
addMods(inventory, [
{
ItemType: upgrade.ItemType,
ItemCount: -1
}
]);
const dataknifeRawUpgradeIndex = dataknifeUpgrades.indexOf(upgrade.ItemType);
if (dataknifeRawUpgradeIndex != -1) {
dataknifeUpgrades[dataknifeRawUpgradeIndex] = id.toString();
} else {
logger.warn(`${upgrade.ItemType} not found in dataknife config`);
}
response.UpgradeIds.push(id.toString());
response.UpgradeTypes.push(upgrade.ItemType);
response.UpgradeFingerprints.push({ lvl: 1 });
response.UpgradeNew.push(true);
}
};
export const getInnateDamageTag = (KillingSuit: string): TInnateDamageTag => { export const getInnateDamageTag = (KillingSuit: string): TInnateDamageTag => {
return ExportWarframes[KillingSuit].nemesisUpgradeTag!; return ExportWarframes[KillingSuit].nemesisUpgradeTag!;
}; };

View File

@ -5,8 +5,7 @@ import { getRandomWeightedReward, IRngResult } from "@/src/services/rngService";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { addMiscItems, combineInventoryChanges } from "@/src/services/inventoryService"; import { addMiscItems, combineInventoryChanges } from "@/src/services/inventoryService";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService"; import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "../types/purchaseTypes";
import { config } from "@/src/services/configService";
export const crackRelic = async ( export const crackRelic = async (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
@ -14,25 +13,12 @@ export const crackRelic = async (
inventoryChanges: IInventoryChanges = {} inventoryChanges: IInventoryChanges = {}
): Promise<IRngResult> => { ): Promise<IRngResult> => {
const relic = ExportRelics[participant.VoidProjection]; const relic = ExportRelics[participant.VoidProjection];
let weights = refinementToWeights[relic.quality]; const weights = refinementToWeights[relic.quality];
if (relic.quality == "VPQ_SILVER" && config.exceptionalRelicsAlwaysGiveBronzeReward) {
weights = { COMMON: 1, UNCOMMON: 0, RARE: 0, LEGENDARY: 0 };
} else if (relic.quality == "VPQ_GOLD" && config.flawlessRelicsAlwaysGiveSilverReward) {
weights = { COMMON: 0, UNCOMMON: 1, RARE: 0, LEGENDARY: 0 };
} else if (relic.quality == "VPQ_PLATINUM" && config.radiantRelicsAlwaysGiveGoldReward) {
weights = { COMMON: 0, UNCOMMON: 0, RARE: 1, LEGENDARY: 0 };
}
logger.debug(`opening a relic of quality ${relic.quality}; rarity weights are`, weights); logger.debug(`opening a relic of quality ${relic.quality}; rarity weights are`, weights);
let reward = getRandomWeightedReward( const reward = getRandomWeightedReward(
ExportRewards[relic.rewardManifest][0] as { type: string; itemCount: number; rarity: TRarity }[], // rarity is nullable in PE+ typings, but always present for relics ExportRewards[relic.rewardManifest][0] as { type: string; itemCount: number; rarity: TRarity }[], // rarity is nullable in PE+ typings, but always present for relics
weights weights
)!; )!;
if (config.relicRewardItemCountMultiplier !== undefined && (config.relicRewardItemCountMultiplier ?? 1) != 1) {
reward = {
...reward,
itemCount: reward.itemCount * config.relicRewardItemCountMultiplier
};
}
logger.debug(`relic rolled`, reward); logger.debug(`relic rolled`, reward);
participant.Reward = reward.type; participant.Reward = reward.type;

View File

@ -1,5 +1,5 @@
import { IUpgrade } from "warframe-public-export-plus"; import { IUpgrade } from "warframe-public-export-plus";
import { getRandomElement, getRandomInt, getRandomReward } from "@/src/services/rngService"; import { getRandomElement, getRandomInt, getRandomReward } from "../services/rngService";
export type RivenFingerprint = IVeiledRivenFingerprint | IUnveiledRivenFingerprint; export type RivenFingerprint = IVeiledRivenFingerprint | IUnveiledRivenFingerprint;

View File

@ -19,10 +19,10 @@ logger.info("Starting up...");
// Proceed with normal startup: bring up config watcher service, validate config, connect to MongoDB, and finally start listening for HTTP. // Proceed with normal startup: bring up config watcher service, validate config, connect to MongoDB, and finally start listening for HTTP.
import mongoose from "mongoose"; import mongoose from "mongoose";
import { JSONStringify } from "json-with-bigint"; import { JSONStringify } from "json-with-bigint";
import { startWebServer } from "@/src/services/webService"; import { startWebServer } from "./services/webService";
import { syncConfigWithDatabase, validateConfig } from "@/src/services/configWatcherService"; import { syncConfigWithDatabase, validateConfig } from "@/src/services/configWatcherService";
import { updateWorldStateCollections } from "@/src/services/worldStateService"; import { updateWorldStateCollections } from "./services/worldStateService";
// Patch JSON.stringify to work flawlessly with Bigints. // Patch JSON.stringify to work flawlessly with Bigints.
JSON.stringify = JSONStringify; JSON.stringify = JSONStringify;

View File

@ -1,5 +1,5 @@
import { NextFunction, Request, Response } from "express"; import { NextFunction, Request, Response } from "express";
import { logger } from "@/src/utils/logger"; import { logger } from "../utils/logger";
export const errorHandler = (err: Error, req: Request, res: Response, _next: NextFunction): void => { export const errorHandler = (err: Error, req: Request, res: Response, _next: NextFunction): void => {
if (err.message == "Invalid accountId-nonce pair") { if (err.message == "Invalid accountId-nonce pair") {

View File

@ -1,25 +0,0 @@
import { Schema } from "mongoose";
import { IColor, IShipCustomization } from "@/src/types/inventoryTypes/commonInventoryTypes";
export const colorSchema = new Schema<IColor>(
{
t0: Number,
t1: Number,
t2: Number,
t3: Number,
en: Number,
e1: Number,
m0: Number,
m1: Number
},
{ _id: false }
);
export const shipCustomizationSchema = new Schema<IShipCustomization>(
{
SkinFlavourItem: String,
Colors: colorSchema,
ShipAttachments: { HOOD_ORNAMENT: String }
},
{ _id: false }
);

View File

@ -17,8 +17,8 @@ import {
GuildPermission GuildPermission
} from "@/src/types/guildTypes"; } from "@/src/types/guildTypes";
import { Document, Model, model, Schema, Types } from "mongoose"; import { Document, Model, model, Schema, Types } from "mongoose";
import { fusionTreasuresSchema, typeCountSchema } from "@/src/models/inventoryModels/inventoryModel"; import { fusionTreasuresSchema, typeCountSchema } from "./inventoryModels/inventoryModel";
import { pictureFrameInfoSchema } from "@/src/models/personalRoomsModel"; import { pictureFrameInfoSchema } from "./personalRoomsModel";
const dojoDecoSchema = new Schema<IDojoDecoDatabase>({ const dojoDecoSchema = new Schema<IDojoDecoDatabase>({
Type: String, Type: String,

View File

@ -1,7 +1,8 @@
import { model, Schema, Types } from "mongoose"; import { model, Schema, Types } from "mongoose";
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers"; import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
import { typeCountSchema } from "@/src/models/inventoryModels/inventoryModel"; import { typeCountSchema } from "@/src/models/inventoryModels/inventoryModel";
import { IMongoDate, IOid, ITypeCount } from "@/src/types/commonTypes"; import { IMongoDate, IOid } from "@/src/types/commonTypes";
import { ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
export interface IMessageClient export interface IMessageClient
extends Omit<IMessageDatabase, "_id" | "date" | "startDate" | "endDate" | "ownerId" | "attVisualOnly" | "expiry"> { extends Omit<IMessageDatabase, "_id" | "date" | "startDate" | "endDate" | "ownerId" | "attVisualOnly" | "expiry"> {
@ -22,9 +23,7 @@ export interface IMessageDatabase extends IMessage {
export interface IMessage { export interface IMessage {
sndr: string; sndr: string;
msg: string; msg: string;
cinematic?: string;
sub: string; sub: string;
customData?: string;
icon?: string; icon?: string;
highPriority?: boolean; highPriority?: boolean;
lowPrioNewPlayers?: boolean; lowPrioNewPlayers?: boolean;
@ -103,9 +102,7 @@ const messageSchema = new Schema<IMessageDatabase>(
ownerId: Schema.Types.ObjectId, ownerId: Schema.Types.ObjectId,
sndr: String, sndr: String,
msg: String, msg: String,
cinematic: String,
sub: String, sub: String,
customData: String,
icon: String, icon: String,
highPriority: Boolean, highPriority: Boolean,
lowPrioNewPlayers: Boolean, lowPrioNewPlayers: Boolean,

View File

@ -1,5 +1,6 @@
import { Document, Model, Schema, Types, model } from "mongoose"; import { Document, Model, Schema, Types, model } from "mongoose";
import { import {
IFlavourItem,
IRawUpgrade, IRawUpgrade,
IMiscItem, IMiscItem,
IInventoryDatabase, IInventoryDatabase,
@ -9,6 +10,7 @@ import {
IDuviriInfo, IDuviriInfo,
IPendingRecipeDatabase, IPendingRecipeDatabase,
IPendingRecipeClient, IPendingRecipeClient,
ITypeCount,
IFocusXP, IFocusXP,
IFocusUpgrade, IFocusUpgrade,
ITypeXPItem, ITypeXPItem,
@ -37,15 +39,25 @@ import {
IEvolutionProgress, IEvolutionProgress,
IEndlessXpProgressDatabase, IEndlessXpProgressDatabase,
IEndlessXpProgressClient, IEndlessXpProgressClient,
ICrewShipCustomization,
ICrewShipWeapon,
ICrewShipWeaponEmplacements,
IShipExterior,
IHelminthFoodRecord, IHelminthFoodRecord,
ICrewShipMembersDatabase,
IDialogueHistoryDatabase, IDialogueHistoryDatabase,
IDialogueDatabase, IDialogueDatabase,
IDialogueGift, IDialogueGift,
ICompletedDialogue, ICompletedDialogue,
IDialogueClient, IDialogueClient,
IUpgradeDatabase, IUpgradeDatabase,
ICrewShipMemberDatabase,
ICrewShipMemberClient,
TEquipmentKey, TEquipmentKey,
equipmentKeys, equipmentKeys,
IKubrowPetDetailsDatabase,
ITraits,
IKubrowPetDetailsClient,
IKubrowPetEggDatabase, IKubrowPetEggDatabase,
IKubrowPetEggClient, IKubrowPetEggClient,
ICustomMarkers, ICustomMarkers,
@ -84,39 +96,27 @@ import {
IInvasionProgressClient, IInvasionProgressClient,
IAccolades, IAccolades,
IHubNpcCustomization, IHubNpcCustomization,
ILotusCustomization,
IEndlessXpReward, IEndlessXpReward,
IPersonalGoalProgressDatabase, IPersonalGoalProgressDatabase,
IPersonalGoalProgressClient, IPersonalGoalProgressClient,
IKubrowPetPrintClient, IKubrowPetPrintClient,
IKubrowPetPrintDatabase IKubrowPetPrintDatabase
} from "@/src/types/inventoryTypes/inventoryTypes"; } from "../../types/inventoryTypes/inventoryTypes";
import { IOid, ITypeCount } from "@/src/types/commonTypes"; import { IOid } from "../../types/commonTypes";
import { import {
IAbilityOverride, IAbilityOverride,
ICrewShipCustomization, IColor,
IFlavourItem,
IItemConfig, IItemConfig,
ILotusCustomization,
IOperatorConfigDatabase, IOperatorConfigDatabase,
IPolarity IPolarity,
IEquipmentDatabase,
IArchonCrystalUpgrade,
IEquipmentClient
} from "@/src/types/inventoryTypes/commonInventoryTypes"; } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers"; import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
import { EquipmentSelectionSchema, oidSchema } from "@/src/models/inventoryModels/loadoutModel"; import { EquipmentSelectionSchema, oidSchema } from "./loadoutModel";
import { ICountedStoreItem } from "warframe-public-export-plus"; import { ICountedStoreItem } from "warframe-public-export-plus";
import { colorSchema, shipCustomizationSchema } from "@/src/models/commonModel";
import {
IArchonCrystalUpgrade,
ICrewShipMemberClient,
ICrewShipMemberDatabase,
ICrewShipMembersDatabase,
ICrewShipWeapon,
ICrewShipWeaponEmplacements,
IEquipmentClient,
IEquipmentDatabase,
IKubrowPetDetailsClient,
IKubrowPetDetailsDatabase,
ITraits
} from "@/src/types/equipmentTypes";
export const typeCountSchema = new Schema<ITypeCount>({ ItemType: String, ItemCount: Number }, { _id: false }); export const typeCountSchema = new Schema<ITypeCount>({ ItemType: String, ItemCount: Number }, { _id: false });
@ -166,6 +166,20 @@ const abilityOverrideSchema = new Schema<IAbilityOverride>(
{ _id: false } { _id: false }
); );
export const colorSchema = new Schema<IColor>(
{
t0: Number,
t1: Number,
t2: Number,
t3: Number,
en: Number,
e1: Number,
m0: Number,
m1: Number
},
{ _id: false }
);
const operatorConfigSchema = new Schema<IOperatorConfigDatabase>( const operatorConfigSchema = new Schema<IOperatorConfigDatabase>(
{ {
Skins: [String], Skins: [String],
@ -882,9 +896,18 @@ const crewShipWeaponSchema = new Schema<ICrewShipWeapon>(
{ _id: false } { _id: false }
); );
const shipExteriorSchema = new Schema<IShipExterior>(
{
SkinFlavourItem: String,
Colors: colorSchema,
ShipAttachments: { HOOD_ORNAMENT: String }
},
{ _id: false }
);
const crewShipCustomizationSchema = new Schema<ICrewShipCustomization>( const crewShipCustomizationSchema = new Schema<ICrewShipCustomization>(
{ {
CrewshipInterior: shipCustomizationSchema CrewshipInterior: shipExteriorSchema
}, },
{ _id: false } { _id: false }
); );
@ -1216,8 +1239,8 @@ const calenderProgressSchema = new Schema<ICalendarProgress>(
}, },
SeasonProgress: { SeasonProgress: {
SeasonType: { type: String, required: true }, SeasonType: { type: String, required: true },
LastCompletedDayIdx: { type: Number, default: -1 }, LastCompletedDayIdx: { type: Number, default: 0 },
LastCompletedChallengeDayIdx: { type: Number, default: -1 }, LastCompletedChallengeDayIdx: { type: Number, default: 0 },
ActivatedChallenges: { type: [String], default: [] } ActivatedChallenges: { type: [String], default: [] }
} }
}, },

View File

@ -1,5 +1,5 @@
import { IOid } from "@/src/types/commonTypes"; import { IOid } from "@/src/types/commonTypes";
import { IEquipmentSelection } from "@/src/types/equipmentTypes"; import { IEquipmentSelection } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { ILoadoutConfigDatabase, ILoadoutDatabase } from "@/src/types/saveLoadoutTypes"; import { ILoadoutConfigDatabase, ILoadoutDatabase } from "@/src/types/saveLoadoutTypes";
import { Document, Model, Schema, Types, model } from "mongoose"; import { Document, Model, Schema, Types, model } from "mongoose";

View File

@ -1,5 +1,5 @@
import { Document, model, Schema, Types } from "mongoose"; import { Document, model, Schema, Types } from "mongoose";
import { ILeaderboardEntryDatabase } from "@/src/types/leaderboardTypes"; import { ILeaderboardEntryDatabase } from "../types/leaderboardTypes";
const leaderboardEntrySchema = new Schema<ILeaderboardEntryDatabase>( const leaderboardEntrySchema = new Schema<ILeaderboardEntryDatabase>(
{ {

View File

@ -1,22 +1,19 @@
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers"; import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
import { colorSchema } from "@/src/models/inventoryModels/inventoryModel";
import { IOrbiter, IPersonalRoomsDatabase, PersonalRoomsModelType } from "@/src/types/personalRoomsTypes";
import { import {
IApartmentDatabase,
IFavouriteLoadoutDatabase, IFavouriteLoadoutDatabase,
IGardeningDatabase, IGardeningDatabase,
IOrbiterClient,
IOrbiterDatabase,
IPersonalRoomsDatabase,
IPictureFrameInfo,
IPlacedDecosDatabase, IPlacedDecosDatabase,
IPlantClient, IPictureFrameInfo,
IPlantDatabase,
IPlanterDatabase,
IRoom, IRoom,
ITailorShopDatabase, ITailorShopDatabase,
PersonalRoomsModelType IApartmentDatabase,
} from "@/src/types/personalRoomsTypes"; IPlanterDatabase,
IPlantDatabase,
IPlantClient
} from "@/src/types/shipTypes";
import { Schema, Types, model } from "mongoose"; import { Schema, Types, model } from "mongoose";
import { colorSchema, shipCustomizationSchema } from "@/src/models/commonModel";
export const pictureFrameInfoSchema = new Schema<IPictureFrameInfo>( export const pictureFrameInfoSchema = new Schema<IPictureFrameInfo>(
{ {
@ -140,11 +137,10 @@ const apartmentDefault: IApartmentDatabase = {
} }
}; };
const orbiterSchema = new Schema<IOrbiterDatabase>( const orbiterSchema = new Schema<IOrbiter>(
{ {
Features: [String], Features: [String],
Rooms: [roomSchema], Rooms: [roomSchema],
ShipInterior: shipCustomizationSchema,
VignetteFish: { type: [String], default: undefined }, VignetteFish: { type: [String], default: undefined },
FavouriteLoadoutId: Schema.Types.ObjectId, FavouriteLoadoutId: Schema.Types.ObjectId,
Wallpaper: String, Wallpaper: String,
@ -154,18 +150,7 @@ const orbiterSchema = new Schema<IOrbiterDatabase>(
}, },
{ _id: false } { _id: false }
); );
orbiterSchema.set("toJSON", { const orbiterDefault: IOrbiter = {
virtuals: true,
transform(_doc, obj) {
const db = obj as IOrbiterDatabase;
const client = obj as IOrbiterClient;
if (db.FavouriteLoadoutId) {
client.FavouriteLoadoutId = toOid(db.FavouriteLoadoutId);
}
}
});
const orbiterDefault: IOrbiterDatabase = {
Features: ["/Lotus/Types/Items/ShipFeatureItems/EarthNavigationFeatureItem"], //TODO: potentially remove after missionstarting gear Features: ["/Lotus/Types/Items/ShipFeatureItems/EarthNavigationFeatureItem"], //TODO: potentially remove after missionstarting gear
Rooms: [ Rooms: [
{ Name: "AlchemyRoom", MaxCapacity: 1600 }, { Name: "AlchemyRoom", MaxCapacity: 1600 },
@ -212,6 +197,7 @@ const tailorShopDefault: ITailorShopDatabase = {
export const personalRoomsSchema = new Schema<IPersonalRoomsDatabase>({ export const personalRoomsSchema = new Schema<IPersonalRoomsDatabase>({
personalRoomsOwnerId: Schema.Types.ObjectId, personalRoomsOwnerId: Schema.Types.ObjectId,
activeShipId: Schema.Types.ObjectId, activeShipId: Schema.Types.ObjectId,
ShipInteriorColors: colorSchema,
Ship: { type: orbiterSchema, default: orbiterDefault }, Ship: { type: orbiterSchema, default: orbiterDefault },
Apartment: { type: apartmentSchema, default: apartmentDefault }, Apartment: { type: apartmentSchema, default: apartmentDefault },
TailorShop: { type: tailorShopSchema, default: tailorShopDefault } TailorShop: { type: tailorShopSchema, default: tailorShopDefault }

View File

@ -1,7 +1,7 @@
import { Document, Schema, Types, model } from "mongoose"; import { Document, Schema, Types, model } from "mongoose";
import { IShipDatabase } from "@/src/types/shipTypes"; import { IShipDatabase } from "../types/shipTypes";
import { toOid } from "@/src/helpers/inventoryHelpers"; import { toOid } from "@/src/helpers/inventoryHelpers";
import { colorSchema } from "@/src/models/commonModel"; import { colorSchema } from "@/src/models/inventoryModels/inventoryModel";
import { IShipInventory } from "@/src/types/inventoryTypes/inventoryTypes"; import { IShipInventory } from "@/src/types/inventoryTypes/inventoryTypes";
const shipSchema = new Schema<IShipDatabase>( const shipSchema = new Schema<IShipDatabase>(

View File

@ -112,7 +112,6 @@ import { removeFromGuildController } from "@/src/controllers/api/removeFromGuild
import { removeIgnoredUserController } from "@/src/controllers/api/removeIgnoredUserController"; import { removeIgnoredUserController } from "@/src/controllers/api/removeIgnoredUserController";
import { renamePetController } from "@/src/controllers/api/renamePetController"; import { renamePetController } from "@/src/controllers/api/renamePetController";
import { rerollRandomModController } from "@/src/controllers/api/rerollRandomModController"; import { rerollRandomModController } from "@/src/controllers/api/rerollRandomModController";
import { resetQuestProgressController } from "@/src/controllers/api/resetQuestProgressController";
import { retrievePetFromStasisController } from "@/src/controllers/api/retrievePetFromStasisController"; import { retrievePetFromStasisController } from "@/src/controllers/api/retrievePetFromStasisController";
import { saveDialogueController } from "@/src/controllers/api/saveDialogueController"; import { saveDialogueController } from "@/src/controllers/api/saveDialogueController";
import { saveLoadoutController } from "@/src/controllers/api/saveLoadoutController"; import { saveLoadoutController } from "@/src/controllers/api/saveLoadoutController";
@ -210,7 +209,6 @@ apiRouter.get("/questControl.php", questControlController);
apiRouter.get("/queueDojoComponentDestruction.php", queueDojoComponentDestructionController); apiRouter.get("/queueDojoComponentDestruction.php", queueDojoComponentDestructionController);
apiRouter.get("/removeFriend.php", removeFriendGetController); apiRouter.get("/removeFriend.php", removeFriendGetController);
apiRouter.get("/removeFromAlliance.php", removeFromAllianceController); apiRouter.get("/removeFromAlliance.php", removeFromAllianceController);
apiRouter.get("/resetQuestProgress.php", resetQuestProgressController);
apiRouter.get("/setActiveQuest.php", setActiveQuestController); apiRouter.get("/setActiveQuest.php", setActiveQuestController);
apiRouter.get("/setActiveShip.php", setActiveShipController); apiRouter.get("/setActiveShip.php", setActiveShipController);
apiRouter.get("/setAllianceGuildPermissions.php", setAllianceGuildPermissionsController); apiRouter.get("/setAllianceGuildPermissions.php", setAllianceGuildPermissionsController);

View File

@ -64,13 +64,9 @@ export interface IConfig {
noDojoResearchTime?: boolean; noDojoResearchTime?: boolean;
fastClanAscension?: boolean; fastClanAscension?: boolean;
missionsCanGiveAllRelics?: boolean; missionsCanGiveAllRelics?: boolean;
exceptionalRelicsAlwaysGiveBronzeReward?: boolean;
flawlessRelicsAlwaysGiveSilverReward?: boolean;
radiantRelicsAlwaysGiveGoldReward?: boolean;
unlockAllSimarisResearchEntries?: boolean; unlockAllSimarisResearchEntries?: boolean;
disableDailyTribute?: boolean; disableDailyTribute?: boolean;
spoofMasteryRank?: number; spoofMasteryRank?: number;
relicRewardItemCountMultiplier?: number;
nightwaveStandingMultiplier?: number; nightwaveStandingMultiplier?: number;
unfaithfulBugFixes?: { unfaithfulBugFixes?: {
ignore1999LastRegionPlayed?: boolean; ignore1999LastRegionPlayed?: boolean;
@ -89,8 +85,6 @@ export interface IConfig {
allTheFissures?: string; allTheFissures?: string;
circuitGameModes?: string[]; circuitGameModes?: string[];
darvoStockMultiplier?: number; darvoStockMultiplier?: number;
varziaOverride?: string;
varziaFullyStocked?: boolean;
}; };
dev?: { dev?: {
keepVendorsExpired?: boolean; keepVendorsExpired?: boolean;

View File

@ -1,14 +1,15 @@
import chokidar from "chokidar"; import chokidar from "chokidar";
import { logger } from "@/src/utils/logger"; import fsPromises from "fs/promises";
import { config, configPath, loadConfig } from "@/src/services/configService"; import { logger } from "../utils/logger";
import { saveConfig, shouldReloadConfig } from "@/src/services/configWriterService"; import { config, configPath, loadConfig } from "./configService";
import { getWebPorts, startWebServer, stopWebServer } from "@/src/services/webService"; import { getWebPorts, sendWsBroadcast, startWebServer, stopWebServer } from "./webService";
import { sendWsBroadcast } from "@/src/services/wsService"; import { Inbox } from "../models/inboxModel";
import { Inbox } from "@/src/models/inboxModel";
import varzia from "@/static/fixed_responses/worldState/varzia.json";
let amnesia = false;
chokidar.watch(configPath).on("change", () => { chokidar.watch(configPath).on("change", () => {
if (shouldReloadConfig()) { if (amnesia) {
amnesia = false;
} else {
logger.info("Detected a change to config file, reloading its contents."); logger.info("Detected a change to config file, reloading its contents.");
try { try {
loadConfig(); loadConfig();
@ -56,19 +57,17 @@ export const validateConfig = (): void => {
config.worldState.galleonOfGhouls = 0; config.worldState.galleonOfGhouls = 0;
modified = true; modified = true;
} }
if (
config.worldState?.varziaOverride &&
!varzia.primeDualPacks.some(p => p.ItemType === config.worldState?.varziaOverride)
) {
config.worldState.varziaOverride = "";
modified = true;
}
if (modified) { if (modified) {
logger.info(`Updating config file to fix some issues with it.`); logger.info(`Updating config file to fix some issues with it.`);
void saveConfig(); void saveConfig();
} }
}; };
export const saveConfig = async (): Promise<void> => {
amnesia = true;
await fsPromises.writeFile(configPath, JSON.stringify(config, null, 2));
};
export const syncConfigWithDatabase = (): void => { export const syncConfigWithDatabase = (): void => {
// Event messages are deleted after endDate. Since we don't use beginDate/endDate and instead have config toggles, we need to delete the messages once those bools are false. // Event messages are deleted after endDate. Since we don't use beginDate/endDate and instead have config toggles, we need to delete the messages once those bools are false.
if (!config.worldState?.galleonOfGhouls) { if (!config.worldState?.galleonOfGhouls) {

View File

@ -1,17 +0,0 @@
import fsPromises from "fs/promises";
import { config, configPath } from "@/src/services/configService";
let amnesia = false;
export const saveConfig = async (): Promise<void> => {
amnesia = true;
await fsPromises.writeFile(configPath, JSON.stringify(config, null, 2));
};
export const shouldReloadConfig = (): boolean => {
if (amnesia) {
amnesia = false;
return false;
}
return true;
};

View File

@ -1,10 +1,10 @@
import { IFriendInfo } from "@/src/types/friendTypes"; import { IFriendInfo } from "../types/friendTypes";
import { getInventory } from "@/src/services/inventoryService"; import { getInventory } from "./inventoryService";
import { config } from "@/src/services/configService"; import { config } from "./configService";
import { Account } from "@/src/models/loginModel"; import { Account } from "../models/loginModel";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { Friendship } from "@/src/models/friendModel"; import { Friendship } from "../models/friendModel";
import { fromOid, toMongoDate } from "@/src/helpers/inventoryHelpers"; import { fromOid, toMongoDate } from "../helpers/inventoryHelpers";
export const addAccountDataToFriendInfo = async (info: IFriendInfo): Promise<void> => { export const addAccountDataToFriendInfo = async (info: IFriendInfo): Promise<void> => {
const account = (await Account.findById(fromOid(info._id), "DisplayName LastLogin"))!; const account = (await Account.findById(fromOid(info._id), "DisplayName LastLogin"))!;

View File

@ -22,17 +22,16 @@ import {
import { toMongoDate, toOid, toOid2 } from "@/src/helpers/inventoryHelpers"; import { toMongoDate, toOid, toOid2 } from "@/src/helpers/inventoryHelpers";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { ExportDojoRecipes, ExportResources, IDojoBuild, IDojoResearch } from "warframe-public-export-plus"; import { ExportDojoRecipes, ExportResources, IDojoBuild, IDojoResearch } from "warframe-public-export-plus";
import { logger } from "@/src/utils/logger"; import { logger } from "../utils/logger";
import { config } from "@/src/services/configService"; import { config } from "./configService";
import { getRandomInt } from "@/src/services/rngService"; import { getRandomInt } from "./rngService";
import { Inbox } from "@/src/models/inboxModel"; import { Inbox } from "../models/inboxModel";
import { IFusionTreasure } from "@/src/types/inventoryTypes/inventoryTypes"; import { IFusionTreasure, ITypeCount } from "../types/inventoryTypes/inventoryTypes";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "../types/purchaseTypes";
import { parallelForeach } from "@/src/utils/async-utils"; import { parallelForeach } from "../utils/async-utils";
import allDecoRecipes from "@/static/fixed_responses/allDecoRecipes.json"; import allDecoRecipes from "@/static/fixed_responses/allDecoRecipes.json";
import { createMessage } from "@/src/services/inboxService"; import { createMessage } from "./inboxService";
import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "@/src/services/friendService"; import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "./friendService";
import { ITypeCount } from "@/src/types/commonTypes";
export const getGuildForRequest = async (req: Request): Promise<TGuildDatabaseDocument> => { export const getGuildForRequest = async (req: Request): Promise<TGuildDatabaseDocument> => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -551,19 +550,6 @@ export const processFundedGuildTechProject = (
guild.XP += recipe.guildXpValue; guild.XP += recipe.guildXpValue;
} }
setGuildTechLogState(guild, techProject.ItemType, config.noDojoResearchTime ? 4 : 3, techProject.CompletionDate); setGuildTechLogState(guild, techProject.ItemType, config.noDojoResearchTime ? 4 : 3, techProject.CompletionDate);
if (config.noDojoResearchTime) {
processCompletedGuildTechProject(guild, techProject.ItemType);
}
};
export const processCompletedGuildTechProject = (guild: TGuildDatabaseDocument, type: string): void => {
if (type.startsWith("/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/")) {
guild.VaultDecoRecipes ??= [];
guild.VaultDecoRecipes.push({
ItemType: type,
ItemCount: 1
});
}
}; };
export const setGuildTechLogState = ( export const setGuildTechLogState = (

View File

@ -1,12 +1,18 @@
import { Types } from "mongoose"; import { Types } from "mongoose";
import { import {
IEquipmentClient,
IEquipmentDatabase,
IItemConfig, IItemConfig,
IOperatorConfigClient, IOperatorConfigClient,
IOperatorConfigDatabase IOperatorConfigDatabase
} from "@/src/types/inventoryTypes/commonInventoryTypes"; } from "../types/inventoryTypes/commonInventoryTypes";
import { IMongoDate } from "@/src/types/commonTypes"; import { IMongoDate } from "../types/commonTypes";
import { import {
equipmentKeys, equipmentKeys,
ICrewShipMemberClient,
ICrewShipMemberDatabase,
ICrewShipMembersClient,
ICrewShipMembersDatabase,
IDialogueClient, IDialogueClient,
IDialogueDatabase, IDialogueDatabase,
IDialogueHistoryClient, IDialogueHistoryClient,
@ -14,6 +20,10 @@ import {
IInfestedFoundryClient, IInfestedFoundryClient,
IInfestedFoundryDatabase, IInfestedFoundryDatabase,
IInventoryClient, IInventoryClient,
IKubrowPetDetailsClient,
IKubrowPetDetailsDatabase,
ILoadoutConfigClient,
ILoadOutPresets,
INemesisClient, INemesisClient,
INemesisDatabase, INemesisDatabase,
IPendingRecipeClient, IPendingRecipeClient,
@ -25,25 +35,10 @@ import {
IUpgradeDatabase, IUpgradeDatabase,
IWeaponSkinClient, IWeaponSkinClient,
IWeaponSkinDatabase IWeaponSkinDatabase
} from "@/src/types/inventoryTypes/inventoryTypes"; } from "../types/inventoryTypes/inventoryTypes";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
import { import { ILoadoutConfigDatabase, ILoadoutDatabase } from "../types/saveLoadoutTypes";
ILoadoutConfigClient, import { slotNames } from "../types/purchaseTypes";
ILoadoutConfigDatabase,
ILoadoutDatabase,
ILoadOutPresets
} from "@/src/types/saveLoadoutTypes";
import { slotNames } from "@/src/types/purchaseTypes";
import {
ICrewShipMemberClient,
ICrewShipMemberDatabase,
ICrewShipMembersClient,
ICrewShipMembersDatabase,
IEquipmentClient,
IEquipmentDatabase,
IKubrowPetDetailsClient,
IKubrowPetDetailsDatabase
} from "@/src/types/equipmentTypes";
const convertDate = (value: IMongoDate): Date => { const convertDate = (value: IMongoDate): Date => {
return new Date(parseInt(value.$date.$numberLong)); return new Date(parseInt(value.$date.$numberLong));

View File

@ -2,8 +2,8 @@ import { IMessageDatabase, Inbox } from "@/src/models/inboxModel";
import { getAccountForRequest } from "@/src/services/loginService"; import { getAccountForRequest } from "@/src/services/loginService";
import { HydratedDocument, Types } from "mongoose"; import { HydratedDocument, Types } from "mongoose";
import { Request } from "express"; import { Request } from "express";
import { unixTimesInMs } from "@/src/constants/timeConstants"; import { unixTimesInMs } from "../constants/timeConstants";
import { config } from "@/src/services/configService"; import { config } from "./configService";
export const getAllMessagesSorted = async (accountId: string): Promise<HydratedDocument<IMessageDatabase>[]> => { export const getAllMessagesSorted = async (accountId: string): Promise<HydratedDocument<IMessageDatabase>[]> => {
const inbox = await Inbox.find({ ownerId: accountId }).sort({ date: -1 }); const inbox = await Inbox.find({ ownerId: accountId }).sort({ date: -1 });

View File

@ -1,9 +1,8 @@
import { ExportRecipes } from "warframe-public-export-plus"; import { ExportRecipes } from "warframe-public-export-plus";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
import { IInfestedFoundryClient, IInfestedFoundryDatabase } from "@/src/types/inventoryTypes/inventoryTypes"; import { IInfestedFoundryClient, IInfestedFoundryDatabase, ITypeCount } from "../types/inventoryTypes/inventoryTypes";
import { addRecipes } from "@/src/services/inventoryService"; import { addRecipes } from "./inventoryService";
import { config } from "@/src/services/configService"; import { config } from "./configService";
import { ITypeCount } from "@/src/types/commonTypes";
export const addInfestedFoundryXP = (infestedFoundry: IInfestedFoundryDatabase, delta: number): ITypeCount[] => { export const addInfestedFoundryXP = (infestedFoundry: IInfestedFoundryDatabase, delta: number): ITypeCount[] => {
const recipeChanges: ITypeCount[] = []; const recipeChanges: ITypeCount[] = [];

View File

@ -4,10 +4,12 @@ import { Types } from "mongoose";
import { SlotNames, IInventoryChanges, IBinChanges, slotNames, IAffiliationMods } from "@/src/types/purchaseTypes"; import { SlotNames, IInventoryChanges, IBinChanges, slotNames, IAffiliationMods } from "@/src/types/purchaseTypes";
import { import {
IChallengeProgress, IChallengeProgress,
IFlavourItem,
IMiscItem, IMiscItem,
IMission, IMission,
IRawUpgrade, IRawUpgrade,
ISeasonChallenge, ISeasonChallenge,
ITypeCount,
InventorySlot, InventorySlot,
IWeaponSkinClient, IWeaponSkinClient,
TEquipmentKey, TEquipmentKey,
@ -21,17 +23,25 @@ import {
TPartialStartingGear, TPartialStartingGear,
ILoreFragmentScan, ILoreFragmentScan,
ICrewMemberClient, ICrewMemberClient,
Status,
IKubrowPetDetailsDatabase,
ITraits,
ICalendarProgress, ICalendarProgress,
INemesisWeaponTargetFingerprint, INemesisWeaponTargetFingerprint,
INemesisPetTargetFingerprint, INemesisPetTargetFingerprint,
IDialogueDatabase, IDialogueDatabase,
IKubrowPetPrintClient IKubrowPetPrintClient
} from "@/src/types/inventoryTypes/inventoryTypes"; } from "@/src/types/inventoryTypes/inventoryTypes";
import { IGenericUpdate, IUpdateNodeIntrosResponse } from "@/src/types/genericUpdate"; import { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/genericUpdate";
import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "@/src/types/requestTypes"; import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { convertInboxMessage, fromStoreItem, getKeyChainItems } from "@/src/services/itemDataService"; import { convertInboxMessage, fromStoreItem, getKeyChainItems } from "@/src/services/itemDataService";
import { IFlavourItem, IItemConfig } from "@/src/types/inventoryTypes/commonInventoryTypes"; import {
EquipmentFeatures,
IEquipmentClient,
IEquipmentDatabase,
IItemConfig
} from "../types/inventoryTypes/commonInventoryTypes";
import { import {
ExportArcanes, ExportArcanes,
ExportBoosters, ExportBoosters,
@ -59,7 +69,7 @@ import {
ISentinel, ISentinel,
TStandingLimitBin TStandingLimitBin
} from "warframe-public-export-plus"; } from "warframe-public-export-plus";
import { createShip } from "@/src/services/shipService"; import { createShip } from "./shipService";
import { import {
catbrowDetails, catbrowDetails,
fromMongoDate, fromMongoDate,
@ -67,36 +77,20 @@ import {
kubrowDetails, kubrowDetails,
kubrowFurPatternsWeights, kubrowFurPatternsWeights,
kubrowWeights, kubrowWeights,
toOid, toOid
TTraitsPool } from "../helpers/inventoryHelpers";
} from "@/src/helpers/inventoryHelpers";
import { addQuestKey, completeQuest } from "@/src/services/questService"; import { addQuestKey, completeQuest } from "@/src/services/questService";
import { handleBundleAcqusition } from "@/src/services/purchaseService"; import { handleBundleAcqusition } from "./purchaseService";
import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json"; import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json";
import { import { getRandomElement, getRandomInt, getRandomWeightedReward, SRng } from "./rngService";
generateRewardSeed, import { createMessage } from "./inboxService";
getRandomElement,
getRandomInt,
getRandomWeightedReward,
SRng
} from "@/src/services/rngService";
import { createMessage, IMessageCreationTemplate } from "@/src/services/inboxService";
import { getMaxStanding, getMinStanding } from "@/src/helpers/syndicateStandingHelper"; import { getMaxStanding, getMinStanding } from "@/src/helpers/syndicateStandingHelper";
import { getNightwaveSyndicateTag, getWorldState } from "@/src/services/worldStateService"; import { getNightwaveSyndicateTag, getWorldState } from "./worldStateService";
import { ICalendarSeason } from "@/src/types/worldStateTypes"; import { ICalendarSeason } from "@/src/types/worldStateTypes";
import { generateNemesisProfile, INemesisProfile } from "@/src/helpers/nemesisHelpers"; import { generateNemesisProfile, INemesisProfile } from "../helpers/nemesisHelpers";
import { TAccountDocument } from "@/src/services/loginService"; import { TAccountDocument } from "./loginService";
import { unixTimesInMs } from "@/src/constants/timeConstants"; import { unixTimesInMs } from "../constants/timeConstants";
import { addString } from "@/src/helpers/stringHelpers"; import { addString } from "../helpers/stringHelpers";
import {
EquipmentFeatures,
IEquipmentClient,
IEquipmentDatabase,
IKubrowPetDetailsDatabase,
ITraits,
Status
} from "@/src/types/equipmentTypes";
import { ITypeCount } from "@/src/types/commonTypes";
export const createInventory = async ( export const createInventory = async (
accountOwnerId: Types.ObjectId, accountOwnerId: Types.ObjectId,
@ -138,6 +132,17 @@ export const createInventory = async (
} }
}; };
export const generateRewardSeed = (): bigint => {
const hiDword = getRandomInt(0, 0x7fffffff);
const loDword = getRandomInt(0, 0xffffffff);
let seed = (BigInt(hiDword) << 32n) | BigInt(loDword);
if (Math.random() < 0.5) {
seed *= -1n;
seed -= 1n;
}
return seed;
};
//TODO: RawUpgrades might need to return a LastAdded //TODO: RawUpgrades might need to return a LastAdded
const awakeningRewards = [ const awakeningRewards = [
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem1", "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem1",
@ -478,19 +483,6 @@ export const addItem = async (
return addCustomization(inventory, typeName); return addCustomization(inventory, typeName);
} }
if (typeName in ExportUpgrades || typeName in ExportArcanes) { if (typeName in ExportUpgrades || typeName in ExportArcanes) {
if (targetFingerprint) {
if (quantity != 1) {
logger.warn(`adding 1 of ${typeName} ${targetFingerprint} even tho quantity ${quantity} was requested`);
}
const upgrade =
inventory.Upgrades[
inventory.Upgrades.push({
ItemType: typeName,
UpgradeFingerprint: targetFingerprint
}) - 1
];
return { Upgrades: [upgrade.toJSON<IUpgradeClient>()] };
}
const changes = [ const changes = [
{ {
ItemType: typeName, ItemType: typeName,
@ -815,7 +807,7 @@ export const addItem = async (
if (!seed) { if (!seed) {
throw new Error(`Expected crew member to have a seed`); throw new Error(`Expected crew member to have a seed`);
} }
seed |= BigInt(Math.trunc(inventory.Created.getTime() / 1000) & 0xffffff) << 32n; seed |= 0x33b81en << 32n;
return { return {
...addCrewMember(inventory, typeName, seed), ...addCrewMember(inventory, typeName, seed),
...occupySlot(inventory, InventorySlot.CREWMEMBERS, premiumPurchase) ...occupySlot(inventory, InventorySlot.CREWMEMBERS, premiumPurchase)
@ -1052,21 +1044,6 @@ export const addSpaceSuit = (
return inventoryChanges; return inventoryChanges;
}; };
const createRandomTraits = (kubrowPetName: string, traitsPool: TTraitsPool): ITraits => {
return {
BaseColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
SecondaryColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
TertiaryColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
AccentColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
EyeColor: getRandomWeightedReward(traitsPool.EyeColors, kubrowWeights)!.type,
FurPattern: getRandomWeightedReward(traitsPool.FurPatterns, kubrowFurPatternsWeights)!.type,
Personality: kubrowPetName,
BodyType: getRandomWeightedReward(traitsPool.BodyTypes, kubrowWeights)!.type,
Head: traitsPool.Heads.length ? getRandomWeightedReward(traitsPool.Heads, kubrowWeights)!.type : undefined,
Tail: traitsPool.Tails.length ? getRandomWeightedReward(traitsPool.Tails, kubrowWeights)!.type : undefined
};
};
export const addKubrowPet = ( export const addKubrowPet = (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
kubrowPetName: string, kubrowPetName: string,
@ -1083,6 +1060,7 @@ export const addKubrowPet = (
addSpecialItem(inventory, specialItem, inventoryChanges); addSpecialItem(inventory, specialItem, inventoryChanges);
} }
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const configs: IItemConfig[] = applyDefaultUpgrades(inventory, kubrowPet?.defaultUpgrades); const configs: IItemConfig[] = applyDefaultUpgrades(inventory, kubrowPet?.defaultUpgrades);
if (!details) { if (!details) {
@ -1092,10 +1070,9 @@ export const addKubrowPet = (
"/Lotus/Types/Game/CatbrowPet/VampireCatbrowPetPowerSuit" "/Lotus/Types/Game/CatbrowPet/VampireCatbrowPetPowerSuit"
].includes(kubrowPetName); ].includes(kubrowPetName);
const traitsPool = isCatbrow ? catbrowDetails : kubrowDetails; let traits: ITraits;
let dominantTraits: ITraits;
if (kubrowPetName == "/Lotus/Types/Game/CatbrowPet/VampireCatbrowPetPowerSuit") { if (kubrowPetName == "/Lotus/Types/Game/CatbrowPet/VampireCatbrowPetPowerSuit") {
dominantTraits = { traits = {
BaseColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseVampire", BaseColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseVampire",
SecondaryColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryVampire", SecondaryColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryVampire",
TertiaryColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryVampire", TertiaryColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryVampire",
@ -1108,35 +1085,19 @@ export const addKubrowPet = (
Tail: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailVampire" Tail: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailVampire"
}; };
} else { } else {
dominantTraits = createRandomTraits(kubrowPetName, traitsPool); const traitsPool = isCatbrow ? catbrowDetails : kubrowDetails;
if (kubrowPetName == "/Lotus/Types/Game/KubrowPet/ChargerKubrowPetPowerSuit") { traits = {
dominantTraits.BodyType = "/Lotus/Types/Game/KubrowPet/BodyTypes/ChargerKubrowPetBodyType"; BaseColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
dominantTraits.FurPattern = "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternInfested"; SecondaryColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
} TertiaryColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
} AccentColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
EyeColor: getRandomWeightedReward(traitsPool.EyeColors, kubrowWeights)!.type,
const recessiveTraits: ITraits = createRandomTraits( FurPattern: getRandomWeightedReward(traitsPool.FurPatterns, kubrowFurPatternsWeights)!.type,
getRandomElement( Personality: kubrowPetName,
isCatbrow BodyType: getRandomWeightedReward(traitsPool.BodyTypes, kubrowWeights)!.type,
? [ Head: isCatbrow ? getRandomWeightedReward(traitsPool.Heads, kubrowWeights)!.type : undefined,
"/Lotus/Types/Game/CatbrowPet/MirrorCatbrowPetPowerSuit", Tail: isCatbrow ? getRandomWeightedReward(traitsPool.Tails, kubrowWeights)!.type : undefined
"/Lotus/Types/Game/CatbrowPet/CheshireCatbrowPetPowerSuit" };
]
: [
"/Lotus/Types/Game/KubrowPet/AdventurerKubrowPetPowerSuit",
"/Lotus/Types/Game/KubrowPet/FurtiveKubrowPetPowerSuit",
"/Lotus/Types/Game/KubrowPet/GuardKubrowPetPowerSuit",
"/Lotus/Types/Game/KubrowPet/HunterKubrowPetPowerSuit",
"/Lotus/Types/Game/KubrowPet/RetrieverKubrowPetPowerSuit"
]
)!,
traitsPool
);
for (const key of Object.keys(recessiveTraits) as (keyof ITraits)[]) {
// My heurstic approximation is a 20% chance for a dominant trait to be copied into the recessive traits. TODO: A more scientific statistical analysis maybe?
if (Math.random() < 0.2) {
recessiveTraits[key] = dominantTraits[key]!;
}
} }
details = { details = {
@ -1148,8 +1109,8 @@ export const addKubrowPet = (
HatchDate: premiumPurchase ? new Date() : new Date(Date.now() + 10 * unixTimesInMs.hour), // On live, this seems to be somewhat randomised so that the pet hatches 9~11 hours after start. HatchDate: premiumPurchase ? new Date() : new Date(Date.now() + 10 * unixTimesInMs.hour), // On live, this seems to be somewhat randomised so that the pet hatches 9~11 hours after start.
IsMale: !!getRandomInt(0, 1), IsMale: !!getRandomInt(0, 1),
Size: getRandomInt(70, 100) / 100, Size: getRandomInt(70, 100) / 100,
DominantTraits: dominantTraits, DominantTraits: traits,
RecessiveTraits: recessiveTraits RecessiveTraits: traits
}; };
} }
@ -1295,8 +1256,8 @@ export const addStanding = (
const max = getMaxStanding(syndicateMeta, syndicate.Title ?? 0); const max = getMaxStanding(syndicateMeta, syndicate.Title ?? 0);
if (syndicate.Standing + gainedStanding > max) gainedStanding = max - syndicate.Standing; if (syndicate.Standing + gainedStanding > max) gainedStanding = max - syndicate.Standing;
if (syndicate.Standing + gainedStanding < -71000) { if (syndicate.Title == -2 && syndicate.Standing + gainedStanding < -71000) {
gainedStanding = -71000 - syndicate.Standing; gainedStanding = -71000 + syndicate.Standing;
} }
if (!isMedallion || syndicateMeta.medallionsCappedByDailyLimit) { if (!isMedallion || syndicateMeta.medallionsCappedByDailyLimit) {
@ -1424,11 +1385,7 @@ export const addSkin = (
if (inventory.WeaponSkins.some(x => x.ItemType == typeName)) { if (inventory.WeaponSkins.some(x => x.ItemType == typeName)) {
logger.debug(`refusing to add WeaponSkin ${typeName} because account already owns it`); logger.debug(`refusing to add WeaponSkin ${typeName} because account already owns it`);
} else { } else {
const index = const index = inventory.WeaponSkins.push({ ItemType: typeName, IsNew: true }) - 1;
inventory.WeaponSkins.push({
ItemType: typeName,
IsNew: typeName.startsWith("/Lotus/Upgrades/Skins/RailJack/") ? undefined : true // railjack skins are incompatible with this flag
}) - 1;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
inventoryChanges.WeaponSkins ??= []; inventoryChanges.WeaponSkins ??= [];
(inventoryChanges.WeaponSkins as IWeaponSkinClient[]).push( (inventoryChanges.WeaponSkins as IWeaponSkinClient[]).push(
@ -1606,22 +1563,7 @@ export const addEmailItem = async (
const meta = ExportEmailItems[typeName]; const meta = ExportEmailItems[typeName];
const emailItem = inventory.EmailItems.find(x => x.ItemType == typeName); const emailItem = inventory.EmailItems.find(x => x.ItemType == typeName);
if (!emailItem || !meta.sendOnlyOnce) { if (!emailItem || !meta.sendOnlyOnce) {
const msg: IMessageCreationTemplate = convertInboxMessage(meta.message); await createMessage(inventory.accountOwnerId, [convertInboxMessage(meta.message)]);
if (msg.cinematic == "/Lotus/Levels/1999/PlayerHomeBalconyCinematics.level") {
msg.customData = JSON.stringify({
Tag: msg.customData + "KissCin",
CinLoadout: {
Skins: inventory.AdultOperatorLoadOuts[0].Skins,
Upgrades: inventory.AdultOperatorLoadOuts[0].Upgrades,
attcol: inventory.AdultOperatorLoadOuts[0].attcol,
cloth: inventory.AdultOperatorLoadOuts[0].cloth,
eyecol: inventory.AdultOperatorLoadOuts[0].eyecol,
pricol: inventory.AdultOperatorLoadOuts[0].pricol,
syancol: inventory.AdultOperatorLoadOuts[0].syancol
}
});
}
await createMessage(inventory.accountOwnerId, [msg]);
if (emailItem) { if (emailItem) {
emailItem.ItemCount += 1; emailItem.ItemCount += 1;
@ -1635,15 +1577,6 @@ export const addEmailItem = async (
return inventoryChanges; return inventoryChanges;
}; };
const xpEarningParts: readonly string[] = [
"LWPT_BLADE",
"LWPT_GUN_BARREL",
"LWPT_AMP_OCULUS",
"LWPT_MOA_HEAD",
"LWPT_ZANUKA_HEAD",
"LWPT_HB_DECK"
];
export const applyClientEquipmentUpdates = ( export const applyClientEquipmentUpdates = (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
gearArray: IEquipmentClient[], gearArray: IEquipmentClient[],
@ -1662,26 +1595,13 @@ export const applyClientEquipmentUpdates = (
item.XP ??= 0; item.XP ??= 0;
item.XP += XP; item.XP += XP;
let xpItemType = item.ItemType; const xpinfoIndex = inventory.XPInfo.findIndex(x => x.ItemType == item.ItemType);
if (item.ModularParts) {
for (const part of item.ModularParts) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const partType = ExportWeapons[part]?.partType;
if (partType !== undefined && xpEarningParts.indexOf(partType) != -1) {
xpItemType = part;
break;
}
}
logger.debug(`adding xp to ${xpItemType} for modular item ${fromOid(ItemId)} (${item.ItemType})`);
}
const xpinfoIndex = inventory.XPInfo.findIndex(x => x.ItemType == xpItemType);
if (xpinfoIndex !== -1) { if (xpinfoIndex !== -1) {
const xpinfo = inventory.XPInfo[xpinfoIndex]; const xpinfo = inventory.XPInfo[xpinfoIndex];
xpinfo.XP += XP; xpinfo.XP += XP;
} else { } else {
inventory.XPInfo.push({ inventory.XPInfo.push({
ItemType: xpItemType, ItemType: item.ItemType,
XP: XP XP: XP
}); });
} }
@ -1954,7 +1874,7 @@ export const addCalendarProgress = (inventory: TInventoryDatabaseDocument, value
calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx = currentSeason.Days.findIndex( calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx = currentSeason.Days.findIndex(
day => day.events.length != 0 && day.events[0].challenge == value[value.length - 1].challenge day => day.events.length != 0 && day.events[0].challenge == value[value.length - 1].challenge
); );
checkCalendarAutoAdvance(inventory, currentSeason); checkCalendarChallengeCompletion(calendarProgress, currentSeason);
}; };
export const addMissionComplete = (inventory: TInventoryDatabaseDocument, { Tag, Completes, Tier }: IMission): void => { export const addMissionComplete = (inventory: TInventoryDatabaseDocument, { Tag, Completes, Tier }: IMission): void => {
@ -2139,8 +2059,8 @@ export const getCalendarProgress = (inventory: TInventoryDatabaseDocument): ICal
}, },
SeasonProgress: { SeasonProgress: {
SeasonType: currentSeason.Season, SeasonType: currentSeason.Season,
LastCompletedDayIdx: -1, LastCompletedDayIdx: 0,
LastCompletedChallengeDayIdx: -1, LastCompletedChallengeDayIdx: 0,
ActivatedChallenges: [] ActivatedChallenges: []
} }
}; };
@ -2161,44 +2081,16 @@ export const getCalendarProgress = (inventory: TInventoryDatabaseDocument): ICal
return inventory.CalendarProgress; return inventory.CalendarProgress;
}; };
export const checkCalendarAutoAdvance = ( export const checkCalendarChallengeCompletion = (
inventory: TInventoryDatabaseDocument, calendarProgress: ICalendarProgress,
currentSeason: ICalendarSeason currentSeason: ICalendarSeason
): void => { ): void => {
const calendarProgress = inventory.CalendarProgress!; const dayIndex = calendarProgress.SeasonProgress.LastCompletedDayIdx + 1;
for ( if (calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx >= dayIndex) {
let dayIndex = calendarProgress.SeasonProgress.LastCompletedDayIdx + 1;
dayIndex != currentSeason.Days.length;
++dayIndex
) {
const day = currentSeason.Days[dayIndex]; const day = currentSeason.Days[dayIndex];
if (day.events.length == 0) { if (day.events.length != 0 && day.events[0].type == "CET_CHALLENGE") {
// birthday
if (day.day == 1) {
// kaya
if ((inventory.Affiliations.find(x => x.Tag == "HexSyndicate")?.Title || 0) >= 4) {
break;
}
logger.debug(`cannot talk to kaya, skipping birthday`);
calendarProgress.SeasonProgress.LastCompletedDayIdx++;
} else if (day.day == 74 || day.day == 355) {
// minerva, velimir
if ((inventory.Affiliations.find(x => x.Tag == "HexSyndicate")?.Title || 0) >= 5) {
break;
}
logger.debug(`cannot talk to minerva/velimir, skipping birthday`);
calendarProgress.SeasonProgress.LastCompletedDayIdx++;
} else {
break;
}
} else if (day.events[0].type == "CET_CHALLENGE") {
if (calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx < dayIndex) {
break;
}
//logger.debug(`already completed the challenge, skipping ahead`); //logger.debug(`already completed the challenge, skipping ahead`);
calendarProgress.SeasonProgress.LastCompletedDayIdx++; calendarProgress.SeasonProgress.LastCompletedDayIdx++;
} else {
break;
} }
} }
}; };

View File

@ -17,7 +17,6 @@ import {
dict_zh, dict_zh,
ExportArcanes, ExportArcanes,
ExportBoosters, ExportBoosters,
ExportBundles,
ExportCustoms, ExportCustoms,
ExportDrones, ExportDrones,
ExportGear, ExportGear,
@ -33,7 +32,7 @@ import {
IRecipe, IRecipe,
TReward TReward
} from "warframe-public-export-plus"; } from "warframe-public-export-plus";
import { IMessage } from "@/src/models/inboxModel"; import { IMessage } from "../models/inboxModel";
export type WeaponTypeInternal = export type WeaponTypeInternal =
| "LongGuns" | "LongGuns"
@ -46,39 +45,6 @@ export type WeaponTypeInternal =
| "SpecialItems"; | "SpecialItems";
export const getRecipe = (uniqueName: string): IRecipe | undefined => { export const getRecipe = (uniqueName: string): IRecipe | undefined => {
// Handle crafting of archwing summon for versions prior to 39.0.0 as this blueprint was removed then.
if (uniqueName == "/Lotus/Types/Recipes/EidolonRecipes/OpenArchwingSummonBlueprint") {
return {
resultType: "/Lotus/Types/Restoratives/OpenArchwingSummon",
buildPrice: 7500,
buildTime: 1800,
skipBuildTimePrice: 10,
consumeOnUse: false,
num: 1,
codexSecret: false,
alwaysAvailable: true,
ingredients: [
{
ItemType: "/Lotus/Types/Gameplay/Eidolon/Resources/IraditeItem",
ItemCount: 50
},
{
ItemType: "/Lotus/Types/Gameplay/Eidolon/Resources/GrokdrulItem",
ItemCount: 50
},
{
ItemType: "/Lotus/Types/Items/Fish/Eidolon/FishParts/EidolonFishOilItem",
ItemCount: 30
},
{
ItemType: "/Lotus/Types/Items/MiscItems/Circuits",
ItemCount: 600
}
],
excludeFromMarket: true
};
}
return ExportRecipes[uniqueName]; return ExportRecipes[uniqueName];
}; };
@ -118,9 +84,6 @@ export const getItemName = (uniqueName: string): string | undefined => {
if (uniqueName in ExportArcanes) { if (uniqueName in ExportArcanes) {
return ExportArcanes[uniqueName].name; return ExportArcanes[uniqueName].name;
} }
if (uniqueName in ExportBundles) {
return ExportBundles[uniqueName].name;
}
if (uniqueName in ExportCustoms) { if (uniqueName in ExportCustoms) {
return ExportCustoms[uniqueName].name; return ExportCustoms[uniqueName].name;
} }
@ -255,9 +218,7 @@ export const convertInboxMessage = (message: IInboxMessage): IMessage => {
return { return {
sndr: message.sender, sndr: message.sender,
msg: message.body, msg: message.body,
cinematic: message.cinematic,
sub: message.title, sub: message.title,
customData: message.customData,
att: message.attachments.length > 0 ? message.attachments : undefined, att: message.attachments.length > 0 ? message.attachments : undefined,
countedAtt: message.countedAttachments.length > 0 ? message.countedAttachments : undefined, countedAtt: message.countedAttachments.length > 0 ? message.countedAttachments : undefined,
icon: message.icon ?? "", icon: message.icon ?? "",

View File

@ -1,6 +1,6 @@
import { Guild } from "@/src/models/guildModel"; import { Guild } from "../models/guildModel";
import { Leaderboard, TLeaderboardEntryDocument } from "@/src/models/leaderboardModel"; import { Leaderboard, TLeaderboardEntryDocument } from "../models/leaderboardModel";
import { ILeaderboardEntryClient } from "@/src/types/leaderboardTypes"; import { ILeaderboardEntryClient } from "../types/leaderboardTypes";
export const submitLeaderboardScore = async ( export const submitLeaderboardScore = async (
schedule: "weekly" | "daily", schedule: "weekly" | "daily",

View File

@ -1,10 +1,10 @@
import randomRewards from "@/static/fixed_responses/loginRewards/randomRewards.json"; import randomRewards from "@/static/fixed_responses/loginRewards/randomRewards.json";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "../types/purchaseTypes";
import { TAccountDocument } from "@/src/services/loginService"; import { TAccountDocument } from "./loginService";
import { mixSeeds, SRng } from "@/src/services/rngService"; import { mixSeeds, SRng } from "./rngService";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
import { addBooster, updateCurrency } from "@/src/services/inventoryService"; import { addBooster, updateCurrency } from "./inventoryService";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService"; import { handleStoreItemAcquisition } from "./purchaseService";
import { import {
ExportBoosterPacks, ExportBoosterPacks,
ExportBoosters, ExportBoosters,
@ -12,7 +12,7 @@ import {
ExportWarframes, ExportWarframes,
ExportWeapons ExportWeapons
} from "warframe-public-export-plus"; } from "warframe-public-export-plus";
import { toStoreItem } from "@/src/services/itemDataService"; import { toStoreItem } from "./itemDataService";
export interface ILoginRewardsReponse { export interface ILoginRewardsReponse {
DailyTributeInfo: { DailyTributeInfo: {

View File

@ -1,7 +1,7 @@
import { Account } from "@/src/models/loginModel"; import { Account } from "@/src/models/loginModel";
import { createInventory } from "@/src/services/inventoryService"; import { createInventory } from "@/src/services/inventoryService";
import { IDatabaseAccountJson, IDatabaseAccountRequiredFields } from "@/src/types/loginTypes"; import { IDatabaseAccountJson, IDatabaseAccountRequiredFields } from "@/src/types/loginTypes";
import { createShip } from "@/src/services/shipService"; import { createShip } from "./shipService";
import { Document, Types } from "mongoose"; import { Document, Types } from "mongoose";
import { Loadout } from "@/src/models/inventoryModels/loadoutModel"; import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
import { PersonalRooms } from "@/src/models/personalRoomsModel"; import { PersonalRooms } from "@/src/models/personalRoomsModel";

View File

@ -8,10 +8,10 @@ import {
IRegion, IRegion,
IReward IReward
} from "warframe-public-export-plus"; } from "warframe-public-export-plus";
import { IMissionInventoryUpdateRequest, IRewardInfo } from "@/src/types/requestTypes"; import { IMissionInventoryUpdateRequest, IRewardInfo } from "../types/requestTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { IRngResult, SRng, generateRewardSeed, getRandomElement, getRandomReward } from "@/src/services/rngService"; import { IRngResult, SRng, getRandomElement, getRandomReward } from "@/src/services/rngService";
import { equipmentKeys, IMission, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes"; import { equipmentKeys, IMission, ITypeCount, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import { import {
addBooster, addBooster,
addCalendarProgress, addCalendarProgress,
@ -35,6 +35,7 @@ import {
addStanding, addStanding,
applyClientEquipmentUpdates, applyClientEquipmentUpdates,
combineInventoryChanges, combineInventoryChanges,
generateRewardSeed,
getDialogue, getDialogue,
giveNemesisPetRecipe, giveNemesisPetRecipe,
giveNemesisWeaponRecipe, giveNemesisWeaponRecipe,
@ -47,10 +48,11 @@ import { IAffiliationMods, IInventoryChanges } from "@/src/types/purchaseTypes";
import { fromStoreItem, getLevelKeyRewards, isStoreItem, toStoreItem } from "@/src/services/itemDataService"; import { fromStoreItem, getLevelKeyRewards, isStoreItem, toStoreItem } from "@/src/services/itemDataService";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { getEntriesUnsafe } from "@/src/utils/ts-utils"; import { getEntriesUnsafe } from "@/src/utils/ts-utils";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService"; import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { IMissionCredits, IMissionReward } from "@/src/types/missionTypes"; import { handleStoreItemAcquisition } from "./purchaseService";
import { IMissionCredits, IMissionReward } from "../types/missionTypes";
import { crackRelic } from "@/src/helpers/relicHelper"; import { crackRelic } from "@/src/helpers/relicHelper";
import { createMessage } from "@/src/services/inboxService"; import { createMessage } from "./inboxService";
import kuriaMessage50 from "@/static/fixed_responses/kuriaMessages/fiftyPercent.json"; import kuriaMessage50 from "@/static/fixed_responses/kuriaMessages/fiftyPercent.json";
import kuriaMessage75 from "@/static/fixed_responses/kuriaMessages/seventyFivePercent.json"; import kuriaMessage75 from "@/static/fixed_responses/kuriaMessages/seventyFivePercent.json";
import kuriaMessage100 from "@/static/fixed_responses/kuriaMessages/oneHundredPercent.json"; import kuriaMessage100 from "@/static/fixed_responses/kuriaMessages/oneHundredPercent.json";
@ -63,8 +65,8 @@ import {
getNemesisManifest, getNemesisManifest,
getNemesisPasscode getNemesisPasscode
} from "@/src/helpers/nemesisHelpers"; } from "@/src/helpers/nemesisHelpers";
import { Loadout } from "@/src/models/inventoryModels/loadoutModel"; import { Loadout } from "../models/inventoryModels/loadoutModel";
import { ILoadoutConfigDatabase } from "@/src/types/saveLoadoutTypes"; import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes";
import { import {
getLiteSortie, getLiteSortie,
getSortie, getSortie,
@ -73,14 +75,12 @@ import {
idToDay, idToDay,
idToWeek, idToWeek,
pushClassicBounties pushClassicBounties
} from "@/src/services/worldStateService"; } from "./worldStateService";
import { config } from "@/src/services/configService"; import { config } from "./configService";
import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json"; import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json";
import { ISyndicateMissionInfo } from "@/src/types/worldStateTypes"; import { ISyndicateMissionInfo } from "../types/worldStateTypes";
import { fromOid } from "@/src/helpers/inventoryHelpers"; import { fromOid } from "../helpers/inventoryHelpers";
import { TAccountDocument } from "@/src/services/loginService"; import { TAccountDocument } from "./loginService";
import { ITypeCount } from "@/src/types/commonTypes";
import { IEquipmentClient } from "@/src/types/equipmentTypes";
const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[] => { const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[] => {
// For Spy missions, e.g. 3 vaults cracked = A, B, C // For Spy missions, e.g. 3 vaults cracked = A, B, C
@ -558,7 +558,6 @@ export const addMissionInventoryUpdates = async (
} }
]); ]);
} }
inventory.DeathSquadable = false;
break; break;
} }
case "LockedWeaponGroup": { case "LockedWeaponGroup": {
@ -577,7 +576,7 @@ export const addMissionInventoryUpdates = async (
break; break;
} }
case "IncHarvester": { case "IncHarvester": {
// Unsure what to do with this inventory.Harvestable = true;
break; break;
} }
case "CurrentLoadOutIds": { case "CurrentLoadOutIds": {

View File

@ -1,7 +1,8 @@
import { PersonalRooms } from "@/src/models/personalRoomsModel"; import { PersonalRooms } from "@/src/models/personalRoomsModel";
import { addItem, getInventory } from "@/src/services/inventoryService"; import { addItem, getInventory } from "@/src/services/inventoryService";
import { IGardeningDatabase, TPersonalRoomsDatabaseDocument } from "@/src/types/personalRoomsTypes"; import { TPersonalRoomsDatabaseDocument } from "../types/personalRoomsTypes";
import { getRandomElement } from "@/src/services/rngService"; import { IGardeningDatabase } from "../types/shipTypes";
import { getRandomElement } from "./rngService";
export const getPersonalRooms = async ( export const getPersonalRooms = async (
accountId: string, accountId: string,

View File

@ -20,7 +20,8 @@ import {
IPurchaseParams IPurchaseParams
} from "@/src/types/purchaseTypes"; } from "@/src/types/purchaseTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { getWorldState } from "@/src/services/worldStateService"; import { getWorldState } from "./worldStateService";
import staticWorldState from "@/static/fixed_responses/worldState/worldState.json";
import { import {
ExportBoosterPacks, ExportBoosterPacks,
ExportBoosters, ExportBoosters,
@ -32,11 +33,11 @@ import {
ExportVendors, ExportVendors,
TRarity TRarity
} from "warframe-public-export-plus"; } from "warframe-public-export-plus";
import { config } from "@/src/services/configService"; import { config } from "./configService";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
import { fromStoreItem, toStoreItem } from "@/src/services/itemDataService"; import { fromStoreItem, toStoreItem } from "./itemDataService";
import { DailyDeal } from "@/src/models/worldStateModel"; import { DailyDeal } from "../models/worldStateModel";
import { fromMongoDate, toMongoDate } from "@/src/helpers/inventoryHelpers"; import { fromMongoDate, toMongoDate } from "../helpers/inventoryHelpers";
export const getStoreItemCategory = (storeItem: string): string => { export const getStoreItemCategory = (storeItem: string): string => {
const storeItemString = getSubstringFromKeyword(storeItem, "StoreItems/"); const storeItemString = getSubstringFromKeyword(storeItem, "StoreItems/");
@ -304,15 +305,14 @@ export const handlePurchase = async (
} }
break; break;
case PurchaseSource.PrimeVaultTrader: { case PurchaseSource.PrimeVaultTrader: {
const worldState = getWorldState(); if (purchaseRequest.PurchaseParams.SourceId! != staticWorldState.PrimeVaultTraders[0]._id.$oid) {
if (purchaseRequest.PurchaseParams.SourceId! != worldState.PrimeVaultTraders[0]._id.$oid) {
throw new Error("invalid request source"); throw new Error("invalid request source");
} }
const offer = const offer =
worldState.PrimeVaultTraders[0].Manifest.find( staticWorldState.PrimeVaultTraders[0].Manifest.find(
x => x.ItemType == purchaseRequest.PurchaseParams.StoreItem x => x.ItemType == purchaseRequest.PurchaseParams.StoreItem
) ?? ) ??
worldState.PrimeVaultTraders[0].EvergreenManifest.find( staticWorldState.PrimeVaultTraders[0].EvergreenManifest.find(
x => x.ItemType == purchaseRequest.PurchaseParams.StoreItem x => x.ItemType == purchaseRequest.PurchaseParams.StoreItem
); );
if (offer) { if (offer) {

View File

@ -4,14 +4,13 @@ import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/invento
import { createMessage } from "@/src/services/inboxService"; import { createMessage } from "@/src/services/inboxService";
import { addItem, addItems, addKeyChainItems, setupKahlSyndicate } from "@/src/services/inventoryService"; import { addItem, addItems, addKeyChainItems, setupKahlSyndicate } from "@/src/services/inventoryService";
import { fromStoreItem, getKeyChainMessage, getLevelKeyRewards } from "@/src/services/itemDataService"; import { fromStoreItem, getKeyChainMessage, getLevelKeyRewards } from "@/src/services/itemDataService";
import { IQuestKeyClient, IQuestKeyDatabase, IQuestStage } from "@/src/types/inventoryTypes/inventoryTypes"; import { IQuestKeyClient, IQuestKeyDatabase, IQuestStage, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { ExportKeys } from "warframe-public-export-plus"; import { ExportKeys } from "warframe-public-export-plus";
import { addFixedLevelRewards } from "@/src/services/missionInventoryUpdateService"; import { addFixedLevelRewards } from "./missionInventoryUpdateService";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "../types/purchaseTypes";
import questCompletionItems from "@/static/fixed_responses/questCompletionRewards.json"; import questCompletionItems from "@/static/fixed_responses/questCompletionRewards.json";
import { ITypeCount } from "@/src/types/commonTypes";
export interface IUpdateQuestRequest { export interface IUpdateQuestRequest {
QuestKeys: Omit<IQuestKeyDatabase, "CompletionDate">[]; QuestKeys: Omit<IQuestKeyDatabase, "CompletionDate">[];
@ -236,7 +235,7 @@ const handleQuestCompletion = async (
setupKahlSyndicate(inventory); setupKahlSyndicate(inventory);
} }
// Whispers in the Walls is unlocked once The New War + Heart of Deimos are completed. // Whispers in the Walls is unlocked once The New + Heart of Deimos are completed.
if ( if (
doesQuestCompletionFinishSet(inventory, questKey, [ doesQuestCompletionFinishSet(inventory, questKey, [
"/Lotus/Types/Keys/NewWarQuest/NewWarQuestKeyChain", "/Lotus/Types/Keys/NewWarQuest/NewWarQuestKeyChain",

View File

@ -18,17 +18,6 @@ export const getRandomInt = (min: number, max: number): number => {
return Math.floor(Math.random() * (max - min + 1)) + min; return Math.floor(Math.random() * (max - min + 1)) + min;
}; };
export const generateRewardSeed = (): bigint => {
const hiDword = getRandomInt(0, 0x7fffffff);
const loDword = getRandomInt(0, 0xffffffff);
let seed = (BigInt(hiDword) << 32n) | BigInt(loDword);
if (Math.random() < 0.5) {
seed *= -1n;
seed -= 1n;
}
return seed;
};
export const getRewardAtPercentage = <T extends { probability: number }>( export const getRewardAtPercentage = <T extends { probability: number }>(
pool: T[], pool: T[],
percentage: number percentage: number
@ -151,57 +140,4 @@ export class SRng {
arr[lastIdx] = tmp; arr[lastIdx] = tmp;
} }
} }
shuffledArray<T>(inarr: readonly T[]): T[] {
const arr = [...inarr];
this.shuffleArray(arr);
return arr;
}
} }
export const sequentiallyUniqueRandomElement = <T>(
deck: readonly T[],
idx: number,
lookbehind: number,
seed: number = 0
): T | undefined => {
// This algorithm may modify a shuffle up to index `lookbehind + 1`. It assumes that the last `lookbehind` cards are not adjusted.
if (lookbehind + 1 >= deck.length - lookbehind) {
throw new Error(
`this algorithm cannot guarantee ${lookbehind} unique cards in a row with a deck of size ${deck.length}`
);
}
const iteration = Math.trunc(idx / deck.length);
const card = idx % deck.length;
const currentShuffle = new SRng(mixSeeds(new SRng(iteration).randomInt(0, 100_000), seed)).shuffledArray(deck);
if (card < currentShuffle.length - lookbehind) {
// We are indexing before the end of the deck, so adjustments may be needed to achieve uniqueness.
const window: T[] = [];
{
const previousShuffle = new SRng(
mixSeeds(new SRng(iteration - 1).randomInt(0, 100_000), seed)
).shuffledArray(deck);
for (let i = previousShuffle.length - lookbehind; i != previousShuffle.length; ++i) {
window.push(previousShuffle[i]);
}
}
// From this point on, `window.length == lookbehind` should hold.
for (let i = 0; i != lookbehind; ++i) {
if (window.indexOf(currentShuffle[i]) != -1) {
for (let j = i; ; ++j) {
// `j < currentShuffle.length - lookbehind` should hold.
if (window.indexOf(currentShuffle[j]) == -1) {
const tmp = currentShuffle[j];
currentShuffle[j] = currentShuffle[i];
currentShuffle[i] = tmp;
break;
}
}
}
window.splice(0, 1);
window.push(currentShuffle[i]);
}
}
return currentShuffle[card];
};

View File

@ -13,8 +13,8 @@ import { Types } from "mongoose";
import { isEmptyObject } from "@/src/helpers/general"; import { isEmptyObject } from "@/src/helpers/general";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { equipmentKeys, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes"; import { equipmentKeys, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import { IItemConfig } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { IItemConfig } from "../types/inventoryTypes/commonInventoryTypes";
import { importCrewMemberId } from "@/src/services/importService"; import { importCrewMemberId } from "./importService";
//TODO: setup default items on account creation or like originally in giveStartingItems.php //TODO: setup default items on account creation or like originally in giveStartingItems.php
@ -167,23 +167,8 @@ export const handleInventoryItemConfigChange = async (
inventory.LotusCustomization = equipmentChanges.LotusCustomization; inventory.LotusCustomization = equipmentChanges.LotusCustomization;
break; break;
} }
case "ValidNewLoadoutId": {
logger.debug(`ignoring ValidNewLoadoutId (${equipmentChanges.ValidNewLoadoutId})`);
// seems always equal to the id of loadout config NORMAL[0], likely has no purpose and we're free to ignore it
break;
}
case "ActiveCrewShip": {
if (inventory.CrewShips.length != 1) {
logger.warn(`saving railjack changes with broken inventory?`);
} else if (!inventory.CrewShips[0]._id.equals(equipmentChanges.ActiveCrewShip.$oid)) {
logger.warn(
`client provided CrewShip id ${equipmentChanges.ActiveCrewShip.$oid} but id in inventory is ${inventory.CrewShips[0]._id.toString()}`
);
}
break;
}
default: { default: {
if (equipmentKeys.includes(equipmentName as TEquipmentKey)) { if (equipmentKeys.includes(equipmentName as TEquipmentKey) && equipmentName != "ValidNewLoadoutId") {
logger.debug(`general Item config saved of type ${equipmentName}`, { logger.debug(`general Item config saved of type ${equipmentName}`, {
config: equipment config: equipment
}); });
@ -231,7 +216,7 @@ export const handleInventoryItemConfigChange = async (
} }
break; break;
} else { } else {
logger.warn(`unknown saveLoadout field: ${equipmentName}`, { logger.warn(`loadout category not implemented, changes may be lost: ${equipmentName}`, {
config: equipment config: equipment
}); });
} }

View File

@ -6,7 +6,7 @@ import { mixSeeds, SRng } from "@/src/services/rngService";
import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes"; import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { ExportVendors, IRange, IVendor, IVendorOffer } from "warframe-public-export-plus"; import { ExportVendors, IRange, IVendor, IVendorOffer } from "warframe-public-export-plus";
import { config } from "@/src/services/configService"; import { config } from "./configService";
interface IGeneratableVendorInfo extends Omit<IVendorInfo, "ItemManifest" | "Expiry"> { interface IGeneratableVendorInfo extends Omit<IVendorInfo, "ItemManifest" | "Expiry"> {
cycleOffset?: number; cycleOffset?: number;
@ -299,12 +299,9 @@ const generateVendorManifest = (
? numUncountedOffers + numCountedOffers ? numUncountedOffers + numCountedOffers
: manifest.numItems : manifest.numItems
? numUncountedOffers + ? numUncountedOffers +
Math.min( (useRng
Object.values(remainingItemCapacity).reduce((a, b) => a + b, 0), ? rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue)
useRng : manifest.numItems.minValue)
? rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue)
: manifest.numItems.minValue
)
: manifest.items.length; : manifest.items.length;
let i = 0; let i = 0;
const rollableOffers = manifest.items.filter(x => x.probability !== undefined) as (Omit< const rollableOffers = manifest.items.filter(x => x.probability !== undefined) as (Omit<
@ -498,13 +495,4 @@ if (args.dev) {
) { ) {
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Ostron/MaskSalesmanManifest`); logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Ostron/MaskSalesmanManifest`);
} }
// strange case where numItems is 5 even tho only 3 offers can possibly be generated
const loid = getVendorManifestByTypeName(
"/Lotus/Types/Game/VendorManifests/EntratiLabs/EntratiLabsCommisionsManifest",
false
)!.VendorInfo.ItemManifest;
if (loid.length != 3) {
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/EntratiLabs/EntratiLabsCommisionsManifest`);
}
} }

View File

@ -1,22 +1,21 @@
import { getPersonalRooms } from "@/src/services/personalRoomsService"; import { getPersonalRooms } from "@/src/services/personalRoomsService";
import { getShip } from "@/src/services/shipService"; import { getShip } from "@/src/services/shipService";
import { import {
ISetPlacedDecoInfoRequest,
ISetShipCustomizationsRequest, ISetShipCustomizationsRequest,
IShipDecorationsRequest, IShipDecorationsRequest,
IShipDecorationsResponse, IShipDecorationsResponse,
RoomsType, ISetPlacedDecoInfoRequest,
TBootLocation, TBootLocation
TPersonalRoomsDatabaseDocument } from "@/src/types/shipTypes";
} from "@/src/types/personalRoomsTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { addFusionTreasures, addShipDecorations, getInventory } from "@/src/services/inventoryService"; import { addFusionTreasures, addShipDecorations, getInventory } from "./inventoryService";
import { config } from "@/src/services/configService"; import { config } from "./configService";
import { Guild } from "@/src/models/guildModel"; import { Guild } from "../models/guildModel";
import { hasGuildPermission } from "@/src/services/guildService"; import { hasGuildPermission } from "./guildService";
import { GuildPermission } from "@/src/types/guildTypes"; import { GuildPermission } from "../types/guildTypes";
import { ExportResources } from "warframe-public-export-plus"; import { ExportResources } from "warframe-public-export-plus";
import { RoomsType, TPersonalRoomsDatabaseDocument } from "../types/personalRoomsTypes";
export const setShipCustomizations = async ( export const setShipCustomizations = async (
accountId: string, accountId: string,
@ -40,7 +39,7 @@ export const setShipCustomizations = async (
personalRooms.TailorShop.LevelDecosVisible = shipCustomization.Customization.LevelDecosVisible; personalRooms.TailorShop.LevelDecosVisible = shipCustomization.Customization.LevelDecosVisible;
personalRooms.TailorShop.CustomJson = shipCustomization.Customization.CustomJson; personalRooms.TailorShop.CustomJson = shipCustomization.Customization.CustomJson;
} else { } else {
personalRooms.Ship.ShipInterior = shipCustomization.Customization; personalRooms.ShipInteriorColors = shipCustomization.Customization.Colors;
} }
await personalRooms.save(); await personalRooms.save();
} }
@ -65,12 +64,8 @@ export const handleSetShipDecorations = async (
throw new Error(`unknown room: ${placedDecoration.Room}`); throw new Error(`unknown room: ${placedDecoration.Room}`);
} }
const entry = Object.entries(ExportResources).find(arr => arr[1].deco == placedDecoration.Type); const [itemType, meta] = Object.entries(ExportResources).find(arr => arr[1].deco == placedDecoration.Type)!;
if (!entry) { if (!itemType || meta.capacityCost === undefined) {
throw new Error(`unknown deco type: ${placedDecoration.Type}`);
}
const [itemType, meta] = entry;
if (meta.capacityCost === undefined) {
throw new Error(`unknown deco type: ${placedDecoration.Type}`); throw new Error(`unknown deco type: ${placedDecoration.Type}`);
} }

View File

@ -10,7 +10,7 @@ import {
} from "@/src/types/statTypes"; } from "@/src/types/statTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { addEmailItem, getInventory } from "@/src/services/inventoryService"; import { addEmailItem, getInventory } from "@/src/services/inventoryService";
import { submitLeaderboardScore } from "@/src/services/leaderboardService"; import { submitLeaderboardScore } from "./leaderboardService";
export const createStats = async (accountId: string): Promise<TStatsDatabaseDocument> => { export const createStats = async (accountId: string): Promise<TStatsDatabaseDocument> => {
const stats = new Stats({ accountOwnerId: accountId }); const stats = new Stats({ accountOwnerId: accountId });

View File

@ -1,15 +1,21 @@
import http from "http"; import http from "http";
import https from "https"; import https from "https";
import fs from "node:fs"; import fs from "node:fs";
import { config } from "@/src/services/configService"; import { config } from "./configService";
import { logger } from "@/src/utils/logger"; import { logger } from "../utils/logger";
import { app } from "@/src/app"; import { app } from "../app";
import { AddressInfo } from "node:net"; import { AddressInfo } from "node:net";
import ws from "ws";
import { Account } from "../models/loginModel";
import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "./loginService";
import { IDatabaseAccountJson } from "../types/loginTypes";
import { HydratedDocument } from "mongoose";
import { Agent, WebSocket as UnidiciWebSocket } from "undici"; import { Agent, WebSocket as UnidiciWebSocket } from "undici";
import { startWsServer, startWssServer, stopWsServers } from "@/src/services/wsService";
let httpServer: http.Server | undefined; let httpServer: http.Server | undefined;
let httpsServer: https.Server | undefined; let httpsServer: https.Server | undefined;
let wsServer: ws.Server | undefined;
let wssServer: ws.Server | undefined;
const tlsOptions = { const tlsOptions = {
key: fs.readFileSync("static/certs/key.pem"), key: fs.readFileSync("static/certs/key.pem"),
@ -23,14 +29,16 @@ export const startWebServer = (): void => {
// eslint-disable-next-line @typescript-eslint/no-misused-promises // eslint-disable-next-line @typescript-eslint/no-misused-promises
httpServer = http.createServer(app); httpServer = http.createServer(app);
httpServer.listen(httpPort, () => { httpServer.listen(httpPort, () => {
startWsServer(httpServer!); wsServer = new ws.Server({ server: httpServer });
wsServer.on("connection", wsOnConnect);
logger.info("HTTP server started on port " + httpPort); logger.info("HTTP server started on port " + httpPort);
// eslint-disable-next-line @typescript-eslint/no-misused-promises // eslint-disable-next-line @typescript-eslint/no-misused-promises
httpsServer = https.createServer(tlsOptions, app); httpsServer = https.createServer(tlsOptions, app);
httpsServer.listen(httpsPort, () => { httpsServer.listen(httpsPort, () => {
startWssServer(httpsServer!); wssServer = new ws.Server({ server: httpsServer });
wssServer.on("connection", wsOnConnect);
logger.info("HTTPS server started on port " + httpsPort); logger.info("HTTPS server started on port " + httpsPort);
@ -107,6 +115,182 @@ export const stopWebServer = async (): Promise<void> => {
}) })
); );
} }
stopWsServers(promises); if (wsServer) {
promises.push(
new Promise(resolve => {
wsServer!.close(() => {
resolve();
});
})
);
}
if (wssServer) {
promises.push(
new Promise(resolve => {
wssServer!.close(() => {
resolve();
});
})
);
}
await Promise.all(promises); await Promise.all(promises);
}; };
let lastWsid: number = 0;
interface IWsCustomData extends ws {
id?: number;
accountId?: string;
}
interface IWsMsgFromClient {
auth?: {
email: string;
password: string;
isRegister: boolean;
};
logout?: boolean;
}
interface IWsMsgToClient {
//wsid?: number;
reload?: boolean;
ports?: {
http: number | undefined;
https: number | undefined;
};
config_reloaded?: boolean;
auth_succ?: {
id: string;
DisplayName: string;
Nonce: number;
};
auth_fail?: {
isRegister: boolean;
};
logged_out?: boolean;
update_inventory?: boolean;
}
const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => {
if (req.url == "/custom/selftest") {
ws.send("SpaceNinjaServer");
ws.close();
return;
}
(ws as IWsCustomData).id = ++lastWsid;
ws.send(JSON.stringify({ wsid: lastWsid }));
// eslint-disable-next-line @typescript-eslint/no-misused-promises
ws.on("message", async msg => {
const data = JSON.parse(String(msg)) as IWsMsgFromClient;
if (data.auth) {
let account: IDatabaseAccountJson | null = await Account.findOne({ email: data.auth.email });
if (account) {
if (isCorrectPassword(data.auth.password, account.password)) {
if (!account.Nonce) {
account.ClientType = "webui";
account.Nonce = createNonce();
await (account as HydratedDocument<IDatabaseAccountJson>).save();
}
} else {
account = null;
}
} else if (data.auth.isRegister) {
const name = await getUsernameFromEmail(data.auth.email);
account = await createAccount({
email: data.auth.email,
password: data.auth.password,
ClientType: "webui",
LastLogin: new Date(),
DisplayName: name,
Nonce: createNonce()
});
}
if (account) {
(ws as IWsCustomData).accountId = account.id;
ws.send(
JSON.stringify({
auth_succ: {
id: account.id,
DisplayName: account.DisplayName,
Nonce: account.Nonce
}
} satisfies IWsMsgToClient)
);
} else {
ws.send(
JSON.stringify({
auth_fail: {
isRegister: data.auth.isRegister
}
} satisfies IWsMsgToClient)
);
}
}
if (data.logout) {
const accountId = (ws as IWsCustomData).accountId;
(ws as IWsCustomData).accountId = undefined;
await Account.updateOne(
{
_id: accountId,
ClientType: "webui"
},
{
Nonce: 0
}
);
}
});
};
export const sendWsBroadcast = (data: IWsMsgToClient): void => {
const msg = JSON.stringify(data);
if (wsServer) {
for (const client of wsServer.clients) {
client.send(msg);
}
}
if (wssServer) {
for (const client of wssServer.clients) {
client.send(msg);
}
}
};
export const sendWsBroadcastTo = (accountId: string, data: IWsMsgToClient): void => {
const msg = JSON.stringify(data);
if (wsServer) {
for (const client of wsServer.clients) {
if ((client as IWsCustomData).accountId == accountId) {
client.send(msg);
}
}
}
if (wssServer) {
for (const client of wssServer.clients) {
if ((client as IWsCustomData).accountId == accountId) {
client.send(msg);
}
}
}
};
export const sendWsBroadcastExcept = (wsid: number | undefined, data: IWsMsgToClient): void => {
const msg = JSON.stringify(data);
if (wsServer) {
for (const client of wsServer.clients) {
if ((client as IWsCustomData).id != wsid) {
client.send(msg);
}
}
}
if (wssServer) {
for (const client of wssServer.clients) {
if ((client as IWsCustomData).id != wsid) {
client.send(msg);
}
}
}
};

View File

@ -1,26 +1,20 @@
import staticWorldState from "@/static/fixed_responses/worldState/worldState.json"; import staticWorldState from "@/static/fixed_responses/worldState/worldState.json";
import baro from "@/static/fixed_responses/worldState/baro.json"; import baro from "@/static/fixed_responses/worldState/baro.json";
import varzia from "@/static/fixed_responses/worldState/varzia.json";
import fissureMissions from "@/static/fixed_responses/worldState/fissureMissions.json"; import fissureMissions from "@/static/fixed_responses/worldState/fissureMissions.json";
import sortieTilesets from "@/static/fixed_responses/worldState/sortieTilesets.json"; import sortieTilesets from "@/static/fixed_responses/worldState/sortieTilesets.json";
import sortieTilesetMissions from "@/static/fixed_responses/worldState/sortieTilesetMissions.json"; import sortieTilesetMissions from "@/static/fixed_responses/worldState/sortieTilesetMissions.json";
import syndicateMissions from "@/static/fixed_responses/worldState/syndicateMissions.json"; import syndicateMissions from "@/static/fixed_responses/worldState/syndicateMissions.json";
import darvoDeals from "@/static/fixed_responses/worldState/darvoDeals.json"; import darvoDeals from "@/static/fixed_responses/worldState/darvoDeals.json";
import invasionNodes from "@/static/fixed_responses/worldState/invasionNodes.json";
import invasionRewards from "@/static/fixed_responses/worldState/invasionRewards.json";
import { buildConfig } from "@/src/services/buildConfigService"; import { buildConfig } from "@/src/services/buildConfigService";
import { unixTimesInMs } from "@/src/constants/timeConstants"; import { unixTimesInMs } from "@/src/constants/timeConstants";
import { config } from "@/src/services/configService"; import { config } from "@/src/services/configService";
import { getRandomElement, getRandomInt, sequentiallyUniqueRandomElement, SRng } from "@/src/services/rngService"; import { getRandomElement, getRandomInt, SRng } from "@/src/services/rngService";
import { eMissionType, ExportRegions, ExportSyndicates, IMissionReward, IRegion } from "warframe-public-export-plus"; import { eMissionType, ExportRegions, ExportSyndicates, IRegion } from "warframe-public-export-plus";
import { import {
ICalendarDay, ICalendarDay,
ICalendarEvent, ICalendarEvent,
ICalendarSeason, ICalendarSeason,
IInvasion,
ILiteSortie, ILiteSortie,
IPrimeVaultTrader,
IPrimeVaultTraderOffer,
ISeasonChallenge, ISeasonChallenge,
ISortie, ISortie,
ISortieMission, ISortieMission,
@ -31,10 +25,10 @@ import {
IVoidTraderOffer, IVoidTraderOffer,
IWorldState, IWorldState,
TCircuitGameMode TCircuitGameMode
} from "@/src/types/worldStateTypes"; } from "../types/worldStateTypes";
import { toMongoDate, toOid, version_compare } from "@/src/helpers/inventoryHelpers"; import { toMongoDate, toOid, version_compare } from "../helpers/inventoryHelpers";
import { logger } from "@/src/utils/logger"; import { logger } from "../utils/logger";
import { DailyDeal, Fissure } from "@/src/models/worldStateModel"; import { DailyDeal, Fissure } from "../models/worldStateModel";
const sortieBosses = [ const sortieBosses = [
"SORTIE_BOSS_HYENA", "SORTIE_BOSS_HYENA",
@ -388,35 +382,44 @@ const getSeasonChallengePools = (syndicateTag: string): IRotatingSeasonChallenge
const getSeasonDailyChallenge = (pools: IRotatingSeasonChallengePools, day: number): ISeasonChallenge => { const getSeasonDailyChallenge = (pools: IRotatingSeasonChallengePools, day: number): ISeasonChallenge => {
const dayStart = EPOCH + day * 86400000; const dayStart = EPOCH + day * 86400000;
const dayEnd = EPOCH + (day + 3) * 86400000; const dayEnd = EPOCH + (day + 3) * 86400000;
const rng = new SRng(new SRng(day).randomInt(0, 100_000));
return { return {
_id: { $oid: "67e1b5ca9d00cb47" + day.toString().padStart(8, "0") }, _id: { $oid: "67e1b5ca9d00cb47" + day.toString().padStart(8, "0") },
Daily: true, Daily: true,
Activation: { $date: { $numberLong: dayStart.toString() } }, Activation: { $date: { $numberLong: dayStart.toString() } },
Expiry: { $date: { $numberLong: dayEnd.toString() } }, Expiry: { $date: { $numberLong: dayEnd.toString() } },
Challenge: sequentiallyUniqueRandomElement(pools.daily, day, 2, 605732938)! Challenge: rng.randomElement(pools.daily)!
}; };
}; };
const pushSeasonWeeklyChallenge = ( const getSeasonWeeklyChallenge = (pools: IRotatingSeasonChallengePools, week: number, id: number): ISeasonChallenge => {
activeChallenges: ISeasonChallenge[],
pool: string[],
week: number,
id: number
): void => {
const weekStart = EPOCH + week * 604800000; const weekStart = EPOCH + week * 604800000;
const weekEnd = weekStart + 604800000; const weekEnd = weekStart + 604800000;
const challengeId = week * 7 + id; const challengeId = week * 7 + id;
const rng = new SRng(new SRng(challengeId).randomInt(0, 100_000)); const rng = new SRng(new SRng(challengeId).randomInt(0, 100_000));
let challenge: string; return {
do {
challenge = rng.randomElement(pool)!;
} while (activeChallenges.some(x => x.Challenge == challenge));
activeChallenges.push({
_id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") }, _id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
Activation: { $date: { $numberLong: weekStart.toString() } }, Activation: { $date: { $numberLong: weekStart.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } }, Expiry: { $date: { $numberLong: weekEnd.toString() } },
Challenge: challenge Challenge: rng.randomElement(pools.weekly)!
}); };
};
const getSeasonWeeklyHardChallenge = (
pools: IRotatingSeasonChallengePools,
week: number,
id: number
): ISeasonChallenge => {
const weekStart = EPOCH + week * 604800000;
const weekEnd = weekStart + 604800000;
const challengeId = week * 7 + id;
const rng = new SRng(new SRng(challengeId).randomInt(0, 100_000));
return {
_id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
Activation: { $date: { $numberLong: weekStart.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } },
Challenge: rng.randomElement(pools.hardWeekly)!
};
}; };
const pushWeeklyActs = ( const pushWeeklyActs = (
@ -427,8 +430,8 @@ const pushWeeklyActs = (
const weekStart = EPOCH + week * 604800000; const weekStart = EPOCH + week * 604800000;
const weekEnd = weekStart + 604800000; const weekEnd = weekStart + 604800000;
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 0); activeChallenges.push(getSeasonWeeklyChallenge(pools, week, 0));
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 1); activeChallenges.push(getSeasonWeeklyChallenge(pools, week, 1));
if (pools.hasWeeklyPermanent) { if (pools.hasWeeklyPermanent) {
activeChallenges.push({ activeChallenges.push({
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 0).toString().padStart(8, "0") }, _id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 0).toString().padStart(8, "0") },
@ -448,14 +451,14 @@ const pushWeeklyActs = (
Expiry: { $date: { $numberLong: weekEnd.toString() } }, Expiry: { $date: { $numberLong: weekEnd.toString() } },
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEnemies" Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEnemies"
}); });
pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, week, 2); activeChallenges.push(getSeasonWeeklyHardChallenge(pools, week, 2));
pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, week, 3); activeChallenges.push(getSeasonWeeklyHardChallenge(pools, week, 3));
} else { } else {
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 2); activeChallenges.push(getSeasonWeeklyChallenge(pools, week, 2));
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 3); activeChallenges.push(getSeasonWeeklyChallenge(pools, week, 3));
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 4); activeChallenges.push(getSeasonWeeklyChallenge(pools, week, 4));
pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, week, 5); activeChallenges.push(getSeasonWeeklyHardChallenge(pools, week, 5));
pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, week, 6); activeChallenges.push(getSeasonWeeklyHardChallenge(pools, week, 6));
} }
}; };
@ -974,26 +977,25 @@ const getCalendarSeason = (week: number): ICalendarSeason => {
// Not very faithful, but to avoid the same node coming up back-to-back (which is not valid), I've split these into 2 arrays which we're alternating between. // Not very faithful, but to avoid the same node coming up back-to-back (which is not valid), I've split these into 2 arrays which we're alternating between.
const voidStormMissions = { const voidStormMissionsA = {
VoidT1: [ VoidT1: ["CrewBattleNode519", "CrewBattleNode518", "CrewBattleNode515", "CrewBattleNode503"],
"CrewBattleNode519", VoidT2: ["CrewBattleNode501", "CrewBattleNode534", "CrewBattleNode530"],
"CrewBattleNode518", VoidT3: ["CrewBattleNode521", "CrewBattleNode516"],
"CrewBattleNode515",
"CrewBattleNode503",
"CrewBattleNode509",
"CrewBattleNode522",
"CrewBattleNode511",
"CrewBattleNode512"
],
VoidT2: ["CrewBattleNode501", "CrewBattleNode534", "CrewBattleNode530", "CrewBattleNode535", "CrewBattleNode533"],
VoidT3: ["CrewBattleNode521", "CrewBattleNode516", "CrewBattleNode524", "CrewBattleNode525"],
VoidT4: [ VoidT4: [
"CrewBattleNode555", "CrewBattleNode555",
"CrewBattleNode553", "CrewBattleNode553",
"CrewBattleNode554", "CrewBattleNode554",
"CrewBattleNode539", "CrewBattleNode539",
"CrewBattleNode531", "CrewBattleNode531",
"CrewBattleNode527", "CrewBattleNode527"
]
};
const voidStormMissionsB = {
VoidT1: ["CrewBattleNode509", "CrewBattleNode522", "CrewBattleNode511", "CrewBattleNode512"],
VoidT2: ["CrewBattleNode535", "CrewBattleNode533"],
VoidT3: ["CrewBattleNode524", "CrewBattleNode525"],
VoidT4: [
"CrewBattleNode542", "CrewBattleNode542",
"CrewBattleNode538", "CrewBattleNode538",
"CrewBattleNode543", "CrewBattleNode543",
@ -1001,21 +1003,18 @@ const voidStormMissions = {
"CrewBattleNode550", "CrewBattleNode550",
"CrewBattleNode529" "CrewBattleNode529"
] ]
} as const; };
const voidStormLookbehind = {
VoidT1: 3,
VoidT2: 1,
VoidT3: 1,
VoidT4: 3
} as const;
const pushVoidStorms = (arr: IVoidStorm[], hour: number): void => { const pushVoidStorms = (arr: IVoidStorm[], hour: number): void => {
const activation = hour * unixTimesInMs.hour + 40 * unixTimesInMs.minute; const activation = hour * unixTimesInMs.hour + 40 * unixTimesInMs.minute;
const expiry = activation + 90 * unixTimesInMs.minute; const expiry = activation + 90 * unixTimesInMs.minute;
let accum = 0; let accum = 0;
const tierIdx = { VoidT1: hour * 2, VoidT2: hour, VoidT3: hour, VoidT4: hour * 2 }; const rng = new SRng(new SRng(hour).randomInt(0, 100_000));
const voidStormMissions = structuredClone(hour & 1 ? voidStormMissionsA : voidStormMissionsB);
for (const tier of ["VoidT1", "VoidT1", "VoidT2", "VoidT3", "VoidT4", "VoidT4"] as const) { for (const tier of ["VoidT1", "VoidT1", "VoidT2", "VoidT3", "VoidT4", "VoidT4"] as const) {
const idx = rng.randomInt(0, voidStormMissions[tier].length - 1);
const node = voidStormMissions[tier][idx];
voidStormMissions[tier].splice(idx, 1);
arr.push({ arr.push({
_id: { _id: {
$oid: $oid:
@ -1023,12 +1022,7 @@ const pushVoidStorms = (arr: IVoidStorm[], hour: number): void => {
"0321e89b" + "0321e89b" +
(accum++).toString().padStart(8, "0") (accum++).toString().padStart(8, "0")
}, },
Node: sequentiallyUniqueRandomElement( Node: node,
voidStormMissions[tier],
tierIdx[tier]++,
voidStormLookbehind[tier],
2051969264
)!,
Activation: { $date: { $numberLong: activation.toString() } }, Activation: { $date: { $numberLong: activation.toString() } },
Expiry: { $date: { $numberLong: expiry.toString() } }, Expiry: { $date: { $numberLong: expiry.toString() } },
ActiveMissionTier: tier ActiveMissionTier: tier
@ -1036,280 +1030,53 @@ const pushVoidStorms = (arr: IVoidStorm[], hour: number): void => {
} }
}; };
interface ITimeConstraint { const doesTimeSatsifyConstraints = (timeSecs: number): boolean => {
//name: string; if (config.worldState?.eidolonOverride) {
isValidTime: (timeSecs: number) => boolean;
getIdealTimeBefore: (timeSecs: number) => number;
}
const eidolonDayConstraint: ITimeConstraint = {
//name: "eidolon day",
isValidTime: (timeSecs: number): boolean => {
const eidolonEpoch = 1391992660; const eidolonEpoch = 1391992660;
const eidolonCycle = Math.trunc((timeSecs - eidolonEpoch) / 9000); const eidolonCycle = Math.trunc((timeSecs - eidolonEpoch) / 9000);
const eidolonCycleStart = eidolonEpoch + eidolonCycle * 9000; const eidolonCycleStart = eidolonEpoch + eidolonCycle * 9000;
const eidolonCycleEnd = eidolonCycleStart + 9000; const eidolonCycleEnd = eidolonCycleStart + 9000;
const eidolonCycleNightStart = eidolonCycleEnd - 3000; const eidolonCycleNightStart = eidolonCycleEnd - 3000;
return !isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, eidolonCycleNightStart * 1000); if (config.worldState.eidolonOverride == "day") {
}, if (
getIdealTimeBefore: (timeSecs: number): number => { //timeSecs < eidolonCycleStart ||
const eidolonEpoch = 1391992660; isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, eidolonCycleNightStart * 1000)
const eidolonCycle = Math.trunc((timeSecs - eidolonEpoch) / 9000); ) {
const eidolonCycleStart = eidolonEpoch + eidolonCycle * 9000; return false;
return eidolonCycleStart; }
} } else {
}; if (
timeSecs < eidolonCycleNightStart ||
const eidolonNightConstraint: ITimeConstraint = { isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, eidolonCycleEnd * 1000)
//name: "eidolon night", ) {
isValidTime: (timeSecs: number): boolean => { return false;
const eidolonEpoch = 1391992660; }
const eidolonCycle = Math.trunc((timeSecs - eidolonEpoch) / 9000);
const eidolonCycleStart = eidolonEpoch + eidolonCycle * 9000;
const eidolonCycleEnd = eidolonCycleStart + 9000;
const eidolonCycleNightStart = eidolonCycleEnd - 3000;
return (
timeSecs >= eidolonCycleNightStart &&
!isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, eidolonCycleEnd * 1000)
);
},
getIdealTimeBefore: (timeSecs: number): number => {
const eidolonEpoch = 1391992660;
const eidolonCycle = Math.trunc((timeSecs - eidolonEpoch) / 9000);
const eidolonCycleStart = eidolonEpoch + eidolonCycle * 9000;
const eidolonCycleEnd = eidolonCycleStart + 9000;
const eidolonCycleNightStart = eidolonCycleEnd - 3000;
if (eidolonCycleNightStart > timeSecs) {
// Night hasn't started yet, but we need to return a time in the past.
return eidolonCycleNightStart - 9000;
} }
return eidolonCycleNightStart;
} }
};
const venusColdConstraint: ITimeConstraint = { if (config.worldState?.vallisOverride) {
//name: "venus cold",
isValidTime: (timeSecs: number): boolean => {
const vallisEpoch = 1541837628; const vallisEpoch = 1541837628;
const vallisCycle = Math.trunc((timeSecs - vallisEpoch) / 1600); const vallisCycle = Math.trunc((timeSecs - vallisEpoch) / 1600);
const vallisCycleStart = vallisEpoch + vallisCycle * 1600; const vallisCycleStart = vallisEpoch + vallisCycle * 1600;
const vallisCycleEnd = vallisCycleStart + 1600; const vallisCycleEnd = vallisCycleStart + 1600;
const vallisCycleColdStart = vallisCycleStart + 400; const vallisCycleColdStart = vallisCycleStart + 400;
return ( if (config.worldState.vallisOverride == "cold") {
timeSecs >= vallisCycleColdStart && if (
!isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, vallisCycleEnd * 1000) timeSecs < vallisCycleColdStart ||
); isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, vallisCycleEnd * 1000)
}, ) {
getIdealTimeBefore: (timeSecs: number): number => { return false;
const vallisEpoch = 1541837628; }
const vallisCycle = Math.trunc((timeSecs - vallisEpoch) / 1600); } else {
const vallisCycleStart = vallisEpoch + vallisCycle * 1600; if (
const vallisCycleColdStart = vallisCycleStart + 400; //timeSecs < vallisCycleStart ||
if (vallisCycleColdStart > timeSecs) { isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, vallisCycleColdStart * 1000)
// Cold hasn't started yet, but we need to return a time in the past. ) {
return vallisCycleColdStart - 1600; return false;
}
return vallisCycleColdStart;
}
};
const venusWarmConstraint: ITimeConstraint = {
//name: "venus warm",
isValidTime: (timeSecs: number): boolean => {
const vallisEpoch = 1541837628;
const vallisCycle = Math.trunc((timeSecs - vallisEpoch) / 1600);
const vallisCycleStart = vallisEpoch + vallisCycle * 1600;
const vallisCycleColdStart = vallisCycleStart + 400;
return !isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, vallisCycleColdStart * 1000);
},
getIdealTimeBefore: (timeSecs: number): number => {
const vallisEpoch = 1541837628;
const vallisCycle = Math.trunc((timeSecs - vallisEpoch) / 1600);
const vallisCycleStart = vallisEpoch + vallisCycle * 1600;
return vallisCycleStart;
}
};
const getIdealTimeSatsifyingConstraints = (constraints: ITimeConstraint[]): number => {
let timeSecs = Math.trunc(Date.now() / 1000);
let allGood;
do {
allGood = true;
for (const constraint of constraints) {
if (!constraint.isValidTime(timeSecs)) {
//logger.debug(`${constraint.name} is not happy with ${timeSecs}`);
const prevTimeSecs = timeSecs;
const suggestion = constraint.getIdealTimeBefore(timeSecs);
timeSecs = suggestion;
do {
timeSecs += 60;
if (timeSecs >= prevTimeSecs || !constraint.isValidTime(timeSecs)) {
timeSecs = suggestion; // Can't find a compromise; just take the suggestion and try to compromise on another constraint.
break;
}
} while (!constraints.every(constraint => constraint.isValidTime(timeSecs)));
allGood = false;
break;
} }
} }
} while (!allGood);
return timeSecs;
};
const getVarziaRotation = (week: number): string => {
const seed = new SRng(week).randomInt(0, 100_000);
const rng = new SRng(seed);
return rng.randomElement(varzia.primeDualPacks)!.ItemType;
};
const getVarziaManifest = (dualPack: string): IPrimeVaultTraderOffer[] => {
const rotrationManifest = varzia.primeDualPacks.find(pack => pack.ItemType === dualPack);
if (!rotrationManifest) return [];
const mainPack = [{ ItemType: rotrationManifest.ItemType, PrimePrice: 10 }];
const singlePacks: IPrimeVaultTraderOffer[] = [];
const items: IPrimeVaultTraderOffer[] = [];
const bobbleHeads: IPrimeVaultTraderOffer[] = [];
for (const singlePackType of rotrationManifest.SinglePacks) {
singlePacks.push({ ItemType: singlePackType, PrimePrice: 6 });
const sp = varzia.primeSinglePacks.find(pack => pack.ItemType === singlePackType);
if (sp) {
items.push(...sp.Items);
sp.BobbleHeads.forEach(bobbleHead => {
bobbleHeads.push({ ItemType: bobbleHead, PrimePrice: 1 });
});
}
} }
const relics = rotrationManifest.Relics.map(relic => ({ ItemType: relic, RegularPrice: 1 }));
return [singlePacks[0], ...mainPack, singlePacks[1], ...items, ...bobbleHeads, ...relics];
};
const getAllVarziaManifests = (): IPrimeVaultTraderOffer[] => {
const dualPacks: IPrimeVaultTraderOffer[] = [];
const singlePacks: IPrimeVaultTraderOffer[] = [];
const items: IPrimeVaultTraderOffer[] = [];
const bobbleHeads: IPrimeVaultTraderOffer[] = [];
const relics: IPrimeVaultTraderOffer[] = [];
const singlePackSet = new Set<string>();
const itemsSet = new Set<string>();
const bobbleHeadsSet = new Set<string>();
varzia.primeDualPacks.forEach(dualPack => {
dualPacks.push({ ItemType: dualPack.ItemType, PrimePrice: 10 });
dualPack.SinglePacks.forEach(singlePackType => {
if (!singlePackSet.has(singlePackType)) {
singlePackSet.add(singlePackType);
singlePacks.push({ ItemType: singlePackType, PrimePrice: 6 });
}
const sp = varzia.primeSinglePacks.find(pack => pack.ItemType === singlePackType)!;
sp.Items.forEach(item => {
if (!itemsSet.has(item.ItemType)) {
itemsSet.add(item.ItemType);
items.push(item);
}
});
sp.BobbleHeads.forEach(bobbleHead => {
if (!bobbleHeadsSet.has(bobbleHead)) {
bobbleHeadsSet.add(bobbleHead);
bobbleHeads.push({ ItemType: bobbleHead, PrimePrice: 1 });
}
});
});
relics.push(...dualPack.Relics.map(relic => ({ ItemType: relic, RegularPrice: 1 })));
});
return [...dualPacks, ...singlePacks, ...items, ...bobbleHeads, ...relics];
};
const createInvasion = (day: number, idx: number): IInvasion => {
const id = day * 3 + idx;
const defender = (["FC_GRINEER", "FC_CORPUS", day % 2 ? "FC_GRINEER" : "FC_CORPUS"] as const)[idx];
const rng = new SRng(new SRng(id).randomInt(0, 1_000_000));
const isInfestationOutbreak = rng.randomInt(0, 1) == 0;
const attacker = isInfestationOutbreak ? "FC_INFESTATION" : defender == "FC_GRINEER" ? "FC_CORPUS" : "FC_GRINEER";
const startMs = EPOCH + day * 86400_000;
const oid =
((startMs / 1000) & 0xffffffff).toString(16).padStart(8, "0") +
"fd148cb8" +
(idx & 0xffffffff).toString(16).padStart(8, "0");
const node = sequentiallyUniqueRandomElement(invasionNodes[defender], id, 5, 690175)!; // Can't repeat the other 2 on this day nor the last 3
const progress = (Date.now() - startMs) / 86400_000;
const countMultiplier = isInfestationOutbreak || rng.randomInt(0, 1) ? -1 : 1; // if defender is winning, count is negative
const fiftyPercent = rng.randomInt(1000, 29000); // introduce some 'yitter' for the percentages
const rewardFloat = rng.randomFloat();
const rewardTier = rewardFloat < 0.201 ? "RARE" : rewardFloat < 0.7788 ? "COMMON" : "UNCOMMON";
const attackerReward: IMissionReward = {};
const defenderReward: IMissionReward = {};
if (isInfestationOutbreak) {
defenderReward.countedItems = [
rng.randomElement(invasionRewards[rng.randomInt(0, 1) ? "FC_INFESTATION" : defender][rewardTier])!
];
} else {
attackerReward.countedItems = [rng.randomElement(invasionRewards[attacker][rewardTier])!];
defenderReward.countedItems = [rng.randomElement(invasionRewards[defender][rewardTier])!];
}
return {
_id: { $oid: oid },
Faction: attacker,
DefenderFaction: defender,
Node: node,
Count: Math.round(
(progress < 0.5 ? progress * 2 * fiftyPercent : fiftyPercent + (30_000 - fiftyPercent) * (progress - 0.5)) *
countMultiplier
),
Goal: 30000, // Value seems to range from 30000 to 98000 in intervals of 1000. Higher values are increasingly rare. I don't think this is relevant for the frontend besides dividing count by it.
LocTag: isInfestationOutbreak
? ExportRegions[node].missionIndex == 0
? "/Lotus/Language/Menu/InfestedInvasionBoss"
: "/Lotus/Language/Menu/InfestedInvasionGeneric"
: attacker == "FC_CORPUS"
? "/Lotus/Language/Menu/CorpusInvasionGeneric"
: "/Lotus/Language/Menu/GrineerInvasionGeneric",
Completed: startMs + 86400_000 < Date.now(), // Sorta unfaithful. Invasions on live are (at least in part) in fluenced by people completing them. And otherwise also probably not hardcoded to last 24 hours.
ChainID: { $oid: oid },
AttackerReward: attackerReward,
AttackerMissionInfo: {
seed: rng.randomInt(0, 1_000_000),
faction: defender
},
DefenderReward: defenderReward,
DefenderMissionInfo: {
seed: rng.randomInt(0, 1_000_000),
faction: attacker
},
Activation: {
$date: {
$numberLong: startMs.toString()
}
}
};
};
export const getInvasionByOid = (oid: string): IInvasion | undefined => {
const arr = oid.split("fd148cb8");
if (arr.length == 2 && arr[0].length == 8 && arr[1].length == 8) {
return createInvasion(idToDay(oid), parseInt(arr[1], 16));
}
return undefined;
};
export const getWorldState = (buildLabel?: string): IWorldState => {
const constraints: ITimeConstraint[] = [];
if (config.worldState?.eidolonOverride) {
constraints.push(config.worldState.eidolonOverride == "day" ? eidolonDayConstraint : eidolonNightConstraint);
}
if (config.worldState?.vallisOverride) {
constraints.push(config.worldState.vallisOverride == "cold" ? venusColdConstraint : venusWarmConstraint);
}
if (config.worldState?.duviriOverride) { if (config.worldState?.duviriOverride) {
const duviriMoods = ["sorrow", "fear", "joy", "anger", "envy"]; const duviriMoods = ["sorrow", "fear", "joy", "anger", "envy"];
const desiredMood = duviriMoods.indexOf(config.worldState.duviriOverride); const desiredMood = duviriMoods.indexOf(config.worldState.duviriOverride);
@ -1319,22 +1086,26 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
valid_values: duviriMoods valid_values: duviriMoods
}); });
} else { } else {
constraints.push({ const moodIndex = Math.trunc(timeSecs / 7200);
//name: `duviri ${config.worldState.duviriOverride}`, const moodStart = moodIndex * 7200;
isValidTime: (timeSecs: number): boolean => { const moodEnd = moodStart + 7200;
const moodIndex = Math.trunc(timeSecs / 7200); if (
return moodIndex % 5 == desiredMood; moodIndex % 5 != desiredMood ||
}, isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, moodEnd * 1000)
getIdealTimeBefore: (timeSecs: number): number => { ) {
let moodIndex = Math.trunc(timeSecs / 7200); return false;
moodIndex -= ((moodIndex % 5) - desiredMood + 5) % 5; // while (moodIndex % 5 != desiredMood) --moodIndex; }
const moodStart = moodIndex * 7200;
return moodStart;
}
});
} }
} }
const timeSecs = getIdealTimeSatsifyingConstraints(constraints);
return true;
};
export const getWorldState = (buildLabel?: string): IWorldState => {
let timeSecs = Math.round(Date.now() / 1000);
while (!doesTimeSatsifyConstraints(timeSecs)) {
timeSecs -= 60;
}
const timeMs = timeSecs * 1000; const timeMs = timeSecs * 1000;
const day = Math.trunc((timeMs - EPOCH) / 86400000); const day = Math.trunc((timeMs - EPOCH) / 86400000);
const week = Math.trunc(day / 7); const week = Math.trunc(day / 7);
@ -1350,9 +1121,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
LiteSorties: [], LiteSorties: [],
ActiveMissions: [], ActiveMissions: [],
GlobalUpgrades: [], GlobalUpgrades: [],
Invasions: [],
VoidTraders: [], VoidTraders: [],
PrimeVaultTraders: [],
VoidStorms: [], VoidStorms: [],
DailyDeals: [], DailyDeals: [],
EndlessXpChoices: [], EndlessXpChoices: [],
@ -1553,20 +1322,6 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
}); });
} }
// Rough outline of dynamic invasions.
// TODO: Invasions chains, e.g. an infestation mission would soon lead to other nodes on that planet also having an infestation invasion.
// TODO: Grineer/Corpus to fund their death stars with each invasion win.
{
worldState.Invasions.push(createInvasion(day, 0));
worldState.Invasions.push(createInvasion(day, 1));
worldState.Invasions.push(createInvasion(day, 2));
// Completed invasions stay for up to 24 hours as the winner 'occupies' that node
worldState.Invasions.push(createInvasion(day - 1, 0));
worldState.Invasions.push(createInvasion(day - 1, 1));
worldState.Invasions.push(createInvasion(day - 1, 2));
}
// Baro // Baro
{ {
const baroIndex = Math.trunc((Date.now() - 910800000) / (unixTimesInMs.day * 14)); const baroIndex = Math.trunc((Date.now() - 910800000) / (unixTimesInMs.day * 14));
@ -1638,31 +1393,6 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
} }
} }
// Varzia
{
const pt: IPrimeVaultTrader = {
_id: { $oid: ((weekStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "c36af423770eaa97" },
Activation: { $date: { $numberLong: weekStart.toString() } },
InitialStartDate: { $date: { $numberLong: "1662738144266" } },
Node: "TradeHUB1",
Manifest: [],
Expiry: { $date: { $numberLong: weekEnd.toString() } },
EvergreenManifest: varzia.evergreen,
ScheduleInfo: []
};
worldState.PrimeVaultTraders.push(pt);
const rotation = config.worldState?.varziaOverride || getVarziaRotation(week);
pt.Manifest = config.worldState?.varziaFullyStocked ? getAllVarziaManifests() : getVarziaManifest(rotation);
if (config.worldState?.varziaOverride || config.worldState?.varziaFullyStocked) {
pt.Expiry = { $date: { $numberLong: "2000000000000" } };
} else {
pt.ScheduleInfo.push({
Expiry: { $date: { $numberLong: (weekEnd + unixTimesInMs.week).toString() } },
FeaturedItem: getVarziaRotation(week + 1)
});
}
}
// Sortie & syndicate missions cycling every day (at 16:00 or 17:00 UTC depending on if London, OT is observing DST) // Sortie & syndicate missions cycling every day (at 16:00 or 17:00 UTC depending on if London, OT is observing DST)
{ {
const rollover = getSortieTime(day); const rollover = getSortieTime(day);

View File

@ -1,200 +0,0 @@
import http from "http";
import https from "https";
import ws from "ws";
import { Account } from "@/src/models/loginModel";
import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "@/src/services/loginService";
import { IDatabaseAccountJson } from "@/src/types/loginTypes";
import { HydratedDocument } from "mongoose";
let wsServer: ws.Server | undefined;
let wssServer: ws.Server | undefined;
export const startWsServer = (httpServer: http.Server): void => {
wsServer = new ws.Server({ server: httpServer });
wsServer.on("connection", wsOnConnect);
};
export const startWssServer = (httpsServer: https.Server): void => {
wssServer = new ws.Server({ server: httpsServer });
wssServer.on("connection", wsOnConnect);
};
export const stopWsServers = (promises: Promise<void>[]): void => {
if (wsServer) {
promises.push(
new Promise(resolve => {
wsServer!.close(() => {
resolve();
});
})
);
}
if (wssServer) {
promises.push(
new Promise(resolve => {
wssServer!.close(() => {
resolve();
});
})
);
}
};
let lastWsid: number = 0;
interface IWsCustomData extends ws {
id?: number;
accountId?: string;
}
interface IWsMsgFromClient {
auth?: {
email: string;
password: string;
isRegister: boolean;
};
logout?: boolean;
}
interface IWsMsgToClient {
//wsid?: number;
reload?: boolean;
ports?: {
http: number | undefined;
https: number | undefined;
};
config_reloaded?: boolean;
auth_succ?: {
id: string;
DisplayName: string;
Nonce: number;
};
auth_fail?: {
isRegister: boolean;
};
logged_out?: boolean;
update_inventory?: boolean;
}
const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => {
if (req.url == "/custom/selftest") {
ws.send("SpaceNinjaServer");
ws.close();
return;
}
(ws as IWsCustomData).id = ++lastWsid;
ws.send(JSON.stringify({ wsid: lastWsid }));
// eslint-disable-next-line @typescript-eslint/no-misused-promises
ws.on("message", async msg => {
const data = JSON.parse(String(msg)) as IWsMsgFromClient;
if (data.auth) {
let account: IDatabaseAccountJson | null = await Account.findOne({ email: data.auth.email });
if (account) {
if (isCorrectPassword(data.auth.password, account.password)) {
if (!account.Nonce) {
account.ClientType = "webui";
account.Nonce = createNonce();
await (account as HydratedDocument<IDatabaseAccountJson>).save();
}
} else {
account = null;
}
} else if (data.auth.isRegister) {
const name = await getUsernameFromEmail(data.auth.email);
account = await createAccount({
email: data.auth.email,
password: data.auth.password,
ClientType: "webui",
LastLogin: new Date(),
DisplayName: name,
Nonce: createNonce()
});
}
if (account) {
(ws as IWsCustomData).accountId = account.id;
ws.send(
JSON.stringify({
auth_succ: {
id: account.id,
DisplayName: account.DisplayName,
Nonce: account.Nonce
}
} satisfies IWsMsgToClient)
);
} else {
ws.send(
JSON.stringify({
auth_fail: {
isRegister: data.auth.isRegister
}
} satisfies IWsMsgToClient)
);
}
}
if (data.logout) {
const accountId = (ws as IWsCustomData).accountId;
(ws as IWsCustomData).accountId = undefined;
await Account.updateOne(
{
_id: accountId,
ClientType: "webui"
},
{
Nonce: 0
}
);
}
});
};
export const sendWsBroadcast = (data: IWsMsgToClient): void => {
const msg = JSON.stringify(data);
if (wsServer) {
for (const client of wsServer.clients) {
client.send(msg);
}
}
if (wssServer) {
for (const client of wssServer.clients) {
client.send(msg);
}
}
};
export const sendWsBroadcastTo = (accountId: string, data: IWsMsgToClient): void => {
const msg = JSON.stringify(data);
if (wsServer) {
for (const client of wsServer.clients) {
if ((client as IWsCustomData).accountId == accountId) {
client.send(msg);
}
}
}
if (wssServer) {
for (const client of wssServer.clients) {
if ((client as IWsCustomData).accountId == accountId) {
client.send(msg);
}
}
}
};
export const sendWsBroadcastExcept = (wsid: number | undefined, data: IWsMsgToClient): void => {
const msg = JSON.stringify(data);
if (wsServer) {
for (const client of wsServer.clients) {
if ((client as IWsCustomData).id != wsid) {
client.send(msg);
}
}
}
if (wssServer) {
for (const client of wssServer.clients) {
if ((client as IWsCustomData).id != wsid) {
client.send(msg);
}
}
}
};

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