chore(webui): update to Spanish translation #1772
@ -4,7 +4,7 @@
 | 
			
		||||
  "description": "WF Emulator",
 | 
			
		||||
  "main": "index.ts",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "start": "node --import ./build/src/pathman.js build/src/index.js",
 | 
			
		||||
    "start": "node --enable-source-maps --import ./build/src/pathman.js build/src/index.js",
 | 
			
		||||
    "dev": "ts-node-dev --openssl-legacy-provider -r tsconfig-paths/register src/index.ts ",
 | 
			
		||||
    "build": "tsc --incremental --sourceMap && ncp static/webui build/static/webui",
 | 
			
		||||
    "verify": "tsgo --noEmit",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										30
									
								
								src/controllers/api/addIgnoredUserController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/controllers/api/addIgnoredUserController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
			
		||||
import { toOid } from "@/src/helpers/inventoryHelpers";
 | 
			
		||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
			
		||||
import { Account, Ignore } from "@/src/models/loginModel";
 | 
			
		||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
			
		||||
import { IFriendInfo } from "@/src/types/guildTypes";
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
 | 
			
		||||
export const addIgnoredUserController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const data = getJSONfromString<IAddIgnoredUserRequest>(String(req.body));
 | 
			
		||||
    const ignoreeAccount = await Account.findOne(
 | 
			
		||||
        { DisplayName: data.playerName.substring(0, data.playerName.length - 1) },
 | 
			
		||||
        "_id"
 | 
			
		||||
    );
 | 
			
		||||
    if (ignoreeAccount) {
 | 
			
		||||
        await Ignore.create({ ignorer: accountId, ignoree: ignoreeAccount._id });
 | 
			
		||||
        res.json({
 | 
			
		||||
            Ignored: {
 | 
			
		||||
                _id: toOid(ignoreeAccount._id),
 | 
			
		||||
                DisplayName: data.playerName
 | 
			
		||||
            } satisfies IFriendInfo
 | 
			
		||||
        });
 | 
			
		||||
    } else {
 | 
			
		||||
        res.status(400).end();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface IAddIgnoredUserRequest {
 | 
			
		||||
    playerName: string;
 | 
			
		||||
}
 | 
			
		||||
@ -18,6 +18,7 @@ import {
 | 
			
		||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
			
		||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
			
		||||
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
			
		||||
import { toOid } from "@/src/helpers/inventoryHelpers";
 | 
			
		||||
 | 
			
		||||
interface IClaimCompletedRecipeRequest {
 | 
			
		||||
    RecipeIds: IOid[];
 | 
			
		||||
@ -80,6 +81,7 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
 | 
			
		||||
    } else {
 | 
			
		||||
        logger.debug("Claiming Recipe", { recipe, pendingRecipe });
 | 
			
		||||
 | 
			
		||||
        let BrandedSuits: undefined | IOid[];
 | 
			
		||||
        if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
 | 
			
		||||
            inventory.PendingSpectreLoadouts ??= [];
 | 
			
		||||
            inventory.SpectreLoadouts ??= [];
 | 
			
		||||
@ -104,9 +106,10 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
 | 
			
		||||
                inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)),
 | 
			
		||||
                1
 | 
			
		||||
            );
 | 
			
		||||
            BrandedSuits = [toOid(pendingRecipe.SuitToUnbrand!)];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let InventoryChanges = {};
 | 
			
		||||
        let InventoryChanges: IInventoryChanges = {};
 | 
			
		||||
        if (recipe.consumeOnUse) {
 | 
			
		||||
            addRecipes(inventory, [
 | 
			
		||||
                {
 | 
			
		||||
@ -134,6 +137,6 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
        await inventory.save();
 | 
			
		||||
        res.json({ InventoryChanges });
 | 
			
		||||
        res.json({ InventoryChanges, BrandedSuits });
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										28
									
								
								src/controllers/api/crewMembersController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/controllers/api/crewMembersController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,28 @@
 | 
			
		||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
			
		||||
import { getInventory } from "@/src/services/inventoryService";
 | 
			
		||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
			
		||||
import { ICrewMemberClient } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
import { Types } from "mongoose";
 | 
			
		||||
 | 
			
		||||
export const crewMembersController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const inventory = await getInventory(accountId, "CrewMembers");
 | 
			
		||||
    const data = getJSONfromString<ICrewMembersRequest>(String(req.body));
 | 
			
		||||
    const dbCrewMember = inventory.CrewMembers.id(data.crewMember.ItemId.$oid)!;
 | 
			
		||||
    dbCrewMember.AssignedRole = data.crewMember.AssignedRole;
 | 
			
		||||
    dbCrewMember.SkillEfficiency = data.crewMember.SkillEfficiency;
 | 
			
		||||
    dbCrewMember.WeaponConfigIdx = data.crewMember.WeaponConfigIdx;
 | 
			
		||||
    dbCrewMember.WeaponId = new Types.ObjectId(data.crewMember.WeaponId.$oid);
 | 
			
		||||
    dbCrewMember.Configs = data.crewMember.Configs;
 | 
			
		||||
    dbCrewMember.SecondInCommand = data.crewMember.SecondInCommand;
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.json({
 | 
			
		||||
        crewMemberId: data.crewMember.ItemId.$oid,
 | 
			
		||||
        NemesisFingerprint: data.crewMember.NemesisFingerprint
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface ICrewMembersRequest {
 | 
			
		||||
    crewMember: ICrewMemberClient;
 | 
			
		||||
}
 | 
			
		||||
@ -12,6 +12,7 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
			
		||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
			
		||||
import { getRandomInt } from "@/src/services/rngService";
 | 
			
		||||
import { IFingerprintStat } from "@/src/helpers/rivenHelper";
 | 
			
		||||
import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
			
		||||
 | 
			
		||||
export const crewShipIdentifySalvageController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
@ -42,7 +43,9 @@ export const crewShipIdentifySalvageController: RequestHandler = async (req, res
 | 
			
		||||
        );
 | 
			
		||||
    } else {
 | 
			
		||||
        const meta = ExportRailjackWeapons[payload.ItemType];
 | 
			
		||||
        const upgradeType = meta.defaultUpgrades![0].ItemType;
 | 
			
		||||
        let defaultOverwrites: Partial<IEquipmentDatabase> | undefined;
 | 
			
		||||
        if (meta.defaultUpgrades?.[0]) {
 | 
			
		||||
            const upgradeType = meta.defaultUpgrades[0].ItemType;
 | 
			
		||||
            const upgradeMeta = ExportUpgrades[upgradeType];
 | 
			
		||||
            const buffs: IFingerprintStat[] = [];
 | 
			
		||||
            for (const buff of upgradeMeta.upgradeEntries!) {
 | 
			
		||||
@ -51,13 +54,22 @@ export const crewShipIdentifySalvageController: RequestHandler = async (req, res
 | 
			
		||||
                    Value: Math.trunc(Math.random() * 0x40000000)
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        addEquipment(inventory, "CrewShipSalvagedWeapons", payload.ItemType, undefined, inventoryChanges, {
 | 
			
		||||
            defaultOverwrites = {
 | 
			
		||||
                UpgradeType: upgradeType,
 | 
			
		||||
                UpgradeFingerprint: JSON.stringify({
 | 
			
		||||
                    compat: payload.ItemType,
 | 
			
		||||
                    buffs
 | 
			
		||||
                } satisfies IInnateDamageFingerprint)
 | 
			
		||||
        });
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
        addEquipment(
 | 
			
		||||
            inventory,
 | 
			
		||||
            "CrewShipSalvagedWeapons",
 | 
			
		||||
            payload.ItemType,
 | 
			
		||||
            undefined,
 | 
			
		||||
            inventoryChanges,
 | 
			
		||||
            defaultOverwrites
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    inventoryChanges.CrewShipRawSalvage = [
 | 
			
		||||
 | 
			
		||||
@ -1,16 +1,20 @@
 | 
			
		||||
import { toOid } from "@/src/helpers/inventoryHelpers";
 | 
			
		||||
import { Account, Ignore } from "@/src/models/loginModel";
 | 
			
		||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
			
		||||
import { IFriendInfo } from "@/src/types/guildTypes";
 | 
			
		||||
import { parallelForeach } from "@/src/utils/async-utils";
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
 | 
			
		||||
const getIgnoredUsersController: RequestHandler = (_req, res) => {
 | 
			
		||||
    res.writeHead(200, {
 | 
			
		||||
        "Content-Type": "text/html",
 | 
			
		||||
        "Content-Length": "3"
 | 
			
		||||
export const getIgnoredUsersController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const ignores = await Ignore.find({ ignorer: accountId });
 | 
			
		||||
    const ignoredUsers: IFriendInfo[] = [];
 | 
			
		||||
    await parallelForeach(ignores, async ignore => {
 | 
			
		||||
        const ignoreeAccount = (await Account.findById(ignore.ignoree, "DisplayName"))!;
 | 
			
		||||
        ignoredUsers.push({
 | 
			
		||||
            _id: toOid(ignore.ignoree),
 | 
			
		||||
            DisplayName: ignoreeAccount.DisplayName + ""
 | 
			
		||||
        });
 | 
			
		||||
    res.end(
 | 
			
		||||
        Buffer.from([
 | 
			
		||||
            0x7b, 0x22, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x22, 0x3a, 0x38, 0x33, 0x30, 0x34, 0x30, 0x37, 0x37, 0x32, 0x32,
 | 
			
		||||
            0x34, 0x30, 0x32, 0x32, 0x32, 0x36, 0x31, 0x35, 0x30, 0x31, 0x7d
 | 
			
		||||
        ])
 | 
			
		||||
    );
 | 
			
		||||
    });
 | 
			
		||||
    res.json({ IgnoredUsers: ignoredUsers });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export { getIgnoredUsersController };
 | 
			
		||||
 | 
			
		||||
@ -97,6 +97,19 @@ export const guildTechController: RequestHandler = async (req, res) => {
 | 
			
		||||
            res.end();
 | 
			
		||||
        } else {
 | 
			
		||||
            const recipe = ExportDojoRecipes.research[data.RecipeType];
 | 
			
		||||
            if (data.TechProductCategory) {
 | 
			
		||||
                if (
 | 
			
		||||
                    data.TechProductCategory != "CrewShipWeapons" &&
 | 
			
		||||
                    data.TechProductCategory != "CrewShipWeaponSkins"
 | 
			
		||||
                ) {
 | 
			
		||||
                    throw new Error(`unexpected TechProductCategory: ${data.TechProductCategory}`);
 | 
			
		||||
                }
 | 
			
		||||
                if (!inventory[getSalvageCategory(data.TechProductCategory)].id(data.CategoryItemId)) {
 | 
			
		||||
                    throw new Error(
 | 
			
		||||
                        `no item with id ${data.CategoryItemId} in ${getSalvageCategory(data.TechProductCategory)} array`
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            const techProject =
 | 
			
		||||
                inventory.PersonalTechProjects[
 | 
			
		||||
                    inventory.PersonalTechProjects.push({
 | 
			
		||||
@ -347,6 +360,22 @@ export const guildTechController: RequestHandler = async (req, res) => {
 | 
			
		||||
        res.json({
 | 
			
		||||
            inventoryChanges: inventoryChanges
 | 
			
		||||
        });
 | 
			
		||||
    } else if (data.Action == "InstantFinish") {
 | 
			
		||||
        if (data.TechProductCategory != "CrewShipWeapons" && data.TechProductCategory != "CrewShipWeaponSkins") {
 | 
			
		||||
            throw new Error(`unexpected TechProductCategory: ${data.TechProductCategory}`);
 | 
			
		||||
        }
 | 
			
		||||
        const inventoryChanges = finishComponentRepair(inventory, data.TechProductCategory, data.CategoryItemId!);
 | 
			
		||||
        inventoryChanges.MiscItems = [
 | 
			
		||||
            {
 | 
			
		||||
                ItemType: "/Lotus/Types/Items/MiscItems/InstantSalvageRepairItem",
 | 
			
		||||
                ItemCount: -1
 | 
			
		||||
            }
 | 
			
		||||
        ];
 | 
			
		||||
        addMiscItems(inventory, inventoryChanges.MiscItems);
 | 
			
		||||
        await inventory.save();
 | 
			
		||||
        res.json({
 | 
			
		||||
            inventoryChanges: inventoryChanges
 | 
			
		||||
        });
 | 
			
		||||
    } else {
 | 
			
		||||
        logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
 | 
			
		||||
        throw new Error(`unhandled guildTech request`);
 | 
			
		||||
@ -359,7 +388,7 @@ type TGuildTechRequest =
 | 
			
		||||
    | IGuildTechContributeRequest;
 | 
			
		||||
 | 
			
		||||
interface IGuildTechBasicRequest {
 | 
			
		||||
    Action: "Start" | "Fabricate" | "Pause" | "Unpause" | "Cancel" | "Rush";
 | 
			
		||||
    Action: "Start" | "Fabricate" | "Pause" | "Unpause" | "Cancel" | "Rush" | "InstantFinish";
 | 
			
		||||
    Mode: "Guild" | "Personal";
 | 
			
		||||
    RecipeType: string;
 | 
			
		||||
    TechProductCategory?: string;
 | 
			
		||||
@ -380,6 +409,12 @@ interface IGuildTechContributeRequest {
 | 
			
		||||
    VaultMiscItems: IMiscItem[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getSalvageCategory = (
 | 
			
		||||
    category: "CrewShipWeapons" | "CrewShipWeaponSkins"
 | 
			
		||||
): "CrewShipSalvagedWeapons" | "CrewShipSalvagedWeaponSkins" => {
 | 
			
		||||
    return category == "CrewShipWeapons" ? "CrewShipSalvagedWeapons" : "CrewShipSalvagedWeaponSkins";
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const claimSalvagedComponent = (inventory: TInventoryDatabaseDocument, itemId: string): IInventoryChanges => {
 | 
			
		||||
    // delete personal tech project
 | 
			
		||||
    const personalTechProjectIndex = inventory.PersonalTechProjects.findIndex(x => x.CategoryItemId?.equals(itemId));
 | 
			
		||||
@ -387,11 +422,19 @@ const claimSalvagedComponent = (inventory: TInventoryDatabaseDocument, itemId: s
 | 
			
		||||
    inventory.PersonalTechProjects.splice(personalTechProjectIndex, 1);
 | 
			
		||||
 | 
			
		||||
    const category = personalTechProject.ProductCategory! as "CrewShipWeapons" | "CrewShipWeaponSkins";
 | 
			
		||||
    const salvageCategory = category == "CrewShipWeapons" ? "CrewShipSalvagedWeapons" : "CrewShipSalvagedWeaponSkins";
 | 
			
		||||
    return finishComponentRepair(inventory, category, itemId);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const finishComponentRepair = (
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
    category: "CrewShipWeapons" | "CrewShipWeaponSkins",
 | 
			
		||||
    itemId: string
 | 
			
		||||
): IInventoryChanges => {
 | 
			
		||||
    const salvageCategory = getSalvageCategory(category);
 | 
			
		||||
 | 
			
		||||
    // find salved part & delete it
 | 
			
		||||
    const salvageIndex = inventory[salvageCategory].findIndex(x => x._id.equals(itemId));
 | 
			
		||||
    const salvageItem = inventory[category][salvageIndex];
 | 
			
		||||
    const salvageItem = inventory[salvageCategory][salvageIndex];
 | 
			
		||||
    inventory[salvageCategory].splice(salvageIndex, 1);
 | 
			
		||||
 | 
			
		||||
    // add final item
 | 
			
		||||
 | 
			
		||||
@ -53,6 +53,9 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
 | 
			
		||||
    logger.debug("mission report:", missionReport);
 | 
			
		||||
 | 
			
		||||
    const inventory = await getInventory(accountId);
 | 
			
		||||
    const firstCompletion = missionReport.SortieId
 | 
			
		||||
        ? inventory.CompletedSorties.indexOf(missionReport.SortieId) == -1
 | 
			
		||||
        : false;
 | 
			
		||||
    const inventoryUpdates = await addMissionInventoryUpdates(inventory, missionReport);
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
@ -69,7 +72,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const { MissionRewards, inventoryChanges, credits, AffiliationMods, SyndicateXPItemReward } =
 | 
			
		||||
        await addMissionRewards(inventory, missionReport);
 | 
			
		||||
        await addMissionRewards(inventory, missionReport, firstCompletion);
 | 
			
		||||
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    const inventoryResponse = await getInventoryResponse(inventory, true);
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,7 @@ import { getDefaultUpgrades } from "@/src/services/itemDataService";
 | 
			
		||||
import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
 | 
			
		||||
import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
			
		||||
import { getRandomInt } from "@/src/services/rngService";
 | 
			
		||||
import { ExportSentinels } from "warframe-public-export-plus";
 | 
			
		||||
import { ExportSentinels, IDefaultUpgrade } from "warframe-public-export-plus";
 | 
			
		||||
import { Status } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
			
		||||
 | 
			
		||||
interface IModularCraftRequest {
 | 
			
		||||
@ -34,10 +34,8 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
 | 
			
		||||
    const category = modularWeaponTypes[data.WeaponType];
 | 
			
		||||
    const inventory = await getInventory(accountId);
 | 
			
		||||
 | 
			
		||||
    const defaultUpgrades = getDefaultUpgrades(data.Parts);
 | 
			
		||||
    const defaultOverwrites: Partial<IEquipmentDatabase> = {
 | 
			
		||||
        Configs: applyDefaultUpgrades(inventory, defaultUpgrades)
 | 
			
		||||
    };
 | 
			
		||||
    let defaultUpgrades: IDefaultUpgrade[] | undefined;
 | 
			
		||||
    const defaultOverwrites: Partial<IEquipmentDatabase> = {};
 | 
			
		||||
    const inventoryChanges: IInventoryChanges = {};
 | 
			
		||||
    if (category == "KubrowPets") {
 | 
			
		||||
        const traits = {
 | 
			
		||||
@ -129,10 +127,17 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
 | 
			
		||||
        // Only save mutagen & antigen in the ModularParts.
 | 
			
		||||
        defaultOverwrites.ModularParts = [data.Parts[1], data.Parts[2]];
 | 
			
		||||
 | 
			
		||||
        for (const specialItem of ExportSentinels[data.WeaponType].exalted!) {
 | 
			
		||||
        const meta = ExportSentinels[data.WeaponType];
 | 
			
		||||
 | 
			
		||||
        for (const specialItem of meta.exalted!) {
 | 
			
		||||
            addSpecialItem(inventory, specialItem, inventoryChanges);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        defaultUpgrades = meta.defaultUpgrades;
 | 
			
		||||
    } else {
 | 
			
		||||
        defaultUpgrades = getDefaultUpgrades(data.Parts);
 | 
			
		||||
    }
 | 
			
		||||
    defaultOverwrites.Configs = applyDefaultUpgrades(inventory, defaultUpgrades);
 | 
			
		||||
    addEquipment(inventory, category, data.WeaponType, data.Parts, inventoryChanges, defaultOverwrites);
 | 
			
		||||
    combineInventoryChanges(inventoryChanges, occupySlot(inventory, productCategoryToInventoryBin(category)!, false));
 | 
			
		||||
    if (defaultUpgrades) {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										21
									
								
								src/controllers/api/removeIgnoredUserController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/controllers/api/removeIgnoredUserController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
			
		||||
import { Account, Ignore } from "@/src/models/loginModel";
 | 
			
		||||
import { getAccountForRequest } from "@/src/services/loginService";
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
 | 
			
		||||
export const removeIgnoredUserController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountForRequest(req);
 | 
			
		||||
    const data = getJSONfromString<IRemoveIgnoredUserRequest>(String(req.body));
 | 
			
		||||
    const ignoreeAccount = await Account.findOne(
 | 
			
		||||
        { DisplayName: data.playerName.substring(0, data.playerName.length - 1) },
 | 
			
		||||
        "_id"
 | 
			
		||||
    );
 | 
			
		||||
    if (ignoreeAccount) {
 | 
			
		||||
        await Ignore.deleteOne({ ignorer: accountId, ignoree: ignoreeAccount._id });
 | 
			
		||||
    }
 | 
			
		||||
    res.end();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface IRemoveIgnoredUserRequest {
 | 
			
		||||
    playerName: string;
 | 
			
		||||
}
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
			
		||||
import { Account } from "@/src/models/loginModel";
 | 
			
		||||
import { Account, Ignore } from "@/src/models/loginModel";
 | 
			
		||||
import { Inbox } from "@/src/models/inboxModel";
 | 
			
		||||
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
 | 
			
		||||
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
 | 
			
		||||
@ -23,6 +23,8 @@ export const deleteAccountController: RequestHandler = async (req, res) => {
 | 
			
		||||
    await Promise.all([
 | 
			
		||||
        Account.deleteOne({ _id: accountId }),
 | 
			
		||||
        GuildMember.deleteMany({ accountId: accountId }),
 | 
			
		||||
        Ignore.deleteMany({ ignorer: accountId }),
 | 
			
		||||
        Ignore.deleteMany({ ignoree: accountId }),
 | 
			
		||||
        Inbox.deleteMany({ ownerId: accountId }),
 | 
			
		||||
        Inventory.deleteOne({ accountOwnerId: accountId }),
 | 
			
		||||
        Leaderboard.deleteMany({ ownerId: accountId }),
 | 
			
		||||
 | 
			
		||||
@ -39,10 +39,9 @@ import {
 | 
			
		||||
    ILoreFragmentScan,
 | 
			
		||||
    IEvolutionProgress,
 | 
			
		||||
    IEndlessXpProgress,
 | 
			
		||||
    ICrewShipPortGuns,
 | 
			
		||||
    ICrewShipCustomization,
 | 
			
		||||
    ICrewShipWeapon,
 | 
			
		||||
    ICrewShipPilotWeapon,
 | 
			
		||||
    ICrewShipWeaponEmplacements,
 | 
			
		||||
    IShipExterior,
 | 
			
		||||
    IHelminthFoodRecord,
 | 
			
		||||
    ICrewShipMembersDatabase,
 | 
			
		||||
@ -88,7 +87,15 @@ import {
 | 
			
		||||
    IPersonalTechProjectDatabase,
 | 
			
		||||
    IPersonalTechProjectClient,
 | 
			
		||||
    ILastSortieRewardDatabase,
 | 
			
		||||
    ILastSortieRewardClient
 | 
			
		||||
    ILastSortieRewardClient,
 | 
			
		||||
    ICrewMemberSkill,
 | 
			
		||||
    ICrewMemberSkillEfficiency,
 | 
			
		||||
    ICrewMemberDatabase,
 | 
			
		||||
    ICrewMemberClient,
 | 
			
		||||
    ISortieRewardAttenuation,
 | 
			
		||||
    IInvasionProgressDatabase,
 | 
			
		||||
    IInvasionProgressClient,
 | 
			
		||||
    IAccolades
 | 
			
		||||
} from "../../types/inventoryTypes/inventoryTypes";
 | 
			
		||||
import { IOid } from "../../types/commonTypes";
 | 
			
		||||
import {
 | 
			
		||||
@ -102,7 +109,7 @@ import {
 | 
			
		||||
    IEquipmentClient
 | 
			
		||||
} from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
			
		||||
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
 | 
			
		||||
import { EquipmentSelectionSchema } from "./loadoutModel";
 | 
			
		||||
import { EquipmentSelectionSchema, oidSchema } from "./loadoutModel";
 | 
			
		||||
 | 
			
		||||
export const typeCountSchema = new Schema<ITypeCount>({ ItemType: String, ItemCount: Number }, { _id: false });
 | 
			
		||||
 | 
			
		||||
@ -294,6 +301,55 @@ upgradeSchema.set("toJSON", {
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const crewMemberSkillSchema = new Schema<ICrewMemberSkill>(
 | 
			
		||||
    {
 | 
			
		||||
        Assigned: Number
 | 
			
		||||
    },
 | 
			
		||||
    { _id: false }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const crewMemberSkillEfficiencySchema = new Schema<ICrewMemberSkillEfficiency>(
 | 
			
		||||
    {
 | 
			
		||||
        PILOTING: crewMemberSkillSchema,
 | 
			
		||||
        GUNNERY: crewMemberSkillSchema,
 | 
			
		||||
        ENGINEERING: crewMemberSkillSchema,
 | 
			
		||||
        COMBAT: crewMemberSkillSchema,
 | 
			
		||||
        SURVIVABILITY: crewMemberSkillSchema
 | 
			
		||||
    },
 | 
			
		||||
    { _id: false }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const crewMemberSchema = new Schema<ICrewMemberDatabase>(
 | 
			
		||||
    {
 | 
			
		||||
        ItemType: { type: String, required: true },
 | 
			
		||||
        NemesisFingerprint: { type: BigInt, default: 0n },
 | 
			
		||||
        Seed: { type: BigInt, default: 0n },
 | 
			
		||||
        AssignedRole: Number,
 | 
			
		||||
        SkillEfficiency: crewMemberSkillEfficiencySchema,
 | 
			
		||||
        WeaponConfigIdx: Number,
 | 
			
		||||
        WeaponId: { type: Schema.Types.ObjectId, default: "000000000000000000000000" },
 | 
			
		||||
        XP: { type: Number, default: 0 },
 | 
			
		||||
        PowersuitType: { type: String, required: true },
 | 
			
		||||
        Configs: [ItemConfigSchema],
 | 
			
		||||
        SecondInCommand: { type: Boolean, default: false }
 | 
			
		||||
    },
 | 
			
		||||
    { id: false }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
crewMemberSchema.set("toJSON", {
 | 
			
		||||
    virtuals: true,
 | 
			
		||||
    transform(_doc, obj) {
 | 
			
		||||
        const db = obj as ICrewMemberDatabase;
 | 
			
		||||
        const client = obj as ICrewMemberClient;
 | 
			
		||||
 | 
			
		||||
        client.WeaponId = toOid(db.WeaponId);
 | 
			
		||||
        client.ItemId = toOid(db._id);
 | 
			
		||||
 | 
			
		||||
        delete obj._id;
 | 
			
		||||
        delete obj.__v;
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const slotsBinSchema = new Schema<ISlots>(
 | 
			
		||||
    {
 | 
			
		||||
        Slots: Number,
 | 
			
		||||
@ -631,6 +687,27 @@ questKeysSchema.set("toJSON", {
 | 
			
		||||
 | 
			
		||||
export const fusionTreasuresSchema = new Schema<IFusionTreasure>().add(typeCountSchema).add({ Sockets: Number });
 | 
			
		||||
 | 
			
		||||
const invasionProgressSchema = new Schema<IInvasionProgressDatabase>(
 | 
			
		||||
    {
 | 
			
		||||
        invasionId: Schema.Types.ObjectId,
 | 
			
		||||
        Delta: Number,
 | 
			
		||||
        AttackerScore: Number,
 | 
			
		||||
        DefenderScore: Number
 | 
			
		||||
    },
 | 
			
		||||
    { _id: false }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
invasionProgressSchema.set("toJSON", {
 | 
			
		||||
    transform(_doc, obj) {
 | 
			
		||||
        const db = obj as IInvasionProgressDatabase;
 | 
			
		||||
        const client = obj as IInvasionProgressClient;
 | 
			
		||||
 | 
			
		||||
        client._id = toOid(db.invasionId);
 | 
			
		||||
        delete obj.invasionId;
 | 
			
		||||
        delete obj.__v;
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const spectreLoadoutsSchema = new Schema<ISpectreLoadout>(
 | 
			
		||||
    {
 | 
			
		||||
        ItemType: String,
 | 
			
		||||
@ -719,25 +796,23 @@ const endlessXpProgressSchema = new Schema<IEndlessXpProgress>(
 | 
			
		||||
    { _id: false }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const crewShipPilotWeaponSchema = new Schema<ICrewShipPilotWeapon>(
 | 
			
		||||
const crewShipWeaponEmplacementsSchema = new Schema<ICrewShipWeaponEmplacements>(
 | 
			
		||||
    {
 | 
			
		||||
        PRIMARY_A: EquipmentSelectionSchema,
 | 
			
		||||
        SECONDARY_A: EquipmentSelectionSchema
 | 
			
		||||
    },
 | 
			
		||||
    { _id: false }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const crewShipPortGunsSchema = new Schema<ICrewShipPortGuns>(
 | 
			
		||||
    {
 | 
			
		||||
        PRIMARY_A: EquipmentSelectionSchema
 | 
			
		||||
        PRIMARY_B: EquipmentSelectionSchema,
 | 
			
		||||
        SECONDARY_A: EquipmentSelectionSchema,
 | 
			
		||||
        SECONDARY_B: EquipmentSelectionSchema
 | 
			
		||||
    },
 | 
			
		||||
    { _id: false }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const crewShipWeaponSchema = new Schema<ICrewShipWeapon>(
 | 
			
		||||
    {
 | 
			
		||||
        PILOT: crewShipPilotWeaponSchema,
 | 
			
		||||
        PORT_GUNS: crewShipPortGunsSchema
 | 
			
		||||
        PILOT: crewShipWeaponEmplacementsSchema,
 | 
			
		||||
        PORT_GUNS: crewShipWeaponEmplacementsSchema,
 | 
			
		||||
        STARBOARD_GUNS: crewShipWeaponEmplacementsSchema,
 | 
			
		||||
        ARTILLERY: crewShipWeaponEmplacementsSchema,
 | 
			
		||||
        SCANNER: crewShipWeaponEmplacementsSchema
 | 
			
		||||
    },
 | 
			
		||||
    { _id: false }
 | 
			
		||||
);
 | 
			
		||||
@ -761,7 +836,7 @@ const crewShipCustomizationSchema = new Schema<ICrewShipCustomization>(
 | 
			
		||||
const crewShipMemberSchema = new Schema<ICrewShipMemberDatabase>(
 | 
			
		||||
    {
 | 
			
		||||
        ItemId: { type: Schema.Types.ObjectId, required: false },
 | 
			
		||||
        NemesisFingerprint: { type: Number, required: false }
 | 
			
		||||
        NemesisFingerprint: { type: BigInt, required: false }
 | 
			
		||||
    },
 | 
			
		||||
    { _id: false }
 | 
			
		||||
);
 | 
			
		||||
@ -985,6 +1060,13 @@ pendingRecipeSchema.set("toJSON", {
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const accoladesSchema = new Schema<IAccolades>(
 | 
			
		||||
    {
 | 
			
		||||
        Heirloom: Boolean
 | 
			
		||||
    },
 | 
			
		||||
    { _id: false }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const infestedFoundrySchema = new Schema<IInfestedFoundryDatabase>(
 | 
			
		||||
    {
 | 
			
		||||
        Name: String,
 | 
			
		||||
@ -1226,6 +1308,14 @@ lastSortieRewardSchema.set("toJSON", {
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const sortieRewardAttenutationSchema = new Schema<ISortieRewardAttenuation>(
 | 
			
		||||
    {
 | 
			
		||||
        Tag: String,
 | 
			
		||||
        Atten: Number
 | 
			
		||||
    },
 | 
			
		||||
    { _id: false }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const lockedWeaponGroupSchema = new Schema<ILockedWeaponGroupDatabase>(
 | 
			
		||||
    {
 | 
			
		||||
        s: Schema.Types.ObjectId,
 | 
			
		||||
@ -1363,7 +1453,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
 | 
			
		||||
        CrewShipSalvagedWeaponSkins: [upgradeSchema],
 | 
			
		||||
 | 
			
		||||
        //RailJack Crew
 | 
			
		||||
        CrewMembers: [Schema.Types.Mixed],
 | 
			
		||||
        CrewMembers: [crewMemberSchema],
 | 
			
		||||
 | 
			
		||||
        //Complete Mission\Quests
 | 
			
		||||
        Missions: [missionSchema],
 | 
			
		||||
@ -1384,6 +1474,16 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
 | 
			
		||||
        //Mastery Rank next availability
 | 
			
		||||
        TrainingDate: { type: Date, default: new Date(0) },
 | 
			
		||||
 | 
			
		||||
        //Accolades
 | 
			
		||||
        Staff: Boolean,
 | 
			
		||||
        Founder: Number,
 | 
			
		||||
        Guide: Number,
 | 
			
		||||
        Moderator: Boolean,
 | 
			
		||||
        Partner: Boolean,
 | 
			
		||||
        Accolades: accoladesSchema,
 | 
			
		||||
        //Not an accolade but unlocks an extra chat
 | 
			
		||||
        Counselor: Boolean,
 | 
			
		||||
 | 
			
		||||
        //you saw last played Region when you opened the star map
 | 
			
		||||
        LastRegionPlayed: String,
 | 
			
		||||
 | 
			
		||||
@ -1423,7 +1523,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
 | 
			
		||||
 | 
			
		||||
        SentientSpawnChanceBoosters: Schema.Types.Mixed,
 | 
			
		||||
 | 
			
		||||
        QualifyingInvasions: [Schema.Types.Mixed],
 | 
			
		||||
        QualifyingInvasions: [invasionProgressSchema],
 | 
			
		||||
        FactionScores: [Number],
 | 
			
		||||
 | 
			
		||||
        // https://warframe.fandom.com/wiki/Specter_(Tenno)
 | 
			
		||||
@ -1445,6 +1545,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
 | 
			
		||||
        CompletedSorties: [String],
 | 
			
		||||
        LastSortieReward: { type: [lastSortieRewardSchema], default: undefined },
 | 
			
		||||
        LastLiteSortieReward: { type: [lastSortieRewardSchema], default: undefined },
 | 
			
		||||
        SortieRewardAttenuation: { type: [sortieRewardAttenutationSchema], default: undefined },
 | 
			
		||||
 | 
			
		||||
        // Resource Extractor Drones
 | 
			
		||||
        Drones: [droneSchema],
 | 
			
		||||
@ -1538,7 +1639,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
 | 
			
		||||
        HasContributedToDojo: Boolean,
 | 
			
		||||
        HWIDProtectEnabled: Boolean,
 | 
			
		||||
        LoadOutPresets: { type: Schema.Types.ObjectId, ref: "Loadout" },
 | 
			
		||||
        CurrentLoadOutIds: [Schema.Types.Mixed],
 | 
			
		||||
        CurrentLoadOutIds: [oidSchema],
 | 
			
		||||
        RandomUpgradesIdentified: Number,
 | 
			
		||||
        BountyScore: Number,
 | 
			
		||||
        ChallengeInstanceStates: [Schema.Types.Mixed],
 | 
			
		||||
@ -1645,6 +1746,7 @@ export type InventoryDocumentProps = {
 | 
			
		||||
    CrewShipWeaponSkins: Types.DocumentArray<IUpgradeDatabase>;
 | 
			
		||||
    CrewShipSalvagedWeaponSkins: Types.DocumentArray<IUpgradeDatabase>;
 | 
			
		||||
    PersonalTechProjects: Types.DocumentArray<IPersonalTechProjectDatabase>;
 | 
			
		||||
    CrewMembers: Types.DocumentArray<ICrewMemberDatabase>;
 | 
			
		||||
} & { [K in TEquipmentKey]: Types.DocumentArray<IEquipmentDatabase> };
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@ import { IEquipmentSelection } from "@/src/types/inventoryTypes/commonInventoryT
 | 
			
		||||
import { ILoadoutConfigDatabase, ILoadoutDatabase } from "@/src/types/saveLoadoutTypes";
 | 
			
		||||
import { Document, Model, Schema, Types, model } from "mongoose";
 | 
			
		||||
 | 
			
		||||
const oidSchema = new Schema<IOid>(
 | 
			
		||||
export const oidSchema = new Schema<IOid>(
 | 
			
		||||
    {
 | 
			
		||||
        $oid: String
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { IDatabaseAccountJson } from "@/src/types/loginTypes";
 | 
			
		||||
import { IDatabaseAccountJson, IIgnore } from "@/src/types/loginTypes";
 | 
			
		||||
import { model, Schema, SchemaOptions } from "mongoose";
 | 
			
		||||
 | 
			
		||||
const opts = {
 | 
			
		||||
@ -37,3 +37,13 @@ databaseAccountSchema.set("toJSON", {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const Account = model<IDatabaseAccountJson>("Account", databaseAccountSchema);
 | 
			
		||||
 | 
			
		||||
const ignoreSchema = new Schema<IIgnore>({
 | 
			
		||||
    ignorer: Schema.Types.ObjectId,
 | 
			
		||||
    ignoree: Schema.Types.ObjectId
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
ignoreSchema.index({ ignorer: 1 });
 | 
			
		||||
ignoreSchema.index({ ignorer: 1, ignoree: 1 }, { unique: true });
 | 
			
		||||
 | 
			
		||||
export const Ignore = model<IIgnore>("Ignore", ignoreSchema);
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,7 @@ import { abortDojoComponentController } from "@/src/controllers/api/abortDojoCom
 | 
			
		||||
import { abortDojoComponentDestructionController } from "@/src/controllers/api/abortDojoComponentDestructionController";
 | 
			
		||||
import { activateRandomModController } from "@/src/controllers/api/activateRandomModController";
 | 
			
		||||
import { addFriendImageController } from "@/src/controllers/api/addFriendImageController";
 | 
			
		||||
import { addIgnoredUserController } from "@/src/controllers/api/addIgnoredUserController";
 | 
			
		||||
import { addToAllianceController } from "@/src/controllers/api/addToAllianceController";
 | 
			
		||||
import { addToGuildController } from "@/src/controllers/api/addToGuildController";
 | 
			
		||||
import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController";
 | 
			
		||||
@ -27,6 +28,7 @@ import { contributeToVaultController } from "@/src/controllers/api/contributeToV
 | 
			
		||||
import { createAllianceController } from "@/src/controllers/api/createAllianceController";
 | 
			
		||||
import { createGuildController } from "@/src/controllers/api/createGuildController";
 | 
			
		||||
import { creditsController } from "@/src/controllers/api/creditsController";
 | 
			
		||||
import { crewMembersController } from "@/src/controllers/api/crewMembersController";
 | 
			
		||||
import { crewShipIdentifySalvageController } from "@/src/controllers/api/crewShipIdentifySalvageController";
 | 
			
		||||
import { customizeGuildRanksController } from "@/src/controllers/api/customizeGuildRanksController";
 | 
			
		||||
import { customObstacleCourseLeaderboardController } from "@/src/controllers/api/customObstacleCourseLeaderboardController";
 | 
			
		||||
@ -97,6 +99,7 @@ import { redeemPromoCodeController } from "@/src/controllers/api/redeemPromoCode
 | 
			
		||||
import { releasePetController } from "@/src/controllers/api/releasePetController";
 | 
			
		||||
import { removeFromAllianceController } from "@/src/controllers/api/removeFromAllianceController";
 | 
			
		||||
import { removeFromGuildController } from "@/src/controllers/api/removeFromGuildController";
 | 
			
		||||
import { removeIgnoredUserController } from "@/src/controllers/api/removeIgnoredUserController";
 | 
			
		||||
import { rerollRandomModController } from "@/src/controllers/api/rerollRandomModController";
 | 
			
		||||
import { retrievePetFromStasisController } from "@/src/controllers/api/retrievePetFromStasisController";
 | 
			
		||||
import { saveDialogueController } from "@/src/controllers/api/saveDialogueController";
 | 
			
		||||
@ -202,6 +205,7 @@ apiRouter.get("/updateSession.php", updateSessionGetController);
 | 
			
		||||
apiRouter.post("/abortDojoComponent.php", abortDojoComponentController);
 | 
			
		||||
apiRouter.post("/activateRandomMod.php", activateRandomModController);
 | 
			
		||||
apiRouter.post("/addFriendImage.php", addFriendImageController);
 | 
			
		||||
apiRouter.post("/addIgnoredUser.php", addIgnoredUserController);
 | 
			
		||||
apiRouter.post("/addToAlliance.php", addToAllianceController);
 | 
			
		||||
apiRouter.post("/addToGuild.php", addToGuildController);
 | 
			
		||||
apiRouter.post("/arcaneCommon.php", arcaneCommonController);
 | 
			
		||||
@ -219,6 +223,7 @@ apiRouter.post("/contributeToDojoComponent.php", contributeToDojoComponentContro
 | 
			
		||||
apiRouter.post("/contributeToVault.php", contributeToVaultController);
 | 
			
		||||
apiRouter.post("/createAlliance.php", createAllianceController);
 | 
			
		||||
apiRouter.post("/createGuild.php", createGuildController);
 | 
			
		||||
apiRouter.post("/crewMembers.php", crewMembersController);
 | 
			
		||||
apiRouter.post("/crewShipIdentifySalvage.php", crewShipIdentifySalvageController);
 | 
			
		||||
apiRouter.post("/customizeGuildRanks.php", customizeGuildRanksController);
 | 
			
		||||
apiRouter.post("/customObstacleCourseLeaderboard.php", customObstacleCourseLeaderboardController);
 | 
			
		||||
@ -266,6 +271,7 @@ apiRouter.post("/purchase.php", purchaseController);
 | 
			
		||||
apiRouter.post("/redeemPromoCode.php", redeemPromoCodeController);
 | 
			
		||||
apiRouter.post("/releasePet.php", releasePetController);
 | 
			
		||||
apiRouter.post("/removeFromGuild.php", removeFromGuildController);
 | 
			
		||||
apiRouter.post("/removeIgnoredUser.php", removeIgnoredUserController);
 | 
			
		||||
apiRouter.post("/rerollRandomMod.php", rerollRandomModController);
 | 
			
		||||
apiRouter.post("/retrievePetFromStasis.php", retrievePetFromStasisController);
 | 
			
		||||
apiRouter.post("/saveDialogue.php", saveDialogueController);
 | 
			
		||||
 | 
			
		||||
@ -59,6 +59,7 @@ export const getGuildClient = async (guild: TGuildDatabaseDocument, accountId: s
 | 
			
		||||
 | 
			
		||||
    const members: IGuildMemberClient[] = [];
 | 
			
		||||
    let missingEntry = true;
 | 
			
		||||
    const dataFillInPromises: Promise<void>[] = [];
 | 
			
		||||
    for (const guildMember of guildMembers) {
 | 
			
		||||
        const member: IGuildMemberClient = {
 | 
			
		||||
            _id: toOid(guildMember.accountId),
 | 
			
		||||
@ -70,8 +71,12 @@ export const getGuildClient = async (guild: TGuildDatabaseDocument, accountId: s
 | 
			
		||||
        if (guildMember.accountId.equals(accountId)) {
 | 
			
		||||
            missingEntry = false;
 | 
			
		||||
        } else {
 | 
			
		||||
            dataFillInPromises.push(
 | 
			
		||||
                (async (): Promise<void> => {
 | 
			
		||||
                    member.DisplayName = (await Account.findById(guildMember.accountId, "DisplayName"))!.DisplayName;
 | 
			
		||||
            await fillInInventoryDataForGuildMember(member);
 | 
			
		||||
                })()
 | 
			
		||||
            );
 | 
			
		||||
            dataFillInPromises.push(fillInInventoryDataForGuildMember(member));
 | 
			
		||||
        }
 | 
			
		||||
        members.push(member);
 | 
			
		||||
    }
 | 
			
		||||
@ -90,6 +95,8 @@ export const getGuildClient = async (guild: TGuildDatabaseDocument, accountId: s
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await Promise.all(dataFillInPromises);
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        _id: toOid(guild._id),
 | 
			
		||||
        Name: guild.Name,
 | 
			
		||||
 | 
			
		||||
@ -104,18 +104,18 @@ const replaceSlots = (db: ISlots, client: ISlots): void => {
 | 
			
		||||
    db.Slots = client.Slots;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const convertCrewShipMember = (client: ICrewShipMemberClient): ICrewShipMemberDatabase => {
 | 
			
		||||
    return {
 | 
			
		||||
        ...client,
 | 
			
		||||
        ItemId: client.ItemId ? new Types.ObjectId(client.ItemId.$oid) : undefined
 | 
			
		||||
    };
 | 
			
		||||
export const importCrewMemberId = (crewMemberId: ICrewShipMemberClient): ICrewShipMemberDatabase => {
 | 
			
		||||
    if (crewMemberId.ItemId) {
 | 
			
		||||
        return { ItemId: new Types.ObjectId(crewMemberId.ItemId.$oid) };
 | 
			
		||||
    }
 | 
			
		||||
    return { NemesisFingerprint: BigInt(crewMemberId.NemesisFingerprint ?? 0) };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const convertCrewShipMembers = (client: ICrewShipMembersClient): ICrewShipMembersDatabase => {
 | 
			
		||||
    return {
 | 
			
		||||
        SLOT_A: client.SLOT_A ? convertCrewShipMember(client.SLOT_A) : undefined,
 | 
			
		||||
        SLOT_B: client.SLOT_B ? convertCrewShipMember(client.SLOT_B) : undefined,
 | 
			
		||||
        SLOT_C: client.SLOT_C ? convertCrewShipMember(client.SLOT_C) : undefined
 | 
			
		||||
        SLOT_A: client.SLOT_A ? importCrewMemberId(client.SLOT_A) : undefined,
 | 
			
		||||
        SLOT_B: client.SLOT_B ? importCrewMemberId(client.SLOT_B) : undefined,
 | 
			
		||||
        SLOT_C: client.SLOT_C ? importCrewMemberId(client.SLOT_C) : undefined
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -230,17 +230,23 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
 | 
			
		||||
            replaceSlots(db[key], client[key]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    // boolean
 | 
			
		||||
    for (const key of [
 | 
			
		||||
        "UseAdultOperatorLoadout",
 | 
			
		||||
        "HasOwnedVoidProjectionsPreviously",
 | 
			
		||||
        "ReceivedStartingGear",
 | 
			
		||||
        "ArchwingEnabled",
 | 
			
		||||
        "PlayedParkourTutorial"
 | 
			
		||||
        "PlayedParkourTutorial",
 | 
			
		||||
        "Staff",
 | 
			
		||||
        "Moderator",
 | 
			
		||||
        "Partner",
 | 
			
		||||
        "Counselor"
 | 
			
		||||
    ] as const) {
 | 
			
		||||
        if (client[key] !== undefined) {
 | 
			
		||||
            db[key] = client[key];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    // number
 | 
			
		||||
    for (const key of [
 | 
			
		||||
        "PlayerLevel",
 | 
			
		||||
        "RegularCredits",
 | 
			
		||||
@ -250,12 +256,15 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
 | 
			
		||||
        "PrimeTokens",
 | 
			
		||||
        "TradesRemaining",
 | 
			
		||||
        "GiftsRemaining",
 | 
			
		||||
        "ChallengesFixVersion"
 | 
			
		||||
        "ChallengesFixVersion",
 | 
			
		||||
        "Founder",
 | 
			
		||||
        "Guide"
 | 
			
		||||
    ] as const) {
 | 
			
		||||
        if (client[key] !== undefined) {
 | 
			
		||||
            db[key] = client[key];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    // string
 | 
			
		||||
    for (const key of [
 | 
			
		||||
        "ThemeStyle",
 | 
			
		||||
        "ThemeBackground",
 | 
			
		||||
@ -270,6 +279,7 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
 | 
			
		||||
            db[key] = client[key];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    // string[]
 | 
			
		||||
    for (const key of [
 | 
			
		||||
        "EquippedGear",
 | 
			
		||||
        "EquippedEmotes",
 | 
			
		||||
@ -380,6 +390,9 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    if (client.Accolades !== undefined) {
 | 
			
		||||
        db.Accolades = client.Accolades;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const convertLoadOutConfig = (client: ILoadoutConfigClient): ILoadoutConfigDatabase => {
 | 
			
		||||
 | 
			
		||||
@ -22,7 +22,8 @@ import {
 | 
			
		||||
    IDroneClient,
 | 
			
		||||
    IUpgradeClient,
 | 
			
		||||
    TPartialStartingGear,
 | 
			
		||||
    ILoreFragmentScan
 | 
			
		||||
    ILoreFragmentScan,
 | 
			
		||||
    ICrewMemberClient
 | 
			
		||||
} from "@/src/types/inventoryTypes/inventoryTypes";
 | 
			
		||||
import { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/genericUpdate";
 | 
			
		||||
import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes";
 | 
			
		||||
@ -713,6 +714,15 @@ export const addItem = async (
 | 
			
		||||
                        return {
 | 
			
		||||
                            MiscItems: miscItemChanges
 | 
			
		||||
                        };
 | 
			
		||||
                    } else if (typeName.startsWith("/Lotus/Types/Game/CrewShip/CrewMember/")) {
 | 
			
		||||
                        if (!seed) {
 | 
			
		||||
                            throw new Error(`Expected crew member to have a seed`);
 | 
			
		||||
                        }
 | 
			
		||||
                        seed |= 0x33b81en << 32n;
 | 
			
		||||
                        return {
 | 
			
		||||
                            ...addCrewMember(inventory, typeName, seed),
 | 
			
		||||
                            ...occupySlot(inventory, InventorySlot.CREWMEMBERS, premiumPurchase)
 | 
			
		||||
                        };
 | 
			
		||||
                    } else if (typeName == "/Lotus/Types/Game/CrewShip/RailJack/DefaultHarness") {
 | 
			
		||||
                        return addCrewShipHarness(inventory, typeName);
 | 
			
		||||
                    }
 | 
			
		||||
@ -1212,6 +1222,78 @@ const addDrone = (
 | 
			
		||||
    return inventoryChanges;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*const getCrewMemberSkills = (seed: bigint, skillPointsToAssign: number): Record<string, number> => {
 | 
			
		||||
    const rng = new SRng(seed);
 | 
			
		||||
 | 
			
		||||
    const skills = ["PILOTING", "GUNNERY", "ENGINEERING", "COMBAT", "SURVIVABILITY"];
 | 
			
		||||
    for (let i = 1; i != 5; ++i) {
 | 
			
		||||
        const swapIndex = rng.randomInt(0, i);
 | 
			
		||||
        if (swapIndex != i) {
 | 
			
		||||
            const tmp = skills[i];
 | 
			
		||||
            skills[i] = skills[swapIndex];
 | 
			
		||||
            skills[swapIndex] = tmp;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    rng.randomFloat(); // unused afaict
 | 
			
		||||
 | 
			
		||||
    const skillAssignments = [0, 0, 0, 0, 0];
 | 
			
		||||
    for (let skill = 0; skillPointsToAssign; skill = (skill + 1) % 5) {
 | 
			
		||||
        const maxIncrease = Math.min(5 - skillAssignments[skill], skillPointsToAssign);
 | 
			
		||||
        const increase = rng.randomInt(0, maxIncrease);
 | 
			
		||||
        skillAssignments[skill] += increase;
 | 
			
		||||
        skillPointsToAssign -= increase;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    skillAssignments.sort((a, b) => b - a);
 | 
			
		||||
 | 
			
		||||
    const combined: Record<string, number> = {};
 | 
			
		||||
    for (let i = 0; i != 5; ++i) {
 | 
			
		||||
        combined[skills[i]] = skillAssignments[i];
 | 
			
		||||
    }
 | 
			
		||||
    return combined;
 | 
			
		||||
};*/
 | 
			
		||||
 | 
			
		||||
const addCrewMember = (
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
    itemType: string,
 | 
			
		||||
    seed: bigint,
 | 
			
		||||
    inventoryChanges: IInventoryChanges = {}
 | 
			
		||||
): IInventoryChanges => {
 | 
			
		||||
    // SkillEfficiency is additional to the base stats, so we don't need to compute this
 | 
			
		||||
    //const skillPointsToAssign = itemType.endsWith("Strong") ? 12 : itemType.indexOf("Medium") != -1 ? 10 : 8;
 | 
			
		||||
    //const skills = getCrewMemberSkills(seed, skillPointsToAssign);
 | 
			
		||||
 | 
			
		||||
    // Arbiters = male
 | 
			
		||||
    // CephalonSuda = female
 | 
			
		||||
    // NewLoka = female
 | 
			
		||||
    // Perrin = male
 | 
			
		||||
    // RedVeil = male
 | 
			
		||||
    // SteelMeridian = female
 | 
			
		||||
    const powersuitType =
 | 
			
		||||
        itemType.indexOf("Arbiters") != -1 || itemType.indexOf("Perrin") != -1 || itemType.indexOf("RedVeil") != -1
 | 
			
		||||
            ? "/Lotus/Powersuits/NpcPowersuits/CrewMemberMaleSuit"
 | 
			
		||||
            : "/Lotus/Powersuits/NpcPowersuits/CrewMemberFemaleSuit";
 | 
			
		||||
 | 
			
		||||
    const index =
 | 
			
		||||
        inventory.CrewMembers.push({
 | 
			
		||||
            ItemType: itemType,
 | 
			
		||||
            NemesisFingerprint: 0n,
 | 
			
		||||
            Seed: seed,
 | 
			
		||||
            SkillEfficiency: {
 | 
			
		||||
                PILOTING: { Assigned: 0 },
 | 
			
		||||
                GUNNERY: { Assigned: 0 },
 | 
			
		||||
                ENGINEERING: { Assigned: 0 },
 | 
			
		||||
                COMBAT: { Assigned: 0 },
 | 
			
		||||
                SURVIVABILITY: { Assigned: 0 }
 | 
			
		||||
            },
 | 
			
		||||
            PowersuitType: powersuitType
 | 
			
		||||
        }) - 1;
 | 
			
		||||
    inventoryChanges.CrewMembers ??= [];
 | 
			
		||||
    inventoryChanges.CrewMembers.push(inventory.CrewMembers[index].toJSON<ICrewMemberClient>());
 | 
			
		||||
    return inventoryChanges;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const addEmailItem = async (
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
    typeName: string,
 | 
			
		||||
 | 
			
		||||
@ -53,7 +53,7 @@ import conservationAnimals from "@/static/fixed_responses/conservationAnimals.js
 | 
			
		||||
import { getInfNodes } from "@/src/helpers/nemesisHelpers";
 | 
			
		||||
import { Loadout } from "../models/inventoryModels/loadoutModel";
 | 
			
		||||
import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes";
 | 
			
		||||
import { getWorldState } from "./worldStateService";
 | 
			
		||||
import { getLiteSortie, getWorldState, idToWeek } from "./worldStateService";
 | 
			
		||||
import { config } from "./configService";
 | 
			
		||||
 | 
			
		||||
const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[] => {
 | 
			
		||||
@ -71,7 +71,12 @@ const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[]
 | 
			
		||||
        return [rewardInfo.rewardTier];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const rotationCount = rewardInfo.rewardQualifications?.length || 0;
 | 
			
		||||
    // Aborting a railjack mission should not give any rewards (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1741)
 | 
			
		||||
    if (rewardInfo.rewardQualifications === undefined) {
 | 
			
		||||
        return [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const rotationCount = rewardInfo.rewardQualifications.length || 0;
 | 
			
		||||
    if (rotationCount === 0) return [0];
 | 
			
		||||
 | 
			
		||||
    const rotationPattern =
 | 
			
		||||
@ -128,11 +133,18 @@ export const addMissionInventoryUpdates = async (
 | 
			
		||||
                ]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Somewhat heuristically detect G3 capture:
 | 
			
		||||
        // - https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1365
 | 
			
		||||
        // - https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1694
 | 
			
		||||
        // - https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1724
 | 
			
		||||
        if (
 | 
			
		||||
            inventoryUpdates.MissionFailed &&
 | 
			
		||||
            inventoryUpdates.MissionStatus == "GS_FAILURE" &&
 | 
			
		||||
            inventoryUpdates.ObjectiveReached &&
 | 
			
		||||
            !inventoryUpdates.LockedWeaponGroup
 | 
			
		||||
            !inventoryUpdates.LockedWeaponGroup &&
 | 
			
		||||
            !inventory.LockedWeaponGroup &&
 | 
			
		||||
            !inventoryUpdates.LevelKeyName
 | 
			
		||||
        ) {
 | 
			
		||||
            const loadout = (await Loadout.findById(inventory.LoadOutPresets, "NORMAL"))!;
 | 
			
		||||
            const config = loadout.NORMAL.id(inventory.CurrentLoadOutIds[0].$oid)!;
 | 
			
		||||
@ -407,7 +419,9 @@ export const addMissionInventoryUpdates = async (
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            case "SortieId": {
 | 
			
		||||
                if (inventory.CompletedSorties.indexOf(value) == -1) {
 | 
			
		||||
                    inventory.CompletedSorties.push(value);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            case "SeasonChallengeCompletions": {
 | 
			
		||||
@ -525,6 +539,26 @@ export const addMissionInventoryUpdates = async (
 | 
			
		||||
                inventoryChanges.RegularCredits -= value;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            case "InvasionProgress": {
 | 
			
		||||
                for (const clientProgress of value) {
 | 
			
		||||
                    const dbProgress = inventory.QualifyingInvasions.find(x =>
 | 
			
		||||
                        x.invasionId.equals(clientProgress._id.$oid)
 | 
			
		||||
                    );
 | 
			
		||||
                    if (dbProgress) {
 | 
			
		||||
                        dbProgress.Delta += clientProgress.Delta;
 | 
			
		||||
                        dbProgress.AttackerScore += clientProgress.AttackerScore;
 | 
			
		||||
                        dbProgress.DefenderScore += clientProgress.DefenderScore;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        inventory.QualifyingInvasions.push({
 | 
			
		||||
                            invasionId: new Types.ObjectId(clientProgress._id.$oid),
 | 
			
		||||
                            Delta: clientProgress.Delta,
 | 
			
		||||
                            AttackerScore: clientProgress.AttackerScore,
 | 
			
		||||
                            DefenderScore: clientProgress.DefenderScore
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            default:
 | 
			
		||||
                // Equipment XP updates
 | 
			
		||||
                if (equipmentKeys.includes(key as TEquipmentKey)) {
 | 
			
		||||
@ -564,7 +598,8 @@ export const addMissionRewards = async (
 | 
			
		||||
        RegularCredits: creditDrops,
 | 
			
		||||
        VoidTearParticipantsCurrWave: voidTearWave,
 | 
			
		||||
        StrippedItems: strippedItems
 | 
			
		||||
    }: IMissionInventoryUpdateRequest
 | 
			
		||||
    }: IMissionInventoryUpdateRequest,
 | 
			
		||||
    firstCompletion: boolean
 | 
			
		||||
): Promise<AddMissionRewardsReturnType> => {
 | 
			
		||||
    if (!rewardInfo) {
 | 
			
		||||
        //TODO: if there is a case where you can have credits collected during a mission but no rewardInfo, add credits needs to be handled earlier
 | 
			
		||||
@ -578,22 +613,12 @@ export const addMissionRewards = async (
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //TODO: check double reward merging
 | 
			
		||||
    const MissionRewards: IMissionReward[] = getRandomMissionDrops(rewardInfo, wagerTier);
 | 
			
		||||
    const MissionRewards: IMissionReward[] = getRandomMissionDrops(inventory, rewardInfo, wagerTier, firstCompletion);
 | 
			
		||||
    logger.debug("random mission drops:", MissionRewards);
 | 
			
		||||
    const inventoryChanges: IInventoryChanges = {};
 | 
			
		||||
    const AffiliationMods: IAffiliationMods[] = [];
 | 
			
		||||
    let SyndicateXPItemReward;
 | 
			
		||||
 | 
			
		||||
    if (rewardInfo.sortieTag == "Final") {
 | 
			
		||||
        inventory.LastSortieReward = [
 | 
			
		||||
            {
 | 
			
		||||
                SortieId: new Types.ObjectId(rewardInfo.sortieId!.split("_")[1]),
 | 
			
		||||
                StoreItem: MissionRewards[0].StoreItem,
 | 
			
		||||
                Manifest: "/Lotus/Types/Game/MissionDecks/SortieRewards"
 | 
			
		||||
            }
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let missionCompletionCredits = 0;
 | 
			
		||||
    //inventory change is what the client has not rewarded itself, also the client needs to know the credit changes for display
 | 
			
		||||
    if (levelKeyName) {
 | 
			
		||||
@ -695,6 +720,12 @@ export const addMissionRewards = async (
 | 
			
		||||
 | 
			
		||||
    if (strippedItems) {
 | 
			
		||||
        for (const si of strippedItems) {
 | 
			
		||||
            if (si.DropTable == "/Lotus/Types/DropTables/ManInTheWall/MITWGruzzlingArcanesDropTable") {
 | 
			
		||||
                logger.debug(
 | 
			
		||||
                    `rewriting ${si.DropTable} to /Lotus/Types/DropTables/EntratiLabDropTables/DoppelgangerDropTable`
 | 
			
		||||
                );
 | 
			
		||||
                si.DropTable = "/Lotus/Types/DropTables/EntratiLabDropTables/DoppelgangerDropTable";
 | 
			
		||||
            }
 | 
			
		||||
            const droptables = ExportEnemies.droptables[si.DropTable] ?? [];
 | 
			
		||||
            if (si.DROP_MOD) {
 | 
			
		||||
                const modDroptable = droptables.find(x => x.type == "mod");
 | 
			
		||||
@ -951,11 +982,74 @@ function getLevelCreditRewards(node: IRegion): number {
 | 
			
		||||
    //TODO: get dark sektor fixed credit rewards and railjack bonus
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getRandomMissionDrops(RewardInfo: IRewardInfo, tierOverride: number | undefined): IMissionReward[] {
 | 
			
		||||
function getRandomMissionDrops(
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
    RewardInfo: IRewardInfo,
 | 
			
		||||
    tierOverride: number | undefined,
 | 
			
		||||
    firstCompletion: boolean
 | 
			
		||||
): IMissionReward[] {
 | 
			
		||||
    const drops: IMissionReward[] = [];
 | 
			
		||||
    if (RewardInfo.sortieTag == "Final") {
 | 
			
		||||
    if (RewardInfo.sortieTag == "Final" && firstCompletion) {
 | 
			
		||||
        const arr = RewardInfo.sortieId!.split("_");
 | 
			
		||||
        let sortieId = arr[1];
 | 
			
		||||
        if (sortieId == "Lite") {
 | 
			
		||||
            sortieId = arr[2];
 | 
			
		||||
 | 
			
		||||
            const boss = getLiteSortie(idToWeek(sortieId)).Boss;
 | 
			
		||||
            let crystalType = {
 | 
			
		||||
                SORTIE_BOSS_AMAR: "/Lotus/StoreItems/Types/Gameplay/NarmerSorties/ArchonCrystalAmar",
 | 
			
		||||
                SORTIE_BOSS_NIRA: "/Lotus/StoreItems/Types/Gameplay/NarmerSorties/ArchonCrystalNira",
 | 
			
		||||
                SORTIE_BOSS_BOREAL: "/Lotus/StoreItems/Types/Gameplay/NarmerSorties/ArchonCrystalBoreal"
 | 
			
		||||
            }[boss];
 | 
			
		||||
            const attenTag = {
 | 
			
		||||
                SORTIE_BOSS_AMAR: "NarmerSortieAmarCrystalRewards",
 | 
			
		||||
                SORTIE_BOSS_NIRA: "NarmerSortieNiraCrystalRewards",
 | 
			
		||||
                SORTIE_BOSS_BOREAL: "NarmerSortieBorealCrystalRewards"
 | 
			
		||||
            }[boss];
 | 
			
		||||
            const attenIndex = inventory.SortieRewardAttenuation?.findIndex(x => x.Tag == attenTag) ?? -1;
 | 
			
		||||
            const mythicProbability =
 | 
			
		||||
                0.2 + (inventory.SortieRewardAttenuation?.find(x => x.Tag == attenTag)?.Atten ?? 0);
 | 
			
		||||
            if (Math.random() < mythicProbability) {
 | 
			
		||||
                crystalType += "Mythic";
 | 
			
		||||
                if (attenIndex != -1) {
 | 
			
		||||
                    inventory.SortieRewardAttenuation!.splice(attenIndex, 1);
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                if (attenIndex == -1) {
 | 
			
		||||
                    inventory.SortieRewardAttenuation ??= [];
 | 
			
		||||
                    inventory.SortieRewardAttenuation.push({
 | 
			
		||||
                        Tag: attenTag,
 | 
			
		||||
                        Atten: 0.2
 | 
			
		||||
                    });
 | 
			
		||||
                } else {
 | 
			
		||||
                    inventory.SortieRewardAttenuation![attenIndex].Atten += 0.2;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            drops.push({ StoreItem: crystalType, ItemCount: 1 });
 | 
			
		||||
 | 
			
		||||
            const drop = getRandomRewardByChance(
 | 
			
		||||
                ExportRewards["/Lotus/Types/Game/MissionDecks/ArchonSortieRewards"][0]
 | 
			
		||||
            )!;
 | 
			
		||||
            drops.push({ StoreItem: drop.type, ItemCount: drop.itemCount });
 | 
			
		||||
            inventory.LastLiteSortieReward = [
 | 
			
		||||
                {
 | 
			
		||||
                    SortieId: new Types.ObjectId(sortieId),
 | 
			
		||||
                    StoreItem: drop.type,
 | 
			
		||||
                    Manifest: "/Lotus/Types/Game/MissionDecks/ArchonSortieRewards"
 | 
			
		||||
                }
 | 
			
		||||
            ];
 | 
			
		||||
        } else {
 | 
			
		||||
            const drop = getRandomRewardByChance(ExportRewards["/Lotus/Types/Game/MissionDecks/SortieRewards"][0])!;
 | 
			
		||||
            drops.push({ StoreItem: drop.type, ItemCount: drop.itemCount });
 | 
			
		||||
            inventory.LastSortieReward = [
 | 
			
		||||
                {
 | 
			
		||||
                    SortieId: new Types.ObjectId(sortieId),
 | 
			
		||||
                    StoreItem: drop.type,
 | 
			
		||||
                    Manifest: "/Lotus/Types/Game/MissionDecks/SortieRewards"
 | 
			
		||||
                }
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (RewardInfo.periodicMissionTag?.startsWith("HardDaily")) {
 | 
			
		||||
        drops.push({
 | 
			
		||||
@ -965,10 +1059,16 @@ function getRandomMissionDrops(RewardInfo: IRewardInfo, tierOverride: number | u
 | 
			
		||||
    }
 | 
			
		||||
    if (RewardInfo.node in ExportRegions) {
 | 
			
		||||
        const region = ExportRegions[RewardInfo.node];
 | 
			
		||||
        let rewardManifests: string[] =
 | 
			
		||||
            RewardInfo.periodicMissionTag == "EliteAlert" || RewardInfo.periodicMissionTag == "EliteAlertB"
 | 
			
		||||
                ? ["/Lotus/Types/Game/MissionDecks/EliteAlertMissionRewards/EliteAlertMissionRewards"]
 | 
			
		||||
                : region.rewardManifests;
 | 
			
		||||
        let rewardManifests: string[];
 | 
			
		||||
        if (RewardInfo.periodicMissionTag == "EliteAlert" || RewardInfo.periodicMissionTag == "EliteAlertB") {
 | 
			
		||||
            rewardManifests = ["/Lotus/Types/Game/MissionDecks/EliteAlertMissionRewards/EliteAlertMissionRewards"];
 | 
			
		||||
        } else if (RewardInfo.invasionId && region.missionIndex == 0) {
 | 
			
		||||
            // Invasion assassination has Phorid has the boss who should drop Nyx parts
 | 
			
		||||
            // TODO: Check that the invasion faction is indeed FC_INFESTATION once the Invasions in worldState are more dynamic
 | 
			
		||||
            rewardManifests = ["/Lotus/Types/Game/MissionDecks/BossMissionRewards/NyxRewards"];
 | 
			
		||||
        } else {
 | 
			
		||||
            rewardManifests = region.rewardManifests;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let rotations: number[] = [];
 | 
			
		||||
        if (RewardInfo.jobId) {
 | 
			
		||||
 | 
			
		||||
@ -141,7 +141,8 @@ export const handlePurchase = async (
 | 
			
		||||
        inventory,
 | 
			
		||||
        purchaseRequest.PurchaseParams.Quantity,
 | 
			
		||||
        undefined,
 | 
			
		||||
        undefined,
 | 
			
		||||
        false,
 | 
			
		||||
        purchaseRequest.PurchaseParams.UsePremium,
 | 
			
		||||
        seed
 | 
			
		||||
    );
 | 
			
		||||
    combineInventoryChanges(purchaseResponse.InventoryChanges, prePurchaseInventoryChanges);
 | 
			
		||||
@ -331,6 +332,7 @@ export const handleStoreItemAcquisition = async (
 | 
			
		||||
    quantity: number = 1,
 | 
			
		||||
    durability: TRarity = "COMMON",
 | 
			
		||||
    ignorePurchaseQuantity: boolean = false,
 | 
			
		||||
    premiumPurchase: boolean = true,
 | 
			
		||||
    seed?: bigint
 | 
			
		||||
): Promise<IPurchaseResponse> => {
 | 
			
		||||
    let purchaseResponse = {
 | 
			
		||||
@ -352,11 +354,20 @@ export const handleStoreItemAcquisition = async (
 | 
			
		||||
        }
 | 
			
		||||
        switch (storeCategory) {
 | 
			
		||||
            default: {
 | 
			
		||||
                purchaseResponse = { InventoryChanges: await addItem(inventory, internalName, quantity, true, seed) };
 | 
			
		||||
                purchaseResponse = {
 | 
			
		||||
                    InventoryChanges: await addItem(inventory, internalName, quantity, premiumPurchase, seed)
 | 
			
		||||
                };
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            case "Types":
 | 
			
		||||
                purchaseResponse = await handleTypesPurchase(internalName, inventory, quantity, ignorePurchaseQuantity);
 | 
			
		||||
                purchaseResponse = await handleTypesPurchase(
 | 
			
		||||
                    internalName,
 | 
			
		||||
                    inventory,
 | 
			
		||||
                    quantity,
 | 
			
		||||
                    ignorePurchaseQuantity,
 | 
			
		||||
                    premiumPurchase,
 | 
			
		||||
                    seed
 | 
			
		||||
                );
 | 
			
		||||
                break;
 | 
			
		||||
            case "Boosters":
 | 
			
		||||
                purchaseResponse = handleBoostersPurchase(storeItemName, inventory, durability);
 | 
			
		||||
@ -478,13 +489,15 @@ const handleTypesPurchase = async (
 | 
			
		||||
    typesName: string,
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
    quantity: number,
 | 
			
		||||
    ignorePurchaseQuantity: boolean
 | 
			
		||||
    ignorePurchaseQuantity: boolean,
 | 
			
		||||
    premiumPurchase: boolean = true,
 | 
			
		||||
    seed?: bigint
 | 
			
		||||
): Promise<IPurchaseResponse> => {
 | 
			
		||||
    const typeCategory = getStoreItemTypesCategory(typesName);
 | 
			
		||||
    logger.debug(`type category ${typeCategory}`);
 | 
			
		||||
    switch (typeCategory) {
 | 
			
		||||
        default:
 | 
			
		||||
            return { InventoryChanges: await addItem(inventory, typesName, quantity) };
 | 
			
		||||
            return { InventoryChanges: await addItem(inventory, typesName, quantity, premiumPurchase, seed) };
 | 
			
		||||
        case "BoosterPacks":
 | 
			
		||||
            return handleBoosterPackPurchase(typesName, inventory, quantity);
 | 
			
		||||
        case "SlotItems":
 | 
			
		||||
 | 
			
		||||
@ -216,6 +216,27 @@ const handleQuestCompletion = async (
 | 
			
		||||
        setupKahlSyndicate(inventory);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Whispers in the Walls is unlocked once The New + Heart of Deimos are completed.
 | 
			
		||||
    if (
 | 
			
		||||
        (questKey == "/Lotus/Types/Keys/NewWarQuest/NewWarQuestKeyChain" &&
 | 
			
		||||
            inventory.QuestKeys.find(
 | 
			
		||||
                x => x.ItemType == "/Lotus/Types/Keys/InfestedMicroplanetQuest/InfestedMicroplanetQuestKeyChain"
 | 
			
		||||
            )?.Completed) ||
 | 
			
		||||
        (questKey == "/Lotus/Types/Keys/InfestedMicroplanetQuest/InfestedMicroplanetQuestKeyChain" &&
 | 
			
		||||
            inventory.QuestKeys.find(x => x.ItemType == "/Lotus/Types/Keys/NewWarQuest/NewWarQuestKeyChain")?.Completed)
 | 
			
		||||
    ) {
 | 
			
		||||
        await createMessage(inventory.accountOwnerId, [
 | 
			
		||||
            {
 | 
			
		||||
                sndr: "/Lotus/Language/Bosses/Loid",
 | 
			
		||||
                msg: "/Lotus/Language/EntratiLab/EntratiQuest/WiTWQuestRecievedInboxBody",
 | 
			
		||||
                att: ["/Lotus/Types/Keys/EntratiLab/EntratiQuestKeyChain"],
 | 
			
		||||
                sub: "/Lotus/Language/EntratiLab/EntratiQuest/WiTWQuestRecievedInboxTitle",
 | 
			
		||||
                icon: "/Lotus/Interface/Icons/Npcs/Entrati/Loid.png",
 | 
			
		||||
                highPriority: true
 | 
			
		||||
            }
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const questCompletionItems = getQuestCompletionItems(questKey);
 | 
			
		||||
    logger.debug(`quest completion items`, questCompletionItems);
 | 
			
		||||
    if (questCompletionItems) {
 | 
			
		||||
 | 
			
		||||
@ -13,6 +13,8 @@ import { Types } from "mongoose";
 | 
			
		||||
import { isEmptyObject } from "@/src/helpers/general";
 | 
			
		||||
import { logger } from "@/src/utils/logger";
 | 
			
		||||
import { equipmentKeys, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
			
		||||
import { IItemConfig } from "../types/inventoryTypes/commonInventoryTypes";
 | 
			
		||||
import { importCrewMemberId } from "./importService";
 | 
			
		||||
 | 
			
		||||
//TODO: setup default items on account creation or like originally in giveStartingItems.php
 | 
			
		||||
 | 
			
		||||
@ -174,8 +176,8 @@ export const handleInventoryItemConfigChange = async (
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        for (const [configId, config] of Object.entries(itemConfigEntries)) {
 | 
			
		||||
                            if (typeof config !== "boolean") {
 | 
			
		||||
                                inventoryItem.Configs[parseInt(configId)] = config;
 | 
			
		||||
                            if (/^[0-9]+$/.test(configId)) {
 | 
			
		||||
                                inventoryItem.Configs[parseInt(configId)] = config as IItemConfig;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        if ("Favorite" in itemConfigEntries) {
 | 
			
		||||
@ -184,6 +186,26 @@ export const handleInventoryItemConfigChange = async (
 | 
			
		||||
                        if ("IsNew" in itemConfigEntries) {
 | 
			
		||||
                            inventoryItem.IsNew = itemConfigEntries.IsNew;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if ("ItemName" in itemConfigEntries) {
 | 
			
		||||
                            inventoryItem.ItemName = itemConfigEntries.ItemName;
 | 
			
		||||
                        }
 | 
			
		||||
                        if ("RailjackImage" in itemConfigEntries) {
 | 
			
		||||
                            inventoryItem.RailjackImage = itemConfigEntries.RailjackImage;
 | 
			
		||||
                        }
 | 
			
		||||
                        if ("Customization" in itemConfigEntries) {
 | 
			
		||||
                            inventoryItem.Customization = itemConfigEntries.Customization;
 | 
			
		||||
                        }
 | 
			
		||||
                        if ("Weapon" in itemConfigEntries) {
 | 
			
		||||
                            inventoryItem.Weapon = itemConfigEntries.Weapon;
 | 
			
		||||
                        }
 | 
			
		||||
                        if (itemConfigEntries.CrewMembers) {
 | 
			
		||||
                            inventoryItem.CrewMembers = {
 | 
			
		||||
                                SLOT_A: importCrewMemberId(itemConfigEntries.CrewMembers.SLOT_A ?? {}),
 | 
			
		||||
                                SLOT_B: importCrewMemberId(itemConfigEntries.CrewMembers.SLOT_B ?? {}),
 | 
			
		||||
                                SLOT_C: importCrewMemberId(itemConfigEntries.CrewMembers.SLOT_C ?? {})
 | 
			
		||||
                            };
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
                } else {
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,4 @@
 | 
			
		||||
import fs from "fs";
 | 
			
		||||
import path from "path";
 | 
			
		||||
import { repoDir } from "@/src/helpers/pathHelper";
 | 
			
		||||
import { unixTimesInMs } from "@/src/constants/timeConstants";
 | 
			
		||||
import { CRng, mixSeeds } from "@/src/services/rngService";
 | 
			
		||||
import { IMongoDate } from "@/src/types/commonTypes";
 | 
			
		||||
import {
 | 
			
		||||
@ -9,49 +7,73 @@ import {
 | 
			
		||||
    IVendorInfo,
 | 
			
		||||
    IVendorManifestPreprocessed
 | 
			
		||||
} from "@/src/types/vendorTypes";
 | 
			
		||||
import { JSONParse } from "json-with-bigint";
 | 
			
		||||
import { ExportVendors } from "warframe-public-export-plus";
 | 
			
		||||
import { unixTimesInMs } from "../constants/timeConstants";
 | 
			
		||||
 | 
			
		||||
const getVendorManifestJson = (name: string): IRawVendorManifest => {
 | 
			
		||||
    return JSONParse(fs.readFileSync(path.join(repoDir, `static/fixed_responses/getVendorInfo/${name}.json`), "utf-8"));
 | 
			
		||||
};
 | 
			
		||||
import ArchimedeanVendorManifest from "@/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json";
 | 
			
		||||
import DeimosEntratiFragmentVendorProductsManifest from "@/static/fixed_responses/getVendorInfo/DeimosEntratiFragmentVendorProductsManifest.json";
 | 
			
		||||
import DeimosFishmongerVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosFishmongerVendorManifest.json";
 | 
			
		||||
import DeimosHivemindCommisionsManifestFishmonger from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestFishmonger.json";
 | 
			
		||||
import DeimosHivemindCommisionsManifestPetVendor from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestPetVendor.json";
 | 
			
		||||
import DeimosHivemindCommisionsManifestProspector from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestProspector.json";
 | 
			
		||||
import DeimosHivemindCommisionsManifestTokenVendor from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestTokenVendor.json";
 | 
			
		||||
import DeimosHivemindCommisionsManifestWeaponsmith from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestWeaponsmith.json";
 | 
			
		||||
import DeimosHivemindTokenVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosHivemindTokenVendorManifest.json";
 | 
			
		||||
import DeimosPetVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosPetVendorManifest.json";
 | 
			
		||||
import DeimosProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosProspectorVendorManifest.json";
 | 
			
		||||
import DuviriAcrithisVendorManifest from "@/static/fixed_responses/getVendorInfo/DuviriAcrithisVendorManifest.json";
 | 
			
		||||
import EntratiLabsEntratiLabsCommisionsManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabsCommisionsManifest.json";
 | 
			
		||||
import EntratiLabsEntratiLabVendorManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabVendorManifest.json";
 | 
			
		||||
import GuildAdvertisementVendorManifest from "@/static/fixed_responses/getVendorInfo/GuildAdvertisementVendorManifest.json";
 | 
			
		||||
import HubsIronwakeDondaVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsIronwakeDondaVendorManifest.json";
 | 
			
		||||
import HubsRailjackCrewMemberVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsRailjackCrewMemberVendorManifest.json";
 | 
			
		||||
import MaskSalesmanManifest from "@/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json";
 | 
			
		||||
import Nova1999ConquestShopManifest from "@/static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json";
 | 
			
		||||
import OstronFishmongerVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronFishmongerVendorManifest.json";
 | 
			
		||||
import OstronPetVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json";
 | 
			
		||||
import OstronProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronProspectorVendorManifest.json";
 | 
			
		||||
import RadioLegionIntermission12VendorManifest from "@/static/fixed_responses/getVendorInfo/RadioLegionIntermission12VendorManifest.json";
 | 
			
		||||
import SolarisDebtTokenVendorManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorManifest.json";
 | 
			
		||||
import SolarisDebtTokenVendorRepossessionsManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json";
 | 
			
		||||
import SolarisFishmongerVendorManifest from "@/static/fixed_responses/getVendorInfo/SolarisFishmongerVendorManifest.json";
 | 
			
		||||
import SolarisProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/SolarisProspectorVendorManifest.json";
 | 
			
		||||
import TeshinHardModeVendorManifest from "@/static/fixed_responses/getVendorInfo/TeshinHardModeVendorManifest.json";
 | 
			
		||||
import ZarimanCommisionsManifestArchimedean from "@/static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json";
 | 
			
		||||
 | 
			
		||||
const rawVendorManifests: IRawVendorManifest[] = [
 | 
			
		||||
    getVendorManifestJson("ArchimedeanVendorManifest"),
 | 
			
		||||
    getVendorManifestJson("DeimosEntratiFragmentVendorProductsManifest"),
 | 
			
		||||
    getVendorManifestJson("DeimosFishmongerVendorManifest"),
 | 
			
		||||
    getVendorManifestJson("DeimosHivemindCommisionsManifestFishmonger"),
 | 
			
		||||
    getVendorManifestJson("DeimosHivemindCommisionsManifestPetVendor"),
 | 
			
		||||
    getVendorManifestJson("DeimosHivemindCommisionsManifestProspector"),
 | 
			
		||||
    getVendorManifestJson("DeimosHivemindCommisionsManifestTokenVendor"),
 | 
			
		||||
    getVendorManifestJson("DeimosHivemindCommisionsManifestWeaponsmith"),
 | 
			
		||||
    getVendorManifestJson("DeimosHivemindTokenVendorManifest"),
 | 
			
		||||
    getVendorManifestJson("DeimosPetVendorManifest"),
 | 
			
		||||
    getVendorManifestJson("DeimosProspectorVendorManifest"),
 | 
			
		||||
    getVendorManifestJson("DuviriAcrithisVendorManifest"),
 | 
			
		||||
    getVendorManifestJson("EntratiLabsEntratiLabsCommisionsManifest"),
 | 
			
		||||
    getVendorManifestJson("EntratiLabsEntratiLabVendorManifest"),
 | 
			
		||||
    getVendorManifestJson("GuildAdvertisementVendorManifest"), // uses preprocessing
 | 
			
		||||
    getVendorManifestJson("HubsIronwakeDondaVendorManifest"), // uses preprocessing
 | 
			
		||||
    getVendorManifestJson("HubsPerrinSequenceWeaponVendorManifest"),
 | 
			
		||||
    getVendorManifestJson("HubsRailjackCrewMemberVendorManifest"),
 | 
			
		||||
    getVendorManifestJson("MaskSalesmanManifest"),
 | 
			
		||||
    getVendorManifestJson("Nova1999ConquestShopManifest"),
 | 
			
		||||
    getVendorManifestJson("OstronFishmongerVendorManifest"),
 | 
			
		||||
    getVendorManifestJson("OstronPetVendorManifest"),
 | 
			
		||||
    getVendorManifestJson("OstronProspectorVendorManifest"),
 | 
			
		||||
    getVendorManifestJson("RadioLegionIntermission12VendorManifest"),
 | 
			
		||||
    getVendorManifestJson("SolarisDebtTokenVendorManifest"),
 | 
			
		||||
    getVendorManifestJson("SolarisDebtTokenVendorRepossessionsManifest"),
 | 
			
		||||
    getVendorManifestJson("SolarisFishmongerVendorManifest"),
 | 
			
		||||
    getVendorManifestJson("SolarisProspectorVendorManifest"),
 | 
			
		||||
    getVendorManifestJson("TeshinHardModeVendorManifest"), // uses preprocessing
 | 
			
		||||
    getVendorManifestJson("ZarimanCommisionsManifestArchimedean")
 | 
			
		||||
    ArchimedeanVendorManifest,
 | 
			
		||||
    DeimosEntratiFragmentVendorProductsManifest,
 | 
			
		||||
    DeimosFishmongerVendorManifest,
 | 
			
		||||
    DeimosHivemindCommisionsManifestFishmonger,
 | 
			
		||||
    DeimosHivemindCommisionsManifestPetVendor,
 | 
			
		||||
    DeimosHivemindCommisionsManifestProspector,
 | 
			
		||||
    DeimosHivemindCommisionsManifestTokenVendor,
 | 
			
		||||
    DeimosHivemindCommisionsManifestWeaponsmith,
 | 
			
		||||
    DeimosHivemindTokenVendorManifest,
 | 
			
		||||
    DeimosPetVendorManifest,
 | 
			
		||||
    DeimosProspectorVendorManifest,
 | 
			
		||||
    DuviriAcrithisVendorManifest,
 | 
			
		||||
    EntratiLabsEntratiLabsCommisionsManifest,
 | 
			
		||||
    EntratiLabsEntratiLabVendorManifest,
 | 
			
		||||
    GuildAdvertisementVendorManifest, // uses preprocessing
 | 
			
		||||
    HubsIronwakeDondaVendorManifest, // uses preprocessing
 | 
			
		||||
    HubsRailjackCrewMemberVendorManifest,
 | 
			
		||||
    MaskSalesmanManifest,
 | 
			
		||||
    Nova1999ConquestShopManifest,
 | 
			
		||||
    OstronFishmongerVendorManifest,
 | 
			
		||||
    OstronPetVendorManifest,
 | 
			
		||||
    OstronProspectorVendorManifest,
 | 
			
		||||
    RadioLegionIntermission12VendorManifest,
 | 
			
		||||
    SolarisDebtTokenVendorManifest,
 | 
			
		||||
    SolarisDebtTokenVendorRepossessionsManifest,
 | 
			
		||||
    SolarisFishmongerVendorManifest,
 | 
			
		||||
    SolarisProspectorVendorManifest,
 | 
			
		||||
    TeshinHardModeVendorManifest, // uses preprocessing
 | 
			
		||||
    ZarimanCommisionsManifestArchimedean
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
interface IGeneratableVendorInfo extends Omit<IVendorInfo, "ItemManifest" | "Expiry"> {
 | 
			
		||||
    cycleDuration?: number;
 | 
			
		||||
    cycleStart: number;
 | 
			
		||||
    cycleDuration: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const generatableVendors: IGeneratableVendorInfo[] = [
 | 
			
		||||
@ -62,6 +84,16 @@ const generatableVendors: IGeneratableVendorInfo[] = [
 | 
			
		||||
        RandomSeedType: "VRST_WEAPON",
 | 
			
		||||
        RequiredGoalTag: "",
 | 
			
		||||
        WeaponUpgradeValueAttenuationExponent: 2.25,
 | 
			
		||||
        cycleStart: 1740960000_000,
 | 
			
		||||
        cycleDuration: 4 * unixTimesInMs.day
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        _id: { $oid: "60ad3b6ec96976e97d227e19" },
 | 
			
		||||
        TypeName: "/Lotus/Types/Game/VendorManifests/Hubs/PerrinSequenceWeaponVendorManifest",
 | 
			
		||||
        PropertyTextHash: "34F8CF1DFF745F0D67433A5EF0A03E70",
 | 
			
		||||
        RandomSeedType: "VRST_WEAPON",
 | 
			
		||||
        WeaponUpgradeValueAttenuationExponent: 2.25,
 | 
			
		||||
        cycleStart: 1744934400_000,
 | 
			
		||||
        cycleDuration: 4 * unixTimesInMs.day
 | 
			
		||||
    }
 | 
			
		||||
    // {
 | 
			
		||||
@ -124,7 +156,7 @@ const preprocessVendorManifest = (originalManifest: IRawVendorManifest): IVendor
 | 
			
		||||
const refreshExpiry = (expiry: IMongoDate): number => {
 | 
			
		||||
    const period = parseInt(expiry.$date.$numberLong);
 | 
			
		||||
    if (Date.now() >= period) {
 | 
			
		||||
        const epoch = 1734307200 * 1000; // Monday (for weekly schedules)
 | 
			
		||||
        const epoch = 1734307200_000; // Monday (for weekly schedules)
 | 
			
		||||
        const iteration = Math.trunc((Date.now() - epoch) / period);
 | 
			
		||||
        const start = epoch + iteration * period;
 | 
			
		||||
        const end = start + period;
 | 
			
		||||
@ -135,11 +167,11 @@ const refreshExpiry = (expiry: IMongoDate): number => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifestPreprocessed => {
 | 
			
		||||
    const EPOCH = 1740960000 * 1000; // Monday; aligns with coda weapons 8 day cycle.
 | 
			
		||||
    const EPOCH = vendorInfo.cycleStart;
 | 
			
		||||
    const manifest = ExportVendors[vendorInfo.TypeName];
 | 
			
		||||
    let binThisCycle;
 | 
			
		||||
    if (manifest.isOneBinPerCycle) {
 | 
			
		||||
        const cycleDuration = vendorInfo.cycleDuration!; // manifest.items[0].durationHours! * 3600_000;
 | 
			
		||||
        const cycleDuration = vendorInfo.cycleDuration; // manifest.items[0].durationHours! * 3600_000;
 | 
			
		||||
        const cycleIndex = Math.trunc((Date.now() - EPOCH) / cycleDuration);
 | 
			
		||||
        binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now.
 | 
			
		||||
    }
 | 
			
		||||
@ -150,7 +182,7 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
 | 
			
		||||
        if (manifest.isOneBinPerCycle && rawItem.bin != binThisCycle) {
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        const cycleDuration = vendorInfo.cycleDuration!; // rawItem.durationHours! * 3600_000;
 | 
			
		||||
        const cycleDuration = vendorInfo.cycleDuration; // rawItem.durationHours! * 3600_000;
 | 
			
		||||
        const cycleIndex = Math.trunc((Date.now() - EPOCH) / cycleDuration);
 | 
			
		||||
        const cycleStart = EPOCH + cycleIndex * cycleDuration;
 | 
			
		||||
        const cycleEnd = cycleStart + cycleDuration;
 | 
			
		||||
@ -181,10 +213,11 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
 | 
			
		||||
            items.push(item);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    delete vendorInfo.cycleDuration;
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
    const { cycleStart, cycleDuration, ...clientVendorInfo } = vendorInfo;
 | 
			
		||||
    return {
 | 
			
		||||
        VendorInfo: {
 | 
			
		||||
            ...vendorInfo,
 | 
			
		||||
            ...clientVendorInfo,
 | 
			
		||||
            ItemManifest: items,
 | 
			
		||||
            Expiry: { $date: { $numberLong: soonestOfferExpiry.toString() } }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,5 @@
 | 
			
		||||
import { Stats, TStatsDatabaseDocument } from "@/src/models/statsModel";
 | 
			
		||||
import {
 | 
			
		||||
    IEnemy,
 | 
			
		||||
    IStatsAdd,
 | 
			
		||||
    IStatsMax,
 | 
			
		||||
    IStatsSet,
 | 
			
		||||
@ -137,16 +136,21 @@ export const updateStats = async (accountOwnerId: string, payload: IStatsUpdate)
 | 
			
		||||
                        case "HEADSHOT":
 | 
			
		||||
                        case "KILL_ASSIST": {
 | 
			
		||||
                            playerStats.Enemies ??= [];
 | 
			
		||||
                            const enemyStatKey = {
 | 
			
		||||
                            const enemyStatKey = (
 | 
			
		||||
                                {
 | 
			
		||||
                                    KILL_ENEMY: "kills",
 | 
			
		||||
                                    EXECUTE_ENEMY: "executions",
 | 
			
		||||
                                    HEADSHOT: "headshots",
 | 
			
		||||
                                    KILL_ASSIST: "assists"
 | 
			
		||||
                            }[category] as "kills" | "executions" | "headshots" | "assists";
 | 
			
		||||
                                } as const
 | 
			
		||||
                            )[category];
 | 
			
		||||
 | 
			
		||||
                            for (const [type, count] of Object.entries(data as IUploadEntry)) {
 | 
			
		||||
                                const enemy = playerStats.Enemies.find(element => element.type === type);
 | 
			
		||||
                                if (enemy) {
 | 
			
		||||
                                let enemy = playerStats.Enemies.find(element => element.type === type);
 | 
			
		||||
                                if (!enemy) {
 | 
			
		||||
                                    enemy = { type: type };
 | 
			
		||||
                                    playerStats.Enemies.push(enemy);
 | 
			
		||||
                                }
 | 
			
		||||
                                if (category === "KILL_ENEMY") {
 | 
			
		||||
                                    enemy.kills ??= 0;
 | 
			
		||||
                                    const captureCount = (actionData as IStatsAdd)["CAPTURE_ENEMY"]?.[type];
 | 
			
		||||
@ -161,11 +165,6 @@ export const updateStats = async (accountOwnerId: string, payload: IStatsUpdate)
 | 
			
		||||
                                    enemy[enemyStatKey] ??= 0;
 | 
			
		||||
                                    enemy[enemyStatKey] += count;
 | 
			
		||||
                                }
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    const newEnemy: IEnemy = { type: type };
 | 
			
		||||
                                    newEnemy[enemyStatKey] = count;
 | 
			
		||||
                                    playerStats.Enemies.push(newEnemy);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,14 @@ import { unixTimesInMs } from "@/src/constants/timeConstants";
 | 
			
		||||
import { config } from "@/src/services/configService";
 | 
			
		||||
import { CRng } from "@/src/services/rngService";
 | 
			
		||||
import { eMissionType, ExportNightwave, ExportRegions } from "warframe-public-export-plus";
 | 
			
		||||
import { ICalendarDay, ICalendarSeason, ISeasonChallenge, ISortie, IWorldState } from "../types/worldStateTypes";
 | 
			
		||||
import {
 | 
			
		||||
    ICalendarDay,
 | 
			
		||||
    ICalendarSeason,
 | 
			
		||||
    ILiteSortie,
 | 
			
		||||
    ISeasonChallenge,
 | 
			
		||||
    ISortie,
 | 
			
		||||
    IWorldState
 | 
			
		||||
} from "../types/worldStateTypes";
 | 
			
		||||
 | 
			
		||||
const sortieBosses = [
 | 
			
		||||
    "SORTIE_BOSS_HYENA",
 | 
			
		||||
@ -348,6 +355,34 @@ const getSeasonWeeklyHardChallenge = (week: number, id: number): ISeasonChalleng
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const pushWeeklyActs = (worldState: IWorldState, week: number): void => {
 | 
			
		||||
    const weekStart = EPOCH + week * 604800000;
 | 
			
		||||
    const weekEnd = weekStart + 604800000;
 | 
			
		||||
 | 
			
		||||
    worldState.SeasonInfo.ActiveChallenges.push(getSeasonWeeklyChallenge(week, 0));
 | 
			
		||||
    worldState.SeasonInfo.ActiveChallenges.push(getSeasonWeeklyChallenge(week, 1));
 | 
			
		||||
    worldState.SeasonInfo.ActiveChallenges.push(getSeasonWeeklyHardChallenge(week, 2));
 | 
			
		||||
    worldState.SeasonInfo.ActiveChallenges.push(getSeasonWeeklyHardChallenge(week, 3));
 | 
			
		||||
    worldState.SeasonInfo.ActiveChallenges.push({
 | 
			
		||||
        _id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 0).toString().padStart(8, "0") },
 | 
			
		||||
        Activation: { $date: { $numberLong: weekStart.toString() } },
 | 
			
		||||
        Expiry: { $date: { $numberLong: weekEnd.toString() } },
 | 
			
		||||
        Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentCompleteMissions" + (week - 12)
 | 
			
		||||
    });
 | 
			
		||||
    worldState.SeasonInfo.ActiveChallenges.push({
 | 
			
		||||
        _id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 1).toString().padStart(8, "0") },
 | 
			
		||||
        Activation: { $date: { $numberLong: weekStart.toString() } },
 | 
			
		||||
        Expiry: { $date: { $numberLong: weekEnd.toString() } },
 | 
			
		||||
        Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEximus" + (week - 12)
 | 
			
		||||
    });
 | 
			
		||||
    worldState.SeasonInfo.ActiveChallenges.push({
 | 
			
		||||
        _id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 2).toString().padStart(8, "0") },
 | 
			
		||||
        Activation: { $date: { $numberLong: weekStart.toString() } },
 | 
			
		||||
        Expiry: { $date: { $numberLong: weekEnd.toString() } },
 | 
			
		||||
        Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEnemies" + (week - 12)
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const birthdays: number[] = [
 | 
			
		||||
    1, // Kaya
 | 
			
		||||
    45, // Lettie
 | 
			
		||||
@ -604,29 +639,10 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
 | 
			
		||||
    if (isBeforeNextExpectedWorldStateRefresh(EPOCH + (day + 1) * 86400000)) {
 | 
			
		||||
        worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(day + 1));
 | 
			
		||||
    }
 | 
			
		||||
    worldState.SeasonInfo.ActiveChallenges.push(getSeasonWeeklyChallenge(week, 0));
 | 
			
		||||
    worldState.SeasonInfo.ActiveChallenges.push(getSeasonWeeklyChallenge(week, 1));
 | 
			
		||||
    worldState.SeasonInfo.ActiveChallenges.push(getSeasonWeeklyHardChallenge(week, 2));
 | 
			
		||||
    worldState.SeasonInfo.ActiveChallenges.push(getSeasonWeeklyHardChallenge(week, 3));
 | 
			
		||||
    worldState.SeasonInfo.ActiveChallenges.push({
 | 
			
		||||
        _id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 0).toString().padStart(8, "0") },
 | 
			
		||||
        Activation: { $date: { $numberLong: weekStart.toString() } },
 | 
			
		||||
        Expiry: { $date: { $numberLong: weekEnd.toString() } },
 | 
			
		||||
        Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentCompleteMissions" + (week - 12)
 | 
			
		||||
    });
 | 
			
		||||
    worldState.SeasonInfo.ActiveChallenges.push({
 | 
			
		||||
        _id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 1).toString().padStart(8, "0") },
 | 
			
		||||
        Activation: { $date: { $numberLong: weekStart.toString() } },
 | 
			
		||||
        Expiry: { $date: { $numberLong: weekEnd.toString() } },
 | 
			
		||||
        Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEximus" + (week - 12)
 | 
			
		||||
    });
 | 
			
		||||
    worldState.SeasonInfo.ActiveChallenges.push({
 | 
			
		||||
        _id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 2).toString().padStart(8, "0") },
 | 
			
		||||
        Activation: { $date: { $numberLong: weekStart.toString() } },
 | 
			
		||||
        Expiry: { $date: { $numberLong: weekEnd.toString() } },
 | 
			
		||||
        Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEnemies" + (week - 12)
 | 
			
		||||
    });
 | 
			
		||||
    // TODO: Provide upcoming weekly acts if rollover is imminent
 | 
			
		||||
    pushWeeklyActs(worldState, week);
 | 
			
		||||
    if (isBeforeNextExpectedWorldStateRefresh(weekEnd)) {
 | 
			
		||||
        pushWeeklyActs(worldState, week + 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Elite Sanctuary Onslaught cycling every week
 | 
			
		||||
    worldState.NodeOverrides.find(x => x.Node == "SolNode802")!.Seed = week; // unfaithful
 | 
			
		||||
@ -941,65 +957,9 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
 | 
			
		||||
    pushSortieIfRelevant(worldState.Sorties, day);
 | 
			
		||||
 | 
			
		||||
    // Archon Hunt cycling every week
 | 
			
		||||
    // TODO: Handle imminent rollover
 | 
			
		||||
    {
 | 
			
		||||
        const boss = ["SORTIE_BOSS_AMAR", "SORTIE_BOSS_NIRA", "SORTIE_BOSS_BOREAL"][week % 3];
 | 
			
		||||
        const showdownNode = ["SolNode99", "SolNode53", "SolNode24"][week % 3];
 | 
			
		||||
        const systemIndex = [3, 4, 2][week % 3]; // Mars, Jupiter, Earth
 | 
			
		||||
 | 
			
		||||
        const nodes: string[] = [];
 | 
			
		||||
        for (const [key, value] of Object.entries(ExportRegions)) {
 | 
			
		||||
            if (
 | 
			
		||||
                value.systemIndex === systemIndex &&
 | 
			
		||||
                value.factionIndex !== undefined &&
 | 
			
		||||
                value.factionIndex < 2 &&
 | 
			
		||||
                value.name.indexOf("Archwing") == -1 &&
 | 
			
		||||
                value.missionIndex != 0 // Exclude MT_ASSASSINATION
 | 
			
		||||
            ) {
 | 
			
		||||
                nodes.push(key);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const rng = new CRng(week);
 | 
			
		||||
        const firstNodeIndex = rng.randomInt(0, nodes.length - 1);
 | 
			
		||||
        const firstNode = nodes[firstNodeIndex];
 | 
			
		||||
        nodes.splice(firstNodeIndex, 1);
 | 
			
		||||
        worldState.LiteSorties.push({
 | 
			
		||||
            _id: {
 | 
			
		||||
                $oid: Math.trunc(weekStart / 1000).toString(16) + "5e23a244740a190c"
 | 
			
		||||
            },
 | 
			
		||||
            Activation: { $date: { $numberLong: weekStart.toString() } },
 | 
			
		||||
            Expiry: { $date: { $numberLong: weekEnd.toString() } },
 | 
			
		||||
            Reward: "/Lotus/Types/Game/MissionDecks/ArchonSortieRewards",
 | 
			
		||||
            Seed: week,
 | 
			
		||||
            Boss: boss,
 | 
			
		||||
            Missions: [
 | 
			
		||||
                {
 | 
			
		||||
                    missionType: rng.randomElement([
 | 
			
		||||
                        "MT_INTEL",
 | 
			
		||||
                        "MT_MOBILE_DEFENSE",
 | 
			
		||||
                        "MT_EXTERMINATION",
 | 
			
		||||
                        "MT_SABOTAGE",
 | 
			
		||||
                        "MT_RESCUE"
 | 
			
		||||
                    ]),
 | 
			
		||||
                    node: firstNode
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    missionType: rng.randomElement([
 | 
			
		||||
                        "MT_DEFENSE",
 | 
			
		||||
                        "MT_TERRITORY",
 | 
			
		||||
                        "MT_ARTIFACT",
 | 
			
		||||
                        "MT_EXCAVATE",
 | 
			
		||||
                        "MT_SURVIVAL"
 | 
			
		||||
                    ]),
 | 
			
		||||
                    node: rng.randomElement(nodes)
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    missionType: "MT_ASSASSINATION",
 | 
			
		||||
                    node: showdownNode
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        });
 | 
			
		||||
    worldState.LiteSorties.push(getLiteSortie(week));
 | 
			
		||||
    if (isBeforeNextExpectedWorldStateRefresh(weekEnd)) {
 | 
			
		||||
        worldState.LiteSorties.push(getLiteSortie(week + 1));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Circuit choices cycling every week
 | 
			
		||||
@ -1071,3 +1031,70 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
 | 
			
		||||
 | 
			
		||||
    return worldState;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const idToWeek = (id: string): number => {
 | 
			
		||||
    return (parseInt(id.substring(0, 8), 16) * 1000 - EPOCH) / 604800000;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getLiteSortie = (week: number): ILiteSortie => {
 | 
			
		||||
    const boss = (["SORTIE_BOSS_AMAR", "SORTIE_BOSS_NIRA", "SORTIE_BOSS_BOREAL"] as const)[week % 3];
 | 
			
		||||
    const showdownNode = ["SolNode99", "SolNode53", "SolNode24"][week % 3];
 | 
			
		||||
    const systemIndex = [3, 4, 2][week % 3]; // Mars, Jupiter, Earth
 | 
			
		||||
 | 
			
		||||
    const nodes: string[] = [];
 | 
			
		||||
    for (const [key, value] of Object.entries(ExportRegions)) {
 | 
			
		||||
        if (
 | 
			
		||||
            value.systemIndex === systemIndex &&
 | 
			
		||||
            value.factionIndex !== undefined &&
 | 
			
		||||
            value.factionIndex < 2 &&
 | 
			
		||||
            value.name.indexOf("Archwing") == -1 &&
 | 
			
		||||
            value.missionIndex != 0 // Exclude MT_ASSASSINATION
 | 
			
		||||
        ) {
 | 
			
		||||
            nodes.push(key);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const rng = new CRng(week);
 | 
			
		||||
    const firstNodeIndex = rng.randomInt(0, nodes.length - 1);
 | 
			
		||||
    const firstNode = nodes[firstNodeIndex];
 | 
			
		||||
    nodes.splice(firstNodeIndex, 1);
 | 
			
		||||
 | 
			
		||||
    const weekStart = EPOCH + week * 604800000;
 | 
			
		||||
    const weekEnd = weekStart + 604800000;
 | 
			
		||||
    return {
 | 
			
		||||
        _id: {
 | 
			
		||||
            $oid: Math.trunc(weekStart / 1000).toString(16) + "5e23a244740a190c"
 | 
			
		||||
        },
 | 
			
		||||
        Activation: { $date: { $numberLong: weekStart.toString() } },
 | 
			
		||||
        Expiry: { $date: { $numberLong: weekEnd.toString() } },
 | 
			
		||||
        Reward: "/Lotus/Types/Game/MissionDecks/ArchonSortieRewards",
 | 
			
		||||
        Seed: week,
 | 
			
		||||
        Boss: boss,
 | 
			
		||||
        Missions: [
 | 
			
		||||
            {
 | 
			
		||||
                missionType: rng.randomElement([
 | 
			
		||||
                    "MT_INTEL",
 | 
			
		||||
                    "MT_MOBILE_DEFENSE",
 | 
			
		||||
                    "MT_EXTERMINATION",
 | 
			
		||||
                    "MT_SABOTAGE",
 | 
			
		||||
                    "MT_RESCUE"
 | 
			
		||||
                ]),
 | 
			
		||||
                node: firstNode
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                missionType: rng.randomElement([
 | 
			
		||||
                    "MT_DEFENSE",
 | 
			
		||||
                    "MT_TERRITORY",
 | 
			
		||||
                    "MT_ARTIFACT",
 | 
			
		||||
                    "MT_EXCAVATE",
 | 
			
		||||
                    "MT_SURVIVAL"
 | 
			
		||||
                ]),
 | 
			
		||||
                node: rng.randomElement(nodes)
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                missionType: "MT_ASSASSINATION",
 | 
			
		||||
                node: showdownNode
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -103,12 +103,12 @@ export interface IGuildMemberDatabase {
 | 
			
		||||
    ShipDecorationsContributed?: ITypeCount[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface IFriendInfo {
 | 
			
		||||
export interface IFriendInfo {
 | 
			
		||||
    _id: IOid;
 | 
			
		||||
    DisplayName?: string;
 | 
			
		||||
    PlatformNames?: string[];
 | 
			
		||||
    PlatformAccountId?: string;
 | 
			
		||||
    Status: number;
 | 
			
		||||
    Status?: number;
 | 
			
		||||
    ActiveAvatarImageType?: string;
 | 
			
		||||
    LastLogin?: IMongoDate;
 | 
			
		||||
    PlayerLevel?: number;
 | 
			
		||||
 | 
			
		||||
@ -49,6 +49,8 @@ export interface IInventoryDatabase
 | 
			
		||||
            | "PersonalTechProjects"
 | 
			
		||||
            | "LastSortieReward"
 | 
			
		||||
            | "LastLiteSortieReward"
 | 
			
		||||
            | "CrewMembers"
 | 
			
		||||
            | "QualifyingInvasions"
 | 
			
		||||
            | TEquipmentKey
 | 
			
		||||
        >,
 | 
			
		||||
        InventoryDatabaseEquipment {
 | 
			
		||||
@ -83,6 +85,8 @@ export interface IInventoryDatabase
 | 
			
		||||
    PersonalTechProjects: IPersonalTechProjectDatabase[];
 | 
			
		||||
    LastSortieReward?: ILastSortieRewardDatabase[];
 | 
			
		||||
    LastLiteSortieReward?: ILastSortieRewardDatabase[];
 | 
			
		||||
    CrewMembers: ICrewMemberDatabase[];
 | 
			
		||||
    QualifyingInvasions: IInvasionProgressDatabase[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IQuestKeyDatabase {
 | 
			
		||||
@ -246,9 +250,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
 | 
			
		||||
    Guide?: number;
 | 
			
		||||
    Moderator?: boolean;
 | 
			
		||||
    Partner?: boolean;
 | 
			
		||||
    Accolades?: {
 | 
			
		||||
        Heirloom?: boolean;
 | 
			
		||||
    };
 | 
			
		||||
    Accolades?: IAccolades;
 | 
			
		||||
    Counselor?: boolean;
 | 
			
		||||
    Upgrades: IUpgradeClient[];
 | 
			
		||||
    EquippedGear: string[];
 | 
			
		||||
@ -270,7 +272,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
 | 
			
		||||
    SentientSpawnChanceBoosters: ISentientSpawnChanceBoosters;
 | 
			
		||||
    SupportedSyndicate?: string;
 | 
			
		||||
    Affiliations: IAffiliation[];
 | 
			
		||||
    QualifyingInvasions: any[];
 | 
			
		||||
    QualifyingInvasions: IInvasionProgressClient[];
 | 
			
		||||
    FactionScores: number[];
 | 
			
		||||
    ArchwingEnabled?: boolean;
 | 
			
		||||
    PendingSpectreLoadouts?: ISpectreLoadout[];
 | 
			
		||||
@ -283,6 +285,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
 | 
			
		||||
    CompletedSorties: string[];
 | 
			
		||||
    LastSortieReward?: ILastSortieRewardClient[];
 | 
			
		||||
    LastLiteSortieReward?: ILastSortieRewardClient[];
 | 
			
		||||
    SortieRewardAttenuation?: ISortieRewardAttenuation[];
 | 
			
		||||
    Drones: IDroneClient[];
 | 
			
		||||
    StepSequencers: IStepSequencer[];
 | 
			
		||||
    ActiveAvatarImageType: string;
 | 
			
		||||
@ -324,7 +327,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
 | 
			
		||||
    InfestedFoundry?: IInfestedFoundryClient;
 | 
			
		||||
    BlessingCooldown?: IMongoDate;
 | 
			
		||||
    CrewShipRawSalvage: ITypeCount[];
 | 
			
		||||
    CrewMembers: ICrewMember[];
 | 
			
		||||
    CrewMembers: ICrewMemberClient[];
 | 
			
		||||
    LotusCustomization: ILotusCustomization;
 | 
			
		||||
    UseAdultOperatorLoadout?: boolean;
 | 
			
		||||
    NemesisAbandonedRewards: string[];
 | 
			
		||||
@ -461,32 +464,36 @@ export interface ICompletedJob {
 | 
			
		||||
    StageCompletions: number[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ICrewMember {
 | 
			
		||||
export interface ICrewMemberSkill {
 | 
			
		||||
    Assigned: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ICrewMemberSkillEfficiency {
 | 
			
		||||
    PILOTING: ICrewMemberSkill;
 | 
			
		||||
    GUNNERY: ICrewMemberSkill;
 | 
			
		||||
    ENGINEERING: ICrewMemberSkill;
 | 
			
		||||
    COMBAT: ICrewMemberSkill;
 | 
			
		||||
    SURVIVABILITY: ICrewMemberSkill;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ICrewMemberClient {
 | 
			
		||||
    ItemType: string;
 | 
			
		||||
    NemesisFingerprint: number;
 | 
			
		||||
    Seed: number;
 | 
			
		||||
    HireDate: IMongoDate;
 | 
			
		||||
    AssignedRole: number;
 | 
			
		||||
    SkillEfficiency: ISkillEfficiency;
 | 
			
		||||
    NemesisFingerprint: bigint;
 | 
			
		||||
    Seed: bigint;
 | 
			
		||||
    AssignedRole?: number;
 | 
			
		||||
    SkillEfficiency: ICrewMemberSkillEfficiency;
 | 
			
		||||
    WeaponConfigIdx: number;
 | 
			
		||||
    WeaponId: IOid;
 | 
			
		||||
    XP: number;
 | 
			
		||||
    PowersuitType: string;
 | 
			
		||||
    Configs: IItemConfig[];
 | 
			
		||||
    SecondInCommand: boolean;
 | 
			
		||||
    SecondInCommand: boolean; // on call
 | 
			
		||||
    ItemId: IOid;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ISkillEfficiency {
 | 
			
		||||
    PILOTING: ICombat;
 | 
			
		||||
    GUNNERY: ICombat;
 | 
			
		||||
    ENGINEERING: ICombat;
 | 
			
		||||
    COMBAT: ICombat;
 | 
			
		||||
    SURVIVABILITY: ICombat;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ICombat {
 | 
			
		||||
    Assigned: number;
 | 
			
		||||
export interface ICrewMemberDatabase extends Omit<ICrewMemberClient, "WeaponId" | "ItemId"> {
 | 
			
		||||
    WeaponId: Types.ObjectId;
 | 
			
		||||
    _id: Types.ObjectId;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum InventorySlot {
 | 
			
		||||
@ -532,12 +539,12 @@ export interface ICrewShipMembersDatabase {
 | 
			
		||||
 | 
			
		||||
export interface ICrewShipMemberClient {
 | 
			
		||||
    ItemId?: IOid;
 | 
			
		||||
    NemesisFingerprint?: number;
 | 
			
		||||
    NemesisFingerprint?: number | bigint;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ICrewShipMemberDatabase {
 | 
			
		||||
    ItemId?: Types.ObjectId;
 | 
			
		||||
    NemesisFingerprint?: number;
 | 
			
		||||
    NemesisFingerprint?: bigint;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ICrewShipCustomization {
 | 
			
		||||
@ -562,17 +569,18 @@ export type IMiscItem = ITypeCount;
 | 
			
		||||
 | 
			
		||||
// inventory.CrewShips[0].Weapon
 | 
			
		||||
export interface ICrewShipWeapon {
 | 
			
		||||
    PILOT: ICrewShipPilotWeapon;
 | 
			
		||||
    PORT_GUNS: ICrewShipPortGuns;
 | 
			
		||||
    PILOT?: ICrewShipWeaponEmplacements;
 | 
			
		||||
    PORT_GUNS?: ICrewShipWeaponEmplacements;
 | 
			
		||||
    STARBOARD_GUNS?: ICrewShipWeaponEmplacements;
 | 
			
		||||
    ARTILLERY?: ICrewShipWeaponEmplacements;
 | 
			
		||||
    SCANNER?: ICrewShipWeaponEmplacements;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ICrewShipPilotWeapon {
 | 
			
		||||
    PRIMARY_A: IEquipmentSelection;
 | 
			
		||||
    SECONDARY_A: IEquipmentSelection;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ICrewShipPortGuns {
 | 
			
		||||
    PRIMARY_A: IEquipmentSelection;
 | 
			
		||||
export interface ICrewShipWeaponEmplacements {
 | 
			
		||||
    PRIMARY_A?: IEquipmentSelection;
 | 
			
		||||
    PRIMARY_B?: IEquipmentSelection;
 | 
			
		||||
    SECONDARY_A?: IEquipmentSelection;
 | 
			
		||||
    SECONDARY_B?: IEquipmentSelection;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IDiscoveredMarker {
 | 
			
		||||
@ -668,6 +676,17 @@ export interface IInvasionChainProgress {
 | 
			
		||||
    count: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IInvasionProgressClient {
 | 
			
		||||
    _id: IOid;
 | 
			
		||||
    Delta: number;
 | 
			
		||||
    AttackerScore: number;
 | 
			
		||||
    DefenderScore: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IInvasionProgressDatabase extends Omit<IInvasionProgressClient, "_id"> {
 | 
			
		||||
    invasionId: Types.ObjectId;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IKubrowPetEggClient {
 | 
			
		||||
    ItemType: string;
 | 
			
		||||
    ExpirationDate: IMongoDate; // seems to be set to 7 days ahead @ 0 UTC
 | 
			
		||||
@ -739,6 +758,11 @@ export interface ILastSortieRewardDatabase extends Omit<ILastSortieRewardClient,
 | 
			
		||||
    SortieId: Types.ObjectId;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ISortieRewardAttenuation {
 | 
			
		||||
    Tag: string;
 | 
			
		||||
    Atten: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ILibraryDailyTaskInfo {
 | 
			
		||||
    EnemyTypes: string[];
 | 
			
		||||
    EnemyLocTag: string;
 | 
			
		||||
@ -888,6 +912,10 @@ export interface IPendingRecipeClient
 | 
			
		||||
    CompletionDate: IMongoDate;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IAccolades {
 | 
			
		||||
    Heirloom?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IPendingTrade {
 | 
			
		||||
    State: number;
 | 
			
		||||
    SelfReady: boolean;
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
import { Types } from "mongoose";
 | 
			
		||||
 | 
			
		||||
export interface IAccountAndLoginResponseCommons {
 | 
			
		||||
    DisplayName: string;
 | 
			
		||||
    CountryCode: string;
 | 
			
		||||
@ -56,3 +58,8 @@ export interface IGroup {
 | 
			
		||||
    experiment: string;
 | 
			
		||||
    experimentGroup: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IIgnore {
 | 
			
		||||
    ignorer: Types.ObjectId;
 | 
			
		||||
    ignoree: Types.ObjectId;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,8 @@ import {
 | 
			
		||||
    INemesisClient,
 | 
			
		||||
    ITypeCount,
 | 
			
		||||
    IRecentVendorPurchaseClient,
 | 
			
		||||
    TEquipmentKey
 | 
			
		||||
    TEquipmentKey,
 | 
			
		||||
    ICrewMemberClient
 | 
			
		||||
} from "./inventoryTypes/inventoryTypes";
 | 
			
		||||
 | 
			
		||||
export interface IPurchaseRequest {
 | 
			
		||||
@ -47,6 +48,7 @@ export type IInventoryChanges = {
 | 
			
		||||
    Nemesis?: Partial<INemesisClient>;
 | 
			
		||||
    NewVendorPurchase?: IRecentVendorPurchaseClient; // >= 38.5.0
 | 
			
		||||
    RecentVendorPurchases?: IRecentVendorPurchaseClient; // < 38.5.0
 | 
			
		||||
    CrewMembers?: ICrewMemberClient[];
 | 
			
		||||
} & Record<
 | 
			
		||||
        Exclude<
 | 
			
		||||
            string,
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,8 @@ import {
 | 
			
		||||
    ICollectibleEntry,
 | 
			
		||||
    IDiscoveredMarker,
 | 
			
		||||
    ILockedWeaponGroupClient,
 | 
			
		||||
    ILoadOutPresets
 | 
			
		||||
    ILoadOutPresets,
 | 
			
		||||
    IInvasionProgressClient
 | 
			
		||||
} from "./inventoryTypes/inventoryTypes";
 | 
			
		||||
import { IGroup } from "./loginTypes";
 | 
			
		||||
 | 
			
		||||
@ -123,12 +124,15 @@ export type IMissionInventoryUpdateRequest = {
 | 
			
		||||
    };
 | 
			
		||||
    wagerTier?: number; // the index
 | 
			
		||||
    creditsFee?: number; // the index
 | 
			
		||||
    InvasionProgress?: IInvasionProgressClient[];
 | 
			
		||||
} & {
 | 
			
		||||
    [K in TEquipmentKey]?: IEquipmentClient[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export interface IRewardInfo {
 | 
			
		||||
    node: string;
 | 
			
		||||
    invasionId?: string;
 | 
			
		||||
    invasionAllyFaction?: "FC_GRINEER" | "FC_CORPUS";
 | 
			
		||||
    sortieId?: string;
 | 
			
		||||
    sortieTag?: string;
 | 
			
		||||
    sortiePrereqs?: string[];
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,13 @@
 | 
			
		||||
import { IOid } from "@/src/types/commonTypes";
 | 
			
		||||
import { IItemConfig, IOperatorConfigClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
			
		||||
import { Types } from "mongoose";
 | 
			
		||||
import { ILoadoutConfigClient } from "./inventoryTypes/inventoryTypes";
 | 
			
		||||
import {
 | 
			
		||||
    ICrewShipCustomization,
 | 
			
		||||
    ICrewShipMembersClient,
 | 
			
		||||
    ICrewShipWeapon,
 | 
			
		||||
    IFlavourItem,
 | 
			
		||||
    ILoadoutConfigClient
 | 
			
		||||
} from "./inventoryTypes/inventoryTypes";
 | 
			
		||||
 | 
			
		||||
export interface ISaveLoadoutRequest {
 | 
			
		||||
    LoadOuts: ILoadoutClient;
 | 
			
		||||
@ -51,7 +57,16 @@ export interface IItemEntry {
 | 
			
		||||
 | 
			
		||||
export type IConfigEntry = {
 | 
			
		||||
    [configId in "0" | "1" | "2" | "3" | "4" | "5"]: IItemConfig;
 | 
			
		||||
} & { Favorite?: boolean; IsNew?: boolean };
 | 
			
		||||
} & {
 | 
			
		||||
    Favorite?: boolean;
 | 
			
		||||
    IsNew?: boolean;
 | 
			
		||||
    // Railjack
 | 
			
		||||
    ItemName?: string;
 | 
			
		||||
    RailjackImage?: IFlavourItem;
 | 
			
		||||
    Customization?: ICrewShipCustomization;
 | 
			
		||||
    Weapon?: ICrewShipWeapon;
 | 
			
		||||
    CrewMembers?: ICrewShipMembersClient;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type ILoadoutClient = Omit<ILoadoutDatabase, "_id" | "loadoutOwnerId">;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,7 @@ export interface IVendorInfo {
 | 
			
		||||
    TypeName: string;
 | 
			
		||||
    ItemManifest: IItemManifest[];
 | 
			
		||||
    PropertyTextHash?: string;
 | 
			
		||||
    RandomSeedType?: "VRST_WEAPON";
 | 
			
		||||
    RandomSeedType?: string;
 | 
			
		||||
    RequiredGoalTag?: string;
 | 
			
		||||
    WeaponUpgradeValueAttenuationExponent?: number;
 | 
			
		||||
    Expiry: IMongoDate; // Either a date in the distant future or a period in milliseconds for preprocessing.
 | 
			
		||||
 | 
			
		||||
@ -103,7 +103,7 @@ export interface ILiteSortie {
 | 
			
		||||
    Expiry: IMongoDate;
 | 
			
		||||
    Reward: "/Lotus/Types/Game/MissionDecks/ArchonSortieRewards";
 | 
			
		||||
    Seed: number;
 | 
			
		||||
    Boss: string; // "SORTIE_BOSS_AMAR" | "SORTIE_BOSS_NIRA" | "SORTIE_BOSS_BOREAL"
 | 
			
		||||
    Boss: "SORTIE_BOSS_AMAR" | "SORTIE_BOSS_NIRA" | "SORTIE_BOSS_BOREAL";
 | 
			
		||||
    Missions: {
 | 
			
		||||
        missionType: string;
 | 
			
		||||
        node: string;
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
// Misnomer: We have concurrency, not parallelism - oh well!
 | 
			
		||||
export const parallelForeach = async <T>(data: T[], op: (datum: T) => Promise<void>): Promise<void> => {
 | 
			
		||||
    const promises: Promise<void>[] = [];
 | 
			
		||||
    for (const datum of data) {
 | 
			
		||||
 | 
			
		||||
@ -1092,5 +1092,11 @@
 | 
			
		||||
  "/Lotus/Types/Game/CrewShip/GrineerDestroyer/GrineerDestroyerAvatar",
 | 
			
		||||
  "/Lotus/Types/LevelObjects/Zariman/ZarLootCrateUltraRare",
 | 
			
		||||
  "/Lotus/Objects/DomestikDrone/GrineerOceanDomestikDroneMover",
 | 
			
		||||
  "/Lotus/Types/Gameplay/1999Wf/Extermination/SupplyCrate"
 | 
			
		||||
  "/Lotus/Types/Gameplay/1999Wf/Extermination/SupplyCrate",
 | 
			
		||||
  "/Lotus/Objects/Orokin/Props/CollectibleSeriesOne",
 | 
			
		||||
  "/Lotus/Types/LevelObjects/InfestedPumpkinCocoonLamp",
 | 
			
		||||
  "/Lotus/Types/LevelObjects/InfestedPumpkinCocoonLampLarge",
 | 
			
		||||
  "/Lotus/Types/LevelObjects/InfestedPumpkinCocoonLampSmall",
 | 
			
		||||
  "/Lotus/Types/LevelObjects/InfestedPumpkinExplosiveTotem",
 | 
			
		||||
  "/Lotus/Types/Enemies/Orokin/OrokinMoaBipedAvatar"
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@ -1,133 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "VendorInfo": {
 | 
			
		||||
    "_id": {
 | 
			
		||||
      "$oid": "60ad3b6ec96976e97d227e19"
 | 
			
		||||
    },
 | 
			
		||||
    "TypeName": "/Lotus/Types/Game/VendorManifests/Hubs/PerrinSequenceWeaponVendorManifest",
 | 
			
		||||
    "ItemManifest": [
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/BoardExec/Primary/CrpBEFerrox/CrpBEFerrox",
 | 
			
		||||
        "ItemPrices": [
 | 
			
		||||
          {
 | 
			
		||||
            "ItemCount": 40,
 | 
			
		||||
            "ItemType": "/Lotus/Types/Items/MiscItems/GranumBucks",
 | 
			
		||||
            "ProductCategory": "MiscItems"
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": {
 | 
			
		||||
          "$date": {
 | 
			
		||||
            "$numberLong": "9999999000000"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "PurchaseQuantityLimit": 1,
 | 
			
		||||
        "AllowMultipurchase": false,
 | 
			
		||||
        "LocTagRandSeed": 4383829823946960400,
 | 
			
		||||
        "Id": {
 | 
			
		||||
          "$oid": "66fd60b20ba592c4c95e9488"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Melee/CrpBriefcaseScythe/CrpBriefcaseScythe",
 | 
			
		||||
        "ItemPrices": [
 | 
			
		||||
          {
 | 
			
		||||
            "ItemCount": 40,
 | 
			
		||||
            "ItemType": "/Lotus/Types/Items/MiscItems/GranumBucks",
 | 
			
		||||
            "ProductCategory": "MiscItems"
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": {
 | 
			
		||||
          "$date": {
 | 
			
		||||
            "$numberLong": "9999999000000"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "PurchaseQuantityLimit": 1,
 | 
			
		||||
        "AllowMultipurchase": false,
 | 
			
		||||
        "LocTagRandSeed": 7952272124248276000,
 | 
			
		||||
        "Id": {
 | 
			
		||||
          "$oid": "66fd60b20ba592c4c95e9489"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Melee/CrpBriefcase2HKatana/CrpBriefcase2HKatana",
 | 
			
		||||
        "ItemPrices": [
 | 
			
		||||
          {
 | 
			
		||||
            "ItemCount": 40,
 | 
			
		||||
            "ItemType": "/Lotus/Types/Items/MiscItems/GranumBucks",
 | 
			
		||||
            "ProductCategory": "MiscItems"
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": {
 | 
			
		||||
          "$date": {
 | 
			
		||||
            "$numberLong": "9999999000000"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "PurchaseQuantityLimit": 1,
 | 
			
		||||
        "AllowMultipurchase": false,
 | 
			
		||||
        "LocTagRandSeed": 465952672558014140,
 | 
			
		||||
        "Id": {
 | 
			
		||||
          "$oid": "66fd60b20ba592c4c95e948a"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Swords/CrpBigSlash/CrpBigSlash",
 | 
			
		||||
        "ItemPrices": [
 | 
			
		||||
          {
 | 
			
		||||
            "ItemCount": 40,
 | 
			
		||||
            "ItemType": "/Lotus/Types/Items/MiscItems/GranumBucks",
 | 
			
		||||
            "ProductCategory": "MiscItems"
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": {
 | 
			
		||||
          "$date": {
 | 
			
		||||
            "$numberLong": "9999999000000"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "PurchaseQuantityLimit": 1,
 | 
			
		||||
        "AllowMultipurchase": false,
 | 
			
		||||
        "LocTagRandSeed": 8342430883077507000,
 | 
			
		||||
        "Id": {
 | 
			
		||||
          "$oid": "66fd60b20ba592c4c95e948b"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Melee/ShieldAndSword/CrpHammerShield/CrpHammerShield",
 | 
			
		||||
        "ItemPrices": [
 | 
			
		||||
          {
 | 
			
		||||
            "ItemCount": 40,
 | 
			
		||||
            "ItemType": "/Lotus/Types/Items/MiscItems/GranumBucks",
 | 
			
		||||
            "ProductCategory": "MiscItems"
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": {
 | 
			
		||||
          "$date": {
 | 
			
		||||
            "$numberLong": "9999999000000"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "PurchaseQuantityLimit": 1,
 | 
			
		||||
        "AllowMultipurchase": false,
 | 
			
		||||
        "LocTagRandSeed": 7441523153174502000,
 | 
			
		||||
        "Id": {
 | 
			
		||||
          "$oid": "66fd60b20ba592c4c95e948c"
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "PropertyTextHash": "34F8CF1DFF745F0D67433A5EF0A03E70",
 | 
			
		||||
    "RandomSeedType": "VRST_WEAPON",
 | 
			
		||||
    "WeaponUpgradeValueAttenuationExponent": 2.25,
 | 
			
		||||
    "Expiry": {
 | 
			
		||||
      "$date": {
 | 
			
		||||
        "$numberLong": "9999999000000"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -487,6 +487,7 @@ function updateInventory() {
 | 
			
		||||
                            a.href = "#";
 | 
			
		||||
                            a.onclick = function (event) {
 | 
			
		||||
                                event.preventDefault();
 | 
			
		||||
                                document.getElementById(category + "-list").removeChild(tr);
 | 
			
		||||
                                disposeOfGear(category, item.ItemId.$oid);
 | 
			
		||||
                            };
 | 
			
		||||
                            a.title = loc("code_remove");
 | 
			
		||||
@ -683,6 +684,7 @@ function updateInventory() {
 | 
			
		||||
                                a.href = "#";
 | 
			
		||||
                                a.onclick = function (event) {
 | 
			
		||||
                                    event.preventDefault();
 | 
			
		||||
                                    document.getElementById("riven-list").removeChild(tr);
 | 
			
		||||
                                    disposeOfGear("Upgrades", item.ItemId.$oid);
 | 
			
		||||
                                };
 | 
			
		||||
                                a.title = loc("code_remove");
 | 
			
		||||
@ -723,6 +725,7 @@ function updateInventory() {
 | 
			
		||||
                        a.href = "#";
 | 
			
		||||
                        a.onclick = function (event) {
 | 
			
		||||
                            event.preventDefault();
 | 
			
		||||
                            document.getElementById("mods-list").removeChild(tr);
 | 
			
		||||
                            disposeOfGear("Upgrades", item.ItemId.$oid);
 | 
			
		||||
                        };
 | 
			
		||||
                        a.title = loc("code_remove");
 | 
			
		||||
@ -765,6 +768,7 @@ function updateInventory() {
 | 
			
		||||
                            a.href = "#";
 | 
			
		||||
                            a.onclick = function (event) {
 | 
			
		||||
                                event.preventDefault();
 | 
			
		||||
                                document.getElementById("mods-list").removeChild(tr);
 | 
			
		||||
                                disposeOfItems("Upgrades", item.ItemType, item.ItemCount);
 | 
			
		||||
                            };
 | 
			
		||||
                            a.title = loc("code_remove");
 | 
			
		||||
@ -1097,8 +1101,6 @@ function disposeOfGear(category, oid) {
 | 
			
		||||
            url: "/api/sell.php?" + window.authz,
 | 
			
		||||
            contentType: "text/plain",
 | 
			
		||||
            data: JSON.stringify(data)
 | 
			
		||||
        }).done(function () {
 | 
			
		||||
            updateInventory();
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
@ -1120,8 +1122,6 @@ function disposeOfItems(category, type, count) {
 | 
			
		||||
            url: "/api/sell.php?" + window.authz,
 | 
			
		||||
            contentType: "text/plain",
 | 
			
		||||
            data: JSON.stringify(data)
 | 
			
		||||
        }).done(function () {
 | 
			
		||||
            updateInventory();
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
@ -1260,6 +1260,8 @@ function doAcquireMod() {
 | 
			
		||||
        $("#mod-to-acquire").addClass("is-invalid").focus();
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    const count = parseInt($("#mod-count").val());
 | 
			
		||||
    if (count != 0) {
 | 
			
		||||
        revalidateAuthz(() => {
 | 
			
		||||
            $.post({
 | 
			
		||||
                url: "/custom/addItems?" + window.authz,
 | 
			
		||||
@ -1267,14 +1269,19 @@ function doAcquireMod() {
 | 
			
		||||
                data: JSON.stringify([
 | 
			
		||||
                    {
 | 
			
		||||
                        ItemType: uniqueName,
 | 
			
		||||
                    ItemCount: parseInt($("#mod-count").val())
 | 
			
		||||
                        ItemCount: count
 | 
			
		||||
                    }
 | 
			
		||||
                ])
 | 
			
		||||
            }).done(function () {
 | 
			
		||||
            document.getElementById("mod-to-acquire").value = "";
 | 
			
		||||
                if (count > 0) {
 | 
			
		||||
                    toast(loc("code_succAdded"));
 | 
			
		||||
                } else {
 | 
			
		||||
                    toast(loc("code_succRemoved"));
 | 
			
		||||
                }
 | 
			
		||||
                updateInventory();
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const uiConfigs = [...$("#server-settings input[id]")].map(x => x.id);
 | 
			
		||||
@ -1315,7 +1322,7 @@ single.getRoute("/webui/cheats").on("beforeload", function () {
 | 
			
		||||
    interval = setInterval(() => {
 | 
			
		||||
        if (window.authz) {
 | 
			
		||||
            clearInterval(interval);
 | 
			
		||||
            fetch("/custom/config?" + window.authz).then(res => {
 | 
			
		||||
            fetch("/custom/config?" + window.authz).then(async res => {
 | 
			
		||||
                if (res.status == 200) {
 | 
			
		||||
                    $("#server-settings-no-perms").addClass("d-none");
 | 
			
		||||
                    $("#server-settings").removeClass("d-none");
 | 
			
		||||
@ -1334,10 +1341,18 @@ single.getRoute("/webui/cheats").on("beforeload", function () {
 | 
			
		||||
                            }
 | 
			
		||||
                        })
 | 
			
		||||
                    );
 | 
			
		||||
                } else {
 | 
			
		||||
                    if ((await res.text()) == "Log-in expired") {
 | 
			
		||||
                        revalidateAuthz(() => {
 | 
			
		||||
                            if (single.getCurrentPath() == "/webui/cheats") {
 | 
			
		||||
                                single.loadRoute("/webui/cheats");
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
                    } else {
 | 
			
		||||
                        $("#server-settings-no-perms").removeClass("d-none");
 | 
			
		||||
                        $("#server-settings").addClass("d-none");
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }, 10);
 | 
			
		||||
 | 
			
		||||
@ -138,7 +138,7 @@ dict = {
 | 
			
		||||
    cheats_noArgonCrystalDecay: `Argon-Kristalle verschwinden niemals`,
 | 
			
		||||
    cheats_noMasteryRankUpCooldown: `Keine Wartezeit beim Meisterschaftsrangaufstieg`,
 | 
			
		||||
    cheats_noVendorPurchaseLimits: `Keine Kaufbeschränkungen bei Händlern`,
 | 
			
		||||
    cheats_noDeathMarks: `[UNTRANSLATED] No Death Marks`,
 | 
			
		||||
    cheats_noDeathMarks: `Keine Todesmarkierungen`,
 | 
			
		||||
    cheats_noKimCooldowns: `Keine Wartezeit bei KIM`,
 | 
			
		||||
    cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`,
 | 
			
		||||
    cheats_noResourceExtractorDronesDamage: `Kein Schaden für Ressourcen-Extraktor-Drohnen`,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user