chore(webui): update to Spanish translation #1772
@ -4,7 +4,7 @@
 | 
				
			|||||||
  "description": "WF Emulator",
 | 
					  "description": "WF Emulator",
 | 
				
			||||||
  "main": "index.ts",
 | 
					  "main": "index.ts",
 | 
				
			||||||
  "scripts": {
 | 
					  "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 ",
 | 
					    "dev": "ts-node-dev --openssl-legacy-provider -r tsconfig-paths/register src/index.ts ",
 | 
				
			||||||
    "build": "tsc --incremental --sourceMap && ncp static/webui build/static/webui",
 | 
					    "build": "tsc --incremental --sourceMap && ncp static/webui build/static/webui",
 | 
				
			||||||
    "verify": "tsgo --noEmit",
 | 
					    "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 { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
					import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
				
			||||||
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
 | 
					import { toOid } from "@/src/helpers/inventoryHelpers";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IClaimCompletedRecipeRequest {
 | 
					interface IClaimCompletedRecipeRequest {
 | 
				
			||||||
    RecipeIds: IOid[];
 | 
					    RecipeIds: IOid[];
 | 
				
			||||||
@ -80,6 +81,7 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
 | 
				
			|||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        logger.debug("Claiming Recipe", { recipe, pendingRecipe });
 | 
					        logger.debug("Claiming Recipe", { recipe, pendingRecipe });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let BrandedSuits: undefined | IOid[];
 | 
				
			||||||
        if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
 | 
					        if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
 | 
				
			||||||
            inventory.PendingSpectreLoadouts ??= [];
 | 
					            inventory.PendingSpectreLoadouts ??= [];
 | 
				
			||||||
            inventory.SpectreLoadouts ??= [];
 | 
					            inventory.SpectreLoadouts ??= [];
 | 
				
			||||||
@ -104,9 +106,10 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
 | 
				
			|||||||
                inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)),
 | 
					                inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)),
 | 
				
			||||||
                1
 | 
					                1
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
 | 
					            BrandedSuits = [toOid(pendingRecipe.SuitToUnbrand!)];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let InventoryChanges = {};
 | 
					        let InventoryChanges: IInventoryChanges = {};
 | 
				
			||||||
        if (recipe.consumeOnUse) {
 | 
					        if (recipe.consumeOnUse) {
 | 
				
			||||||
            addRecipes(inventory, [
 | 
					            addRecipes(inventory, [
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
@ -134,6 +137,6 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
 | 
				
			|||||||
            };
 | 
					            };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        await inventory.save();
 | 
					        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 { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
import { getRandomInt } from "@/src/services/rngService";
 | 
					import { getRandomInt } from "@/src/services/rngService";
 | 
				
			||||||
import { IFingerprintStat } from "@/src/helpers/rivenHelper";
 | 
					import { IFingerprintStat } from "@/src/helpers/rivenHelper";
 | 
				
			||||||
 | 
					import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const crewShipIdentifySalvageController: RequestHandler = async (req, res) => {
 | 
					export const crewShipIdentifySalvageController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
@ -42,7 +43,9 @@ export const crewShipIdentifySalvageController: RequestHandler = async (req, res
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        const meta = ExportRailjackWeapons[payload.ItemType];
 | 
					        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 upgradeMeta = ExportUpgrades[upgradeType];
 | 
				
			||||||
            const buffs: IFingerprintStat[] = [];
 | 
					            const buffs: IFingerprintStat[] = [];
 | 
				
			||||||
            for (const buff of upgradeMeta.upgradeEntries!) {
 | 
					            for (const buff of upgradeMeta.upgradeEntries!) {
 | 
				
			||||||
@ -51,13 +54,22 @@ export const crewShipIdentifySalvageController: RequestHandler = async (req, res
 | 
				
			|||||||
                    Value: Math.trunc(Math.random() * 0x40000000)
 | 
					                    Value: Math.trunc(Math.random() * 0x40000000)
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        addEquipment(inventory, "CrewShipSalvagedWeapons", payload.ItemType, undefined, inventoryChanges, {
 | 
					            defaultOverwrites = {
 | 
				
			||||||
                UpgradeType: upgradeType,
 | 
					                UpgradeType: upgradeType,
 | 
				
			||||||
                UpgradeFingerprint: JSON.stringify({
 | 
					                UpgradeFingerprint: JSON.stringify({
 | 
				
			||||||
                    compat: payload.ItemType,
 | 
					                    compat: payload.ItemType,
 | 
				
			||||||
                    buffs
 | 
					                    buffs
 | 
				
			||||||
                } satisfies IInnateDamageFingerprint)
 | 
					                } satisfies IInnateDamageFingerprint)
 | 
				
			||||||
        });
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        addEquipment(
 | 
				
			||||||
 | 
					            inventory,
 | 
				
			||||||
 | 
					            "CrewShipSalvagedWeapons",
 | 
				
			||||||
 | 
					            payload.ItemType,
 | 
				
			||||||
 | 
					            undefined,
 | 
				
			||||||
 | 
					            inventoryChanges,
 | 
				
			||||||
 | 
					            defaultOverwrites
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    inventoryChanges.CrewShipRawSalvage = [
 | 
					    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";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getIgnoredUsersController: RequestHandler = (_req, res) => {
 | 
					export const getIgnoredUsersController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    res.writeHead(200, {
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
        "Content-Type": "text/html",
 | 
					    const ignores = await Ignore.find({ ignorer: accountId });
 | 
				
			||||||
        "Content-Length": "3"
 | 
					    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([
 | 
					    res.json({ IgnoredUsers: ignoredUsers });
 | 
				
			||||||
            0x7b, 0x22, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x22, 0x3a, 0x38, 0x33, 0x30, 0x34, 0x30, 0x37, 0x37, 0x32, 0x32,
 | 
					 | 
				
			||||||
            0x34, 0x30, 0x32, 0x32, 0x32, 0x36, 0x31, 0x35, 0x30, 0x31, 0x7d
 | 
					 | 
				
			||||||
        ])
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
export { getIgnoredUsersController };
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -97,6 +97,19 @@ export const guildTechController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            res.end();
 | 
					            res.end();
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            const recipe = ExportDojoRecipes.research[data.RecipeType];
 | 
					            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 =
 | 
					            const techProject =
 | 
				
			||||||
                inventory.PersonalTechProjects[
 | 
					                inventory.PersonalTechProjects[
 | 
				
			||||||
                    inventory.PersonalTechProjects.push({
 | 
					                    inventory.PersonalTechProjects.push({
 | 
				
			||||||
@ -347,6 +360,22 @@ export const guildTechController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        res.json({
 | 
					        res.json({
 | 
				
			||||||
            inventoryChanges: inventoryChanges
 | 
					            inventoryChanges: inventoryChanges
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					    } else if (data.Action == "InstantFinish") {
 | 
				
			||||||
 | 
					        if (data.TechProductCategory != "CrewShipWeapons" && data.TechProductCategory != "CrewShipWeaponSkins") {
 | 
				
			||||||
 | 
					            throw new Error(`unexpected TechProductCategory: ${data.TechProductCategory}`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const inventoryChanges = finishComponentRepair(inventory, data.TechProductCategory, data.CategoryItemId!);
 | 
				
			||||||
 | 
					        inventoryChanges.MiscItems = [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ItemType: "/Lotus/Types/Items/MiscItems/InstantSalvageRepairItem",
 | 
				
			||||||
 | 
					                ItemCount: -1
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					        addMiscItems(inventory, inventoryChanges.MiscItems);
 | 
				
			||||||
 | 
					        await inventory.save();
 | 
				
			||||||
 | 
					        res.json({
 | 
				
			||||||
 | 
					            inventoryChanges: inventoryChanges
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
 | 
					        logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
 | 
				
			||||||
        throw new Error(`unhandled guildTech request`);
 | 
					        throw new Error(`unhandled guildTech request`);
 | 
				
			||||||
@ -359,7 +388,7 @@ type TGuildTechRequest =
 | 
				
			|||||||
    | IGuildTechContributeRequest;
 | 
					    | IGuildTechContributeRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IGuildTechBasicRequest {
 | 
					interface IGuildTechBasicRequest {
 | 
				
			||||||
    Action: "Start" | "Fabricate" | "Pause" | "Unpause" | "Cancel" | "Rush";
 | 
					    Action: "Start" | "Fabricate" | "Pause" | "Unpause" | "Cancel" | "Rush" | "InstantFinish";
 | 
				
			||||||
    Mode: "Guild" | "Personal";
 | 
					    Mode: "Guild" | "Personal";
 | 
				
			||||||
    RecipeType: string;
 | 
					    RecipeType: string;
 | 
				
			||||||
    TechProductCategory?: string;
 | 
					    TechProductCategory?: string;
 | 
				
			||||||
@ -380,6 +409,12 @@ interface IGuildTechContributeRequest {
 | 
				
			|||||||
    VaultMiscItems: IMiscItem[];
 | 
					    VaultMiscItems: IMiscItem[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getSalvageCategory = (
 | 
				
			||||||
 | 
					    category: "CrewShipWeapons" | "CrewShipWeaponSkins"
 | 
				
			||||||
 | 
					): "CrewShipSalvagedWeapons" | "CrewShipSalvagedWeaponSkins" => {
 | 
				
			||||||
 | 
					    return category == "CrewShipWeapons" ? "CrewShipSalvagedWeapons" : "CrewShipSalvagedWeaponSkins";
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const claimSalvagedComponent = (inventory: TInventoryDatabaseDocument, itemId: string): IInventoryChanges => {
 | 
					const claimSalvagedComponent = (inventory: TInventoryDatabaseDocument, itemId: string): IInventoryChanges => {
 | 
				
			||||||
    // delete personal tech project
 | 
					    // delete personal tech project
 | 
				
			||||||
    const personalTechProjectIndex = inventory.PersonalTechProjects.findIndex(x => x.CategoryItemId?.equals(itemId));
 | 
					    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);
 | 
					    inventory.PersonalTechProjects.splice(personalTechProjectIndex, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const category = personalTechProject.ProductCategory! as "CrewShipWeapons" | "CrewShipWeaponSkins";
 | 
					    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
 | 
					    // find salved part & delete it
 | 
				
			||||||
    const salvageIndex = inventory[salvageCategory].findIndex(x => x._id.equals(itemId));
 | 
					    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);
 | 
					    inventory[salvageCategory].splice(salvageIndex, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // add final item
 | 
					    // add final item
 | 
				
			||||||
 | 
				
			|||||||
@ -53,6 +53,9 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
 | 
				
			|||||||
    logger.debug("mission report:", missionReport);
 | 
					    logger.debug("mission report:", missionReport);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const inventory = await getInventory(accountId);
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
 | 
					    const firstCompletion = missionReport.SortieId
 | 
				
			||||||
 | 
					        ? inventory.CompletedSorties.indexOf(missionReport.SortieId) == -1
 | 
				
			||||||
 | 
					        : false;
 | 
				
			||||||
    const inventoryUpdates = await addMissionInventoryUpdates(inventory, missionReport);
 | 
					    const inventoryUpdates = await addMissionInventoryUpdates(inventory, missionReport);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (
 | 
					    if (
 | 
				
			||||||
@ -69,7 +72,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { MissionRewards, inventoryChanges, credits, AffiliationMods, SyndicateXPItemReward } =
 | 
					    const { MissionRewards, inventoryChanges, credits, AffiliationMods, SyndicateXPItemReward } =
 | 
				
			||||||
        await addMissionRewards(inventory, missionReport);
 | 
					        await addMissionRewards(inventory, missionReport, firstCompletion);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    const inventoryResponse = await getInventoryResponse(inventory, true);
 | 
					    const inventoryResponse = await getInventoryResponse(inventory, true);
 | 
				
			||||||
 | 
				
			|||||||
@ -17,7 +17,7 @@ import { getDefaultUpgrades } from "@/src/services/itemDataService";
 | 
				
			|||||||
import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
 | 
					import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
 | 
				
			||||||
import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
					import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
				
			||||||
import { getRandomInt } from "@/src/services/rngService";
 | 
					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";
 | 
					import { Status } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IModularCraftRequest {
 | 
					interface IModularCraftRequest {
 | 
				
			||||||
@ -34,10 +34,8 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
 | 
				
			|||||||
    const category = modularWeaponTypes[data.WeaponType];
 | 
					    const category = modularWeaponTypes[data.WeaponType];
 | 
				
			||||||
    const inventory = await getInventory(accountId);
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const defaultUpgrades = getDefaultUpgrades(data.Parts);
 | 
					    let defaultUpgrades: IDefaultUpgrade[] | undefined;
 | 
				
			||||||
    const defaultOverwrites: Partial<IEquipmentDatabase> = {
 | 
					    const defaultOverwrites: Partial<IEquipmentDatabase> = {};
 | 
				
			||||||
        Configs: applyDefaultUpgrades(inventory, defaultUpgrades)
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    const inventoryChanges: IInventoryChanges = {};
 | 
					    const inventoryChanges: IInventoryChanges = {};
 | 
				
			||||||
    if (category == "KubrowPets") {
 | 
					    if (category == "KubrowPets") {
 | 
				
			||||||
        const traits = {
 | 
					        const traits = {
 | 
				
			||||||
@ -129,10 +127,17 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
 | 
				
			|||||||
        // Only save mutagen & antigen in the ModularParts.
 | 
					        // Only save mutagen & antigen in the ModularParts.
 | 
				
			||||||
        defaultOverwrites.ModularParts = [data.Parts[1], data.Parts[2]];
 | 
					        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);
 | 
					            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);
 | 
					    addEquipment(inventory, category, data.WeaponType, data.Parts, inventoryChanges, defaultOverwrites);
 | 
				
			||||||
    combineInventoryChanges(inventoryChanges, occupySlot(inventory, productCategoryToInventoryBin(category)!, false));
 | 
					    combineInventoryChanges(inventoryChanges, occupySlot(inventory, productCategoryToInventoryBin(category)!, false));
 | 
				
			||||||
    if (defaultUpgrades) {
 | 
					    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 { RequestHandler } from "express";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					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 { Inbox } from "@/src/models/inboxModel";
 | 
				
			||||||
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
 | 
					import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
 | 
				
			||||||
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
 | 
					import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
 | 
				
			||||||
@ -23,6 +23,8 @@ export const deleteAccountController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    await Promise.all([
 | 
					    await Promise.all([
 | 
				
			||||||
        Account.deleteOne({ _id: accountId }),
 | 
					        Account.deleteOne({ _id: accountId }),
 | 
				
			||||||
        GuildMember.deleteMany({ accountId: accountId }),
 | 
					        GuildMember.deleteMany({ accountId: accountId }),
 | 
				
			||||||
 | 
					        Ignore.deleteMany({ ignorer: accountId }),
 | 
				
			||||||
 | 
					        Ignore.deleteMany({ ignoree: accountId }),
 | 
				
			||||||
        Inbox.deleteMany({ ownerId: accountId }),
 | 
					        Inbox.deleteMany({ ownerId: accountId }),
 | 
				
			||||||
        Inventory.deleteOne({ accountOwnerId: accountId }),
 | 
					        Inventory.deleteOne({ accountOwnerId: accountId }),
 | 
				
			||||||
        Leaderboard.deleteMany({ ownerId: accountId }),
 | 
					        Leaderboard.deleteMany({ ownerId: accountId }),
 | 
				
			||||||
 | 
				
			|||||||
@ -39,10 +39,9 @@ import {
 | 
				
			|||||||
    ILoreFragmentScan,
 | 
					    ILoreFragmentScan,
 | 
				
			||||||
    IEvolutionProgress,
 | 
					    IEvolutionProgress,
 | 
				
			||||||
    IEndlessXpProgress,
 | 
					    IEndlessXpProgress,
 | 
				
			||||||
    ICrewShipPortGuns,
 | 
					 | 
				
			||||||
    ICrewShipCustomization,
 | 
					    ICrewShipCustomization,
 | 
				
			||||||
    ICrewShipWeapon,
 | 
					    ICrewShipWeapon,
 | 
				
			||||||
    ICrewShipPilotWeapon,
 | 
					    ICrewShipWeaponEmplacements,
 | 
				
			||||||
    IShipExterior,
 | 
					    IShipExterior,
 | 
				
			||||||
    IHelminthFoodRecord,
 | 
					    IHelminthFoodRecord,
 | 
				
			||||||
    ICrewShipMembersDatabase,
 | 
					    ICrewShipMembersDatabase,
 | 
				
			||||||
@ -88,7 +87,15 @@ import {
 | 
				
			|||||||
    IPersonalTechProjectDatabase,
 | 
					    IPersonalTechProjectDatabase,
 | 
				
			||||||
    IPersonalTechProjectClient,
 | 
					    IPersonalTechProjectClient,
 | 
				
			||||||
    ILastSortieRewardDatabase,
 | 
					    ILastSortieRewardDatabase,
 | 
				
			||||||
    ILastSortieRewardClient
 | 
					    ILastSortieRewardClient,
 | 
				
			||||||
 | 
					    ICrewMemberSkill,
 | 
				
			||||||
 | 
					    ICrewMemberSkillEfficiency,
 | 
				
			||||||
 | 
					    ICrewMemberDatabase,
 | 
				
			||||||
 | 
					    ICrewMemberClient,
 | 
				
			||||||
 | 
					    ISortieRewardAttenuation,
 | 
				
			||||||
 | 
					    IInvasionProgressDatabase,
 | 
				
			||||||
 | 
					    IInvasionProgressClient,
 | 
				
			||||||
 | 
					    IAccolades
 | 
				
			||||||
} from "../../types/inventoryTypes/inventoryTypes";
 | 
					} from "../../types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
import { IOid } from "../../types/commonTypes";
 | 
					import { IOid } from "../../types/commonTypes";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
@ -102,7 +109,7 @@ import {
 | 
				
			|||||||
    IEquipmentClient
 | 
					    IEquipmentClient
 | 
				
			||||||
} from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
					} from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
				
			||||||
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
 | 
					import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
 | 
				
			||||||
import { EquipmentSelectionSchema } from "./loadoutModel";
 | 
					import { EquipmentSelectionSchema, oidSchema } from "./loadoutModel";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const typeCountSchema = new Schema<ITypeCount>({ ItemType: String, ItemCount: Number }, { _id: false });
 | 
					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>(
 | 
					const slotsBinSchema = new Schema<ISlots>(
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        Slots: Number,
 | 
					        Slots: Number,
 | 
				
			||||||
@ -631,6 +687,27 @@ questKeysSchema.set("toJSON", {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export const fusionTreasuresSchema = new Schema<IFusionTreasure>().add(typeCountSchema).add({ Sockets: Number });
 | 
					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>(
 | 
					const spectreLoadoutsSchema = new Schema<ISpectreLoadout>(
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        ItemType: String,
 | 
					        ItemType: String,
 | 
				
			||||||
@ -719,25 +796,23 @@ const endlessXpProgressSchema = new Schema<IEndlessXpProgress>(
 | 
				
			|||||||
    { _id: false }
 | 
					    { _id: false }
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const crewShipPilotWeaponSchema = new Schema<ICrewShipPilotWeapon>(
 | 
					const crewShipWeaponEmplacementsSchema = new Schema<ICrewShipWeaponEmplacements>(
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        PRIMARY_A: EquipmentSelectionSchema,
 | 
					        PRIMARY_A: EquipmentSelectionSchema,
 | 
				
			||||||
        SECONDARY_A: EquipmentSelectionSchema
 | 
					        PRIMARY_B: EquipmentSelectionSchema,
 | 
				
			||||||
    },
 | 
					        SECONDARY_A: EquipmentSelectionSchema,
 | 
				
			||||||
    { _id: false }
 | 
					        SECONDARY_B: EquipmentSelectionSchema
 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const crewShipPortGunsSchema = new Schema<ICrewShipPortGuns>(
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        PRIMARY_A: EquipmentSelectionSchema
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    { _id: false }
 | 
					    { _id: false }
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const crewShipWeaponSchema = new Schema<ICrewShipWeapon>(
 | 
					const crewShipWeaponSchema = new Schema<ICrewShipWeapon>(
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        PILOT: crewShipPilotWeaponSchema,
 | 
					        PILOT: crewShipWeaponEmplacementsSchema,
 | 
				
			||||||
        PORT_GUNS: crewShipPortGunsSchema
 | 
					        PORT_GUNS: crewShipWeaponEmplacementsSchema,
 | 
				
			||||||
 | 
					        STARBOARD_GUNS: crewShipWeaponEmplacementsSchema,
 | 
				
			||||||
 | 
					        ARTILLERY: crewShipWeaponEmplacementsSchema,
 | 
				
			||||||
 | 
					        SCANNER: crewShipWeaponEmplacementsSchema
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    { _id: false }
 | 
					    { _id: false }
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
@ -761,7 +836,7 @@ const crewShipCustomizationSchema = new Schema<ICrewShipCustomization>(
 | 
				
			|||||||
const crewShipMemberSchema = new Schema<ICrewShipMemberDatabase>(
 | 
					const crewShipMemberSchema = new Schema<ICrewShipMemberDatabase>(
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        ItemId: { type: Schema.Types.ObjectId, required: false },
 | 
					        ItemId: { type: Schema.Types.ObjectId, required: false },
 | 
				
			||||||
        NemesisFingerprint: { type: Number, required: false }
 | 
					        NemesisFingerprint: { type: BigInt, required: false }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    { _id: false }
 | 
					    { _id: false }
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
@ -985,6 +1060,13 @@ pendingRecipeSchema.set("toJSON", {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const accoladesSchema = new Schema<IAccolades>(
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Heirloom: Boolean
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    { _id: false }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const infestedFoundrySchema = new Schema<IInfestedFoundryDatabase>(
 | 
					const infestedFoundrySchema = new Schema<IInfestedFoundryDatabase>(
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        Name: String,
 | 
					        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>(
 | 
					const lockedWeaponGroupSchema = new Schema<ILockedWeaponGroupDatabase>(
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        s: Schema.Types.ObjectId,
 | 
					        s: Schema.Types.ObjectId,
 | 
				
			||||||
@ -1363,7 +1453,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
 | 
				
			|||||||
        CrewShipSalvagedWeaponSkins: [upgradeSchema],
 | 
					        CrewShipSalvagedWeaponSkins: [upgradeSchema],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        //RailJack Crew
 | 
					        //RailJack Crew
 | 
				
			||||||
        CrewMembers: [Schema.Types.Mixed],
 | 
					        CrewMembers: [crewMemberSchema],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        //Complete Mission\Quests
 | 
					        //Complete Mission\Quests
 | 
				
			||||||
        Missions: [missionSchema],
 | 
					        Missions: [missionSchema],
 | 
				
			||||||
@ -1384,6 +1474,16 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
 | 
				
			|||||||
        //Mastery Rank next availability
 | 
					        //Mastery Rank next availability
 | 
				
			||||||
        TrainingDate: { type: Date, default: new Date(0) },
 | 
					        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
 | 
					        //you saw last played Region when you opened the star map
 | 
				
			||||||
        LastRegionPlayed: String,
 | 
					        LastRegionPlayed: String,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1423,7 +1523,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        SentientSpawnChanceBoosters: Schema.Types.Mixed,
 | 
					        SentientSpawnChanceBoosters: Schema.Types.Mixed,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        QualifyingInvasions: [Schema.Types.Mixed],
 | 
					        QualifyingInvasions: [invasionProgressSchema],
 | 
				
			||||||
        FactionScores: [Number],
 | 
					        FactionScores: [Number],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // https://warframe.fandom.com/wiki/Specter_(Tenno)
 | 
					        // https://warframe.fandom.com/wiki/Specter_(Tenno)
 | 
				
			||||||
@ -1445,6 +1545,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
 | 
				
			|||||||
        CompletedSorties: [String],
 | 
					        CompletedSorties: [String],
 | 
				
			||||||
        LastSortieReward: { type: [lastSortieRewardSchema], default: undefined },
 | 
					        LastSortieReward: { type: [lastSortieRewardSchema], default: undefined },
 | 
				
			||||||
        LastLiteSortieReward: { type: [lastSortieRewardSchema], default: undefined },
 | 
					        LastLiteSortieReward: { type: [lastSortieRewardSchema], default: undefined },
 | 
				
			||||||
 | 
					        SortieRewardAttenuation: { type: [sortieRewardAttenutationSchema], default: undefined },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Resource Extractor Drones
 | 
					        // Resource Extractor Drones
 | 
				
			||||||
        Drones: [droneSchema],
 | 
					        Drones: [droneSchema],
 | 
				
			||||||
@ -1538,7 +1639,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
 | 
				
			|||||||
        HasContributedToDojo: Boolean,
 | 
					        HasContributedToDojo: Boolean,
 | 
				
			||||||
        HWIDProtectEnabled: Boolean,
 | 
					        HWIDProtectEnabled: Boolean,
 | 
				
			||||||
        LoadOutPresets: { type: Schema.Types.ObjectId, ref: "Loadout" },
 | 
					        LoadOutPresets: { type: Schema.Types.ObjectId, ref: "Loadout" },
 | 
				
			||||||
        CurrentLoadOutIds: [Schema.Types.Mixed],
 | 
					        CurrentLoadOutIds: [oidSchema],
 | 
				
			||||||
        RandomUpgradesIdentified: Number,
 | 
					        RandomUpgradesIdentified: Number,
 | 
				
			||||||
        BountyScore: Number,
 | 
					        BountyScore: Number,
 | 
				
			||||||
        ChallengeInstanceStates: [Schema.Types.Mixed],
 | 
					        ChallengeInstanceStates: [Schema.Types.Mixed],
 | 
				
			||||||
@ -1645,6 +1746,7 @@ export type InventoryDocumentProps = {
 | 
				
			|||||||
    CrewShipWeaponSkins: Types.DocumentArray<IUpgradeDatabase>;
 | 
					    CrewShipWeaponSkins: Types.DocumentArray<IUpgradeDatabase>;
 | 
				
			||||||
    CrewShipSalvagedWeaponSkins: Types.DocumentArray<IUpgradeDatabase>;
 | 
					    CrewShipSalvagedWeaponSkins: Types.DocumentArray<IUpgradeDatabase>;
 | 
				
			||||||
    PersonalTechProjects: Types.DocumentArray<IPersonalTechProjectDatabase>;
 | 
					    PersonalTechProjects: Types.DocumentArray<IPersonalTechProjectDatabase>;
 | 
				
			||||||
 | 
					    CrewMembers: Types.DocumentArray<ICrewMemberDatabase>;
 | 
				
			||||||
} & { [K in TEquipmentKey]: Types.DocumentArray<IEquipmentDatabase> };
 | 
					} & { [K in TEquipmentKey]: Types.DocumentArray<IEquipmentDatabase> };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
 | 
					// 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 { ILoadoutConfigDatabase, ILoadoutDatabase } from "@/src/types/saveLoadoutTypes";
 | 
				
			||||||
import { Document, Model, Schema, Types, model } from "mongoose";
 | 
					import { Document, Model, Schema, Types, model } from "mongoose";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const oidSchema = new Schema<IOid>(
 | 
					export const oidSchema = new Schema<IOid>(
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $oid: String
 | 
					        $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";
 | 
					import { model, Schema, SchemaOptions } from "mongoose";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const opts = {
 | 
					const opts = {
 | 
				
			||||||
@ -37,3 +37,13 @@ databaseAccountSchema.set("toJSON", {
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const Account = model<IDatabaseAccountJson>("Account", databaseAccountSchema);
 | 
					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 { abortDojoComponentDestructionController } from "@/src/controllers/api/abortDojoComponentDestructionController";
 | 
				
			||||||
import { activateRandomModController } from "@/src/controllers/api/activateRandomModController";
 | 
					import { activateRandomModController } from "@/src/controllers/api/activateRandomModController";
 | 
				
			||||||
import { addFriendImageController } from "@/src/controllers/api/addFriendImageController";
 | 
					import { addFriendImageController } from "@/src/controllers/api/addFriendImageController";
 | 
				
			||||||
 | 
					import { addIgnoredUserController } from "@/src/controllers/api/addIgnoredUserController";
 | 
				
			||||||
import { addToAllianceController } from "@/src/controllers/api/addToAllianceController";
 | 
					import { addToAllianceController } from "@/src/controllers/api/addToAllianceController";
 | 
				
			||||||
import { addToGuildController } from "@/src/controllers/api/addToGuildController";
 | 
					import { addToGuildController } from "@/src/controllers/api/addToGuildController";
 | 
				
			||||||
import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController";
 | 
					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 { createAllianceController } from "@/src/controllers/api/createAllianceController";
 | 
				
			||||||
import { createGuildController } from "@/src/controllers/api/createGuildController";
 | 
					import { createGuildController } from "@/src/controllers/api/createGuildController";
 | 
				
			||||||
import { creditsController } from "@/src/controllers/api/creditsController";
 | 
					import { creditsController } from "@/src/controllers/api/creditsController";
 | 
				
			||||||
 | 
					import { crewMembersController } from "@/src/controllers/api/crewMembersController";
 | 
				
			||||||
import { crewShipIdentifySalvageController } from "@/src/controllers/api/crewShipIdentifySalvageController";
 | 
					import { crewShipIdentifySalvageController } from "@/src/controllers/api/crewShipIdentifySalvageController";
 | 
				
			||||||
import { customizeGuildRanksController } from "@/src/controllers/api/customizeGuildRanksController";
 | 
					import { customizeGuildRanksController } from "@/src/controllers/api/customizeGuildRanksController";
 | 
				
			||||||
import { customObstacleCourseLeaderboardController } from "@/src/controllers/api/customObstacleCourseLeaderboardController";
 | 
					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 { releasePetController } from "@/src/controllers/api/releasePetController";
 | 
				
			||||||
import { removeFromAllianceController } from "@/src/controllers/api/removeFromAllianceController";
 | 
					import { removeFromAllianceController } from "@/src/controllers/api/removeFromAllianceController";
 | 
				
			||||||
import { removeFromGuildController } from "@/src/controllers/api/removeFromGuildController";
 | 
					import { removeFromGuildController } from "@/src/controllers/api/removeFromGuildController";
 | 
				
			||||||
 | 
					import { removeIgnoredUserController } from "@/src/controllers/api/removeIgnoredUserController";
 | 
				
			||||||
import { rerollRandomModController } from "@/src/controllers/api/rerollRandomModController";
 | 
					import { rerollRandomModController } from "@/src/controllers/api/rerollRandomModController";
 | 
				
			||||||
import { retrievePetFromStasisController } from "@/src/controllers/api/retrievePetFromStasisController";
 | 
					import { retrievePetFromStasisController } from "@/src/controllers/api/retrievePetFromStasisController";
 | 
				
			||||||
import { saveDialogueController } from "@/src/controllers/api/saveDialogueController";
 | 
					import { saveDialogueController } from "@/src/controllers/api/saveDialogueController";
 | 
				
			||||||
@ -202,6 +205,7 @@ apiRouter.get("/updateSession.php", updateSessionGetController);
 | 
				
			|||||||
apiRouter.post("/abortDojoComponent.php", abortDojoComponentController);
 | 
					apiRouter.post("/abortDojoComponent.php", abortDojoComponentController);
 | 
				
			||||||
apiRouter.post("/activateRandomMod.php", activateRandomModController);
 | 
					apiRouter.post("/activateRandomMod.php", activateRandomModController);
 | 
				
			||||||
apiRouter.post("/addFriendImage.php", addFriendImageController);
 | 
					apiRouter.post("/addFriendImage.php", addFriendImageController);
 | 
				
			||||||
 | 
					apiRouter.post("/addIgnoredUser.php", addIgnoredUserController);
 | 
				
			||||||
apiRouter.post("/addToAlliance.php", addToAllianceController);
 | 
					apiRouter.post("/addToAlliance.php", addToAllianceController);
 | 
				
			||||||
apiRouter.post("/addToGuild.php", addToGuildController);
 | 
					apiRouter.post("/addToGuild.php", addToGuildController);
 | 
				
			||||||
apiRouter.post("/arcaneCommon.php", arcaneCommonController);
 | 
					apiRouter.post("/arcaneCommon.php", arcaneCommonController);
 | 
				
			||||||
@ -219,6 +223,7 @@ apiRouter.post("/contributeToDojoComponent.php", contributeToDojoComponentContro
 | 
				
			|||||||
apiRouter.post("/contributeToVault.php", contributeToVaultController);
 | 
					apiRouter.post("/contributeToVault.php", contributeToVaultController);
 | 
				
			||||||
apiRouter.post("/createAlliance.php", createAllianceController);
 | 
					apiRouter.post("/createAlliance.php", createAllianceController);
 | 
				
			||||||
apiRouter.post("/createGuild.php", createGuildController);
 | 
					apiRouter.post("/createGuild.php", createGuildController);
 | 
				
			||||||
 | 
					apiRouter.post("/crewMembers.php", crewMembersController);
 | 
				
			||||||
apiRouter.post("/crewShipIdentifySalvage.php", crewShipIdentifySalvageController);
 | 
					apiRouter.post("/crewShipIdentifySalvage.php", crewShipIdentifySalvageController);
 | 
				
			||||||
apiRouter.post("/customizeGuildRanks.php", customizeGuildRanksController);
 | 
					apiRouter.post("/customizeGuildRanks.php", customizeGuildRanksController);
 | 
				
			||||||
apiRouter.post("/customObstacleCourseLeaderboard.php", customObstacleCourseLeaderboardController);
 | 
					apiRouter.post("/customObstacleCourseLeaderboard.php", customObstacleCourseLeaderboardController);
 | 
				
			||||||
@ -266,6 +271,7 @@ apiRouter.post("/purchase.php", purchaseController);
 | 
				
			|||||||
apiRouter.post("/redeemPromoCode.php", redeemPromoCodeController);
 | 
					apiRouter.post("/redeemPromoCode.php", redeemPromoCodeController);
 | 
				
			||||||
apiRouter.post("/releasePet.php", releasePetController);
 | 
					apiRouter.post("/releasePet.php", releasePetController);
 | 
				
			||||||
apiRouter.post("/removeFromGuild.php", removeFromGuildController);
 | 
					apiRouter.post("/removeFromGuild.php", removeFromGuildController);
 | 
				
			||||||
 | 
					apiRouter.post("/removeIgnoredUser.php", removeIgnoredUserController);
 | 
				
			||||||
apiRouter.post("/rerollRandomMod.php", rerollRandomModController);
 | 
					apiRouter.post("/rerollRandomMod.php", rerollRandomModController);
 | 
				
			||||||
apiRouter.post("/retrievePetFromStasis.php", retrievePetFromStasisController);
 | 
					apiRouter.post("/retrievePetFromStasis.php", retrievePetFromStasisController);
 | 
				
			||||||
apiRouter.post("/saveDialogue.php", saveDialogueController);
 | 
					apiRouter.post("/saveDialogue.php", saveDialogueController);
 | 
				
			||||||
 | 
				
			|||||||
@ -59,6 +59,7 @@ export const getGuildClient = async (guild: TGuildDatabaseDocument, accountId: s
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const members: IGuildMemberClient[] = [];
 | 
					    const members: IGuildMemberClient[] = [];
 | 
				
			||||||
    let missingEntry = true;
 | 
					    let missingEntry = true;
 | 
				
			||||||
 | 
					    const dataFillInPromises: Promise<void>[] = [];
 | 
				
			||||||
    for (const guildMember of guildMembers) {
 | 
					    for (const guildMember of guildMembers) {
 | 
				
			||||||
        const member: IGuildMemberClient = {
 | 
					        const member: IGuildMemberClient = {
 | 
				
			||||||
            _id: toOid(guildMember.accountId),
 | 
					            _id: toOid(guildMember.accountId),
 | 
				
			||||||
@ -70,8 +71,12 @@ export const getGuildClient = async (guild: TGuildDatabaseDocument, accountId: s
 | 
				
			|||||||
        if (guildMember.accountId.equals(accountId)) {
 | 
					        if (guildMember.accountId.equals(accountId)) {
 | 
				
			||||||
            missingEntry = false;
 | 
					            missingEntry = false;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
 | 
					            dataFillInPromises.push(
 | 
				
			||||||
 | 
					                (async (): Promise<void> => {
 | 
				
			||||||
                    member.DisplayName = (await Account.findById(guildMember.accountId, "DisplayName"))!.DisplayName;
 | 
					                    member.DisplayName = (await Account.findById(guildMember.accountId, "DisplayName"))!.DisplayName;
 | 
				
			||||||
            await fillInInventoryDataForGuildMember(member);
 | 
					                })()
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            dataFillInPromises.push(fillInInventoryDataForGuildMember(member));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        members.push(member);
 | 
					        members.push(member);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -90,6 +95,8 @@ export const getGuildClient = async (guild: TGuildDatabaseDocument, accountId: s
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await Promise.all(dataFillInPromises);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
        _id: toOid(guild._id),
 | 
					        _id: toOid(guild._id),
 | 
				
			||||||
        Name: guild.Name,
 | 
					        Name: guild.Name,
 | 
				
			||||||
 | 
				
			|||||||
@ -104,18 +104,18 @@ const replaceSlots = (db: ISlots, client: ISlots): void => {
 | 
				
			|||||||
    db.Slots = client.Slots;
 | 
					    db.Slots = client.Slots;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const convertCrewShipMember = (client: ICrewShipMemberClient): ICrewShipMemberDatabase => {
 | 
					export const importCrewMemberId = (crewMemberId: ICrewShipMemberClient): ICrewShipMemberDatabase => {
 | 
				
			||||||
    return {
 | 
					    if (crewMemberId.ItemId) {
 | 
				
			||||||
        ...client,
 | 
					        return { ItemId: new Types.ObjectId(crewMemberId.ItemId.$oid) };
 | 
				
			||||||
        ItemId: client.ItemId ? new Types.ObjectId(client.ItemId.$oid) : undefined
 | 
					    }
 | 
				
			||||||
    };
 | 
					    return { NemesisFingerprint: BigInt(crewMemberId.NemesisFingerprint ?? 0) };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const convertCrewShipMembers = (client: ICrewShipMembersClient): ICrewShipMembersDatabase => {
 | 
					const convertCrewShipMembers = (client: ICrewShipMembersClient): ICrewShipMembersDatabase => {
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
        SLOT_A: client.SLOT_A ? convertCrewShipMember(client.SLOT_A) : undefined,
 | 
					        SLOT_A: client.SLOT_A ? importCrewMemberId(client.SLOT_A) : undefined,
 | 
				
			||||||
        SLOT_B: client.SLOT_B ? convertCrewShipMember(client.SLOT_B) : undefined,
 | 
					        SLOT_B: client.SLOT_B ? importCrewMemberId(client.SLOT_B) : undefined,
 | 
				
			||||||
        SLOT_C: client.SLOT_C ? convertCrewShipMember(client.SLOT_C) : 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]);
 | 
					            replaceSlots(db[key], client[key]);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    // boolean
 | 
				
			||||||
    for (const key of [
 | 
					    for (const key of [
 | 
				
			||||||
        "UseAdultOperatorLoadout",
 | 
					        "UseAdultOperatorLoadout",
 | 
				
			||||||
        "HasOwnedVoidProjectionsPreviously",
 | 
					        "HasOwnedVoidProjectionsPreviously",
 | 
				
			||||||
        "ReceivedStartingGear",
 | 
					        "ReceivedStartingGear",
 | 
				
			||||||
        "ArchwingEnabled",
 | 
					        "ArchwingEnabled",
 | 
				
			||||||
        "PlayedParkourTutorial"
 | 
					        "PlayedParkourTutorial",
 | 
				
			||||||
 | 
					        "Staff",
 | 
				
			||||||
 | 
					        "Moderator",
 | 
				
			||||||
 | 
					        "Partner",
 | 
				
			||||||
 | 
					        "Counselor"
 | 
				
			||||||
    ] as const) {
 | 
					    ] as const) {
 | 
				
			||||||
        if (client[key] !== undefined) {
 | 
					        if (client[key] !== undefined) {
 | 
				
			||||||
            db[key] = client[key];
 | 
					            db[key] = client[key];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    // number
 | 
				
			||||||
    for (const key of [
 | 
					    for (const key of [
 | 
				
			||||||
        "PlayerLevel",
 | 
					        "PlayerLevel",
 | 
				
			||||||
        "RegularCredits",
 | 
					        "RegularCredits",
 | 
				
			||||||
@ -250,12 +256,15 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
 | 
				
			|||||||
        "PrimeTokens",
 | 
					        "PrimeTokens",
 | 
				
			||||||
        "TradesRemaining",
 | 
					        "TradesRemaining",
 | 
				
			||||||
        "GiftsRemaining",
 | 
					        "GiftsRemaining",
 | 
				
			||||||
        "ChallengesFixVersion"
 | 
					        "ChallengesFixVersion",
 | 
				
			||||||
 | 
					        "Founder",
 | 
				
			||||||
 | 
					        "Guide"
 | 
				
			||||||
    ] as const) {
 | 
					    ] as const) {
 | 
				
			||||||
        if (client[key] !== undefined) {
 | 
					        if (client[key] !== undefined) {
 | 
				
			||||||
            db[key] = client[key];
 | 
					            db[key] = client[key];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    // string
 | 
				
			||||||
    for (const key of [
 | 
					    for (const key of [
 | 
				
			||||||
        "ThemeStyle",
 | 
					        "ThemeStyle",
 | 
				
			||||||
        "ThemeBackground",
 | 
					        "ThemeBackground",
 | 
				
			||||||
@ -270,6 +279,7 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
 | 
				
			|||||||
            db[key] = client[key];
 | 
					            db[key] = client[key];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    // string[]
 | 
				
			||||||
    for (const key of [
 | 
					    for (const key of [
 | 
				
			||||||
        "EquippedGear",
 | 
					        "EquippedGear",
 | 
				
			||||||
        "EquippedEmotes",
 | 
					        "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 => {
 | 
					const convertLoadOutConfig = (client: ILoadoutConfigClient): ILoadoutConfigDatabase => {
 | 
				
			||||||
 | 
				
			|||||||
@ -22,7 +22,8 @@ import {
 | 
				
			|||||||
    IDroneClient,
 | 
					    IDroneClient,
 | 
				
			||||||
    IUpgradeClient,
 | 
					    IUpgradeClient,
 | 
				
			||||||
    TPartialStartingGear,
 | 
					    TPartialStartingGear,
 | 
				
			||||||
    ILoreFragmentScan
 | 
					    ILoreFragmentScan,
 | 
				
			||||||
 | 
					    ICrewMemberClient
 | 
				
			||||||
} from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					} from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
import { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/genericUpdate";
 | 
					import { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/genericUpdate";
 | 
				
			||||||
import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes";
 | 
					import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes";
 | 
				
			||||||
@ -713,6 +714,15 @@ export const addItem = async (
 | 
				
			|||||||
                        return {
 | 
					                        return {
 | 
				
			||||||
                            MiscItems: miscItemChanges
 | 
					                            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") {
 | 
					                    } else if (typeName == "/Lotus/Types/Game/CrewShip/RailJack/DefaultHarness") {
 | 
				
			||||||
                        return addCrewShipHarness(inventory, typeName);
 | 
					                        return addCrewShipHarness(inventory, typeName);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
@ -1212,6 +1222,78 @@ const addDrone = (
 | 
				
			|||||||
    return inventoryChanges;
 | 
					    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 (
 | 
					export const addEmailItem = async (
 | 
				
			||||||
    inventory: TInventoryDatabaseDocument,
 | 
					    inventory: TInventoryDatabaseDocument,
 | 
				
			||||||
    typeName: string,
 | 
					    typeName: string,
 | 
				
			||||||
 | 
				
			|||||||
@ -53,7 +53,7 @@ import conservationAnimals from "@/static/fixed_responses/conservationAnimals.js
 | 
				
			|||||||
import { getInfNodes } from "@/src/helpers/nemesisHelpers";
 | 
					import { getInfNodes } from "@/src/helpers/nemesisHelpers";
 | 
				
			||||||
import { Loadout } from "../models/inventoryModels/loadoutModel";
 | 
					import { Loadout } from "../models/inventoryModels/loadoutModel";
 | 
				
			||||||
import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes";
 | 
					import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes";
 | 
				
			||||||
import { getWorldState } from "./worldStateService";
 | 
					import { getLiteSortie, getWorldState, idToWeek } from "./worldStateService";
 | 
				
			||||||
import { config } from "./configService";
 | 
					import { config } from "./configService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[] => {
 | 
					const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[] => {
 | 
				
			||||||
@ -71,7 +71,12 @@ const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[]
 | 
				
			|||||||
        return [rewardInfo.rewardTier];
 | 
					        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];
 | 
					    if (rotationCount === 0) return [0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const rotationPattern =
 | 
					    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 (
 | 
					        if (
 | 
				
			||||||
            inventoryUpdates.MissionFailed &&
 | 
					            inventoryUpdates.MissionFailed &&
 | 
				
			||||||
            inventoryUpdates.MissionStatus == "GS_FAILURE" &&
 | 
					            inventoryUpdates.MissionStatus == "GS_FAILURE" &&
 | 
				
			||||||
            inventoryUpdates.ObjectiveReached &&
 | 
					            inventoryUpdates.ObjectiveReached &&
 | 
				
			||||||
            !inventoryUpdates.LockedWeaponGroup
 | 
					            !inventoryUpdates.LockedWeaponGroup &&
 | 
				
			||||||
 | 
					            !inventory.LockedWeaponGroup &&
 | 
				
			||||||
 | 
					            !inventoryUpdates.LevelKeyName
 | 
				
			||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
            const loadout = (await Loadout.findById(inventory.LoadOutPresets, "NORMAL"))!;
 | 
					            const loadout = (await Loadout.findById(inventory.LoadOutPresets, "NORMAL"))!;
 | 
				
			||||||
            const config = loadout.NORMAL.id(inventory.CurrentLoadOutIds[0].$oid)!;
 | 
					            const config = loadout.NORMAL.id(inventory.CurrentLoadOutIds[0].$oid)!;
 | 
				
			||||||
@ -407,7 +419,9 @@ export const addMissionInventoryUpdates = async (
 | 
				
			|||||||
                break;
 | 
					                break;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            case "SortieId": {
 | 
					            case "SortieId": {
 | 
				
			||||||
 | 
					                if (inventory.CompletedSorties.indexOf(value) == -1) {
 | 
				
			||||||
                    inventory.CompletedSorties.push(value);
 | 
					                    inventory.CompletedSorties.push(value);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            case "SeasonChallengeCompletions": {
 | 
					            case "SeasonChallengeCompletions": {
 | 
				
			||||||
@ -525,6 +539,26 @@ export const addMissionInventoryUpdates = async (
 | 
				
			|||||||
                inventoryChanges.RegularCredits -= value;
 | 
					                inventoryChanges.RegularCredits -= value;
 | 
				
			||||||
                break;
 | 
					                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:
 | 
					            default:
 | 
				
			||||||
                // Equipment XP updates
 | 
					                // Equipment XP updates
 | 
				
			||||||
                if (equipmentKeys.includes(key as TEquipmentKey)) {
 | 
					                if (equipmentKeys.includes(key as TEquipmentKey)) {
 | 
				
			||||||
@ -564,7 +598,8 @@ export const addMissionRewards = async (
 | 
				
			|||||||
        RegularCredits: creditDrops,
 | 
					        RegularCredits: creditDrops,
 | 
				
			||||||
        VoidTearParticipantsCurrWave: voidTearWave,
 | 
					        VoidTearParticipantsCurrWave: voidTearWave,
 | 
				
			||||||
        StrippedItems: strippedItems
 | 
					        StrippedItems: strippedItems
 | 
				
			||||||
    }: IMissionInventoryUpdateRequest
 | 
					    }: IMissionInventoryUpdateRequest,
 | 
				
			||||||
 | 
					    firstCompletion: boolean
 | 
				
			||||||
): Promise<AddMissionRewardsReturnType> => {
 | 
					): Promise<AddMissionRewardsReturnType> => {
 | 
				
			||||||
    if (!rewardInfo) {
 | 
					    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
 | 
					        //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
 | 
					    //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);
 | 
					    logger.debug("random mission drops:", MissionRewards);
 | 
				
			||||||
    const inventoryChanges: IInventoryChanges = {};
 | 
					    const inventoryChanges: IInventoryChanges = {};
 | 
				
			||||||
    const AffiliationMods: IAffiliationMods[] = [];
 | 
					    const AffiliationMods: IAffiliationMods[] = [];
 | 
				
			||||||
    let SyndicateXPItemReward;
 | 
					    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;
 | 
					    let missionCompletionCredits = 0;
 | 
				
			||||||
    //inventory change is what the client has not rewarded itself, also the client needs to know the credit changes for display
 | 
					    //inventory change is what the client has not rewarded itself, also the client needs to know the credit changes for display
 | 
				
			||||||
    if (levelKeyName) {
 | 
					    if (levelKeyName) {
 | 
				
			||||||
@ -695,6 +720,12 @@ export const addMissionRewards = async (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if (strippedItems) {
 | 
					    if (strippedItems) {
 | 
				
			||||||
        for (const si of 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] ?? [];
 | 
					            const droptables = ExportEnemies.droptables[si.DropTable] ?? [];
 | 
				
			||||||
            if (si.DROP_MOD) {
 | 
					            if (si.DROP_MOD) {
 | 
				
			||||||
                const modDroptable = droptables.find(x => x.type == "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
 | 
					    //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[] = [];
 | 
					    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])!;
 | 
					            const drop = getRandomRewardByChance(ExportRewards["/Lotus/Types/Game/MissionDecks/SortieRewards"][0])!;
 | 
				
			||||||
            drops.push({ StoreItem: drop.type, ItemCount: drop.itemCount });
 | 
					            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")) {
 | 
					    if (RewardInfo.periodicMissionTag?.startsWith("HardDaily")) {
 | 
				
			||||||
        drops.push({
 | 
					        drops.push({
 | 
				
			||||||
@ -965,10 +1059,16 @@ function getRandomMissionDrops(RewardInfo: IRewardInfo, tierOverride: number | u
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    if (RewardInfo.node in ExportRegions) {
 | 
					    if (RewardInfo.node in ExportRegions) {
 | 
				
			||||||
        const region = ExportRegions[RewardInfo.node];
 | 
					        const region = ExportRegions[RewardInfo.node];
 | 
				
			||||||
        let rewardManifests: string[] =
 | 
					        let rewardManifests: string[];
 | 
				
			||||||
            RewardInfo.periodicMissionTag == "EliteAlert" || RewardInfo.periodicMissionTag == "EliteAlertB"
 | 
					        if (RewardInfo.periodicMissionTag == "EliteAlert" || RewardInfo.periodicMissionTag == "EliteAlertB") {
 | 
				
			||||||
                ? ["/Lotus/Types/Game/MissionDecks/EliteAlertMissionRewards/EliteAlertMissionRewards"]
 | 
					            rewardManifests = ["/Lotus/Types/Game/MissionDecks/EliteAlertMissionRewards/EliteAlertMissionRewards"];
 | 
				
			||||||
                : region.rewardManifests;
 | 
					        } 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[] = [];
 | 
					        let rotations: number[] = [];
 | 
				
			||||||
        if (RewardInfo.jobId) {
 | 
					        if (RewardInfo.jobId) {
 | 
				
			||||||
 | 
				
			|||||||
@ -141,7 +141,8 @@ export const handlePurchase = async (
 | 
				
			|||||||
        inventory,
 | 
					        inventory,
 | 
				
			||||||
        purchaseRequest.PurchaseParams.Quantity,
 | 
					        purchaseRequest.PurchaseParams.Quantity,
 | 
				
			||||||
        undefined,
 | 
					        undefined,
 | 
				
			||||||
        undefined,
 | 
					        false,
 | 
				
			||||||
 | 
					        purchaseRequest.PurchaseParams.UsePremium,
 | 
				
			||||||
        seed
 | 
					        seed
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    combineInventoryChanges(purchaseResponse.InventoryChanges, prePurchaseInventoryChanges);
 | 
					    combineInventoryChanges(purchaseResponse.InventoryChanges, prePurchaseInventoryChanges);
 | 
				
			||||||
@ -331,6 +332,7 @@ export const handleStoreItemAcquisition = async (
 | 
				
			|||||||
    quantity: number = 1,
 | 
					    quantity: number = 1,
 | 
				
			||||||
    durability: TRarity = "COMMON",
 | 
					    durability: TRarity = "COMMON",
 | 
				
			||||||
    ignorePurchaseQuantity: boolean = false,
 | 
					    ignorePurchaseQuantity: boolean = false,
 | 
				
			||||||
 | 
					    premiumPurchase: boolean = true,
 | 
				
			||||||
    seed?: bigint
 | 
					    seed?: bigint
 | 
				
			||||||
): Promise<IPurchaseResponse> => {
 | 
					): Promise<IPurchaseResponse> => {
 | 
				
			||||||
    let purchaseResponse = {
 | 
					    let purchaseResponse = {
 | 
				
			||||||
@ -352,11 +354,20 @@ export const handleStoreItemAcquisition = async (
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        switch (storeCategory) {
 | 
					        switch (storeCategory) {
 | 
				
			||||||
            default: {
 | 
					            default: {
 | 
				
			||||||
                purchaseResponse = { InventoryChanges: await addItem(inventory, internalName, quantity, true, seed) };
 | 
					                purchaseResponse = {
 | 
				
			||||||
 | 
					                    InventoryChanges: await addItem(inventory, internalName, quantity, premiumPurchase, seed)
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            case "Types":
 | 
					            case "Types":
 | 
				
			||||||
                purchaseResponse = await handleTypesPurchase(internalName, inventory, quantity, ignorePurchaseQuantity);
 | 
					                purchaseResponse = await handleTypesPurchase(
 | 
				
			||||||
 | 
					                    internalName,
 | 
				
			||||||
 | 
					                    inventory,
 | 
				
			||||||
 | 
					                    quantity,
 | 
				
			||||||
 | 
					                    ignorePurchaseQuantity,
 | 
				
			||||||
 | 
					                    premiumPurchase,
 | 
				
			||||||
 | 
					                    seed
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            case "Boosters":
 | 
					            case "Boosters":
 | 
				
			||||||
                purchaseResponse = handleBoostersPurchase(storeItemName, inventory, durability);
 | 
					                purchaseResponse = handleBoostersPurchase(storeItemName, inventory, durability);
 | 
				
			||||||
@ -478,13 +489,15 @@ const handleTypesPurchase = async (
 | 
				
			|||||||
    typesName: string,
 | 
					    typesName: string,
 | 
				
			||||||
    inventory: TInventoryDatabaseDocument,
 | 
					    inventory: TInventoryDatabaseDocument,
 | 
				
			||||||
    quantity: number,
 | 
					    quantity: number,
 | 
				
			||||||
    ignorePurchaseQuantity: boolean
 | 
					    ignorePurchaseQuantity: boolean,
 | 
				
			||||||
 | 
					    premiumPurchase: boolean = true,
 | 
				
			||||||
 | 
					    seed?: bigint
 | 
				
			||||||
): Promise<IPurchaseResponse> => {
 | 
					): Promise<IPurchaseResponse> => {
 | 
				
			||||||
    const typeCategory = getStoreItemTypesCategory(typesName);
 | 
					    const typeCategory = getStoreItemTypesCategory(typesName);
 | 
				
			||||||
    logger.debug(`type category ${typeCategory}`);
 | 
					    logger.debug(`type category ${typeCategory}`);
 | 
				
			||||||
    switch (typeCategory) {
 | 
					    switch (typeCategory) {
 | 
				
			||||||
        default:
 | 
					        default:
 | 
				
			||||||
            return { InventoryChanges: await addItem(inventory, typesName, quantity) };
 | 
					            return { InventoryChanges: await addItem(inventory, typesName, quantity, premiumPurchase, seed) };
 | 
				
			||||||
        case "BoosterPacks":
 | 
					        case "BoosterPacks":
 | 
				
			||||||
            return handleBoosterPackPurchase(typesName, inventory, quantity);
 | 
					            return handleBoosterPackPurchase(typesName, inventory, quantity);
 | 
				
			||||||
        case "SlotItems":
 | 
					        case "SlotItems":
 | 
				
			||||||
 | 
				
			|||||||
@ -216,6 +216,27 @@ const handleQuestCompletion = async (
 | 
				
			|||||||
        setupKahlSyndicate(inventory);
 | 
					        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);
 | 
					    const questCompletionItems = getQuestCompletionItems(questKey);
 | 
				
			||||||
    logger.debug(`quest completion items`, questCompletionItems);
 | 
					    logger.debug(`quest completion items`, questCompletionItems);
 | 
				
			||||||
    if (questCompletionItems) {
 | 
					    if (questCompletionItems) {
 | 
				
			||||||
 | 
				
			|||||||
@ -13,6 +13,8 @@ import { Types } from "mongoose";
 | 
				
			|||||||
import { isEmptyObject } from "@/src/helpers/general";
 | 
					import { isEmptyObject } from "@/src/helpers/general";
 | 
				
			||||||
import { logger } from "@/src/utils/logger";
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
import { equipmentKeys, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					import { equipmentKeys, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
 | 
					import { IItemConfig } from "../types/inventoryTypes/commonInventoryTypes";
 | 
				
			||||||
 | 
					import { importCrewMemberId } from "./importService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//TODO: setup default items on account creation or like originally in giveStartingItems.php
 | 
					//TODO: setup default items on account creation or like originally in giveStartingItems.php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -174,8 +176,8 @@ export const handleInventoryItemConfigChange = async (
 | 
				
			|||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        for (const [configId, config] of Object.entries(itemConfigEntries)) {
 | 
					                        for (const [configId, config] of Object.entries(itemConfigEntries)) {
 | 
				
			||||||
                            if (typeof config !== "boolean") {
 | 
					                            if (/^[0-9]+$/.test(configId)) {
 | 
				
			||||||
                                inventoryItem.Configs[parseInt(configId)] = config;
 | 
					                                inventoryItem.Configs[parseInt(configId)] = config as IItemConfig;
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        if ("Favorite" in itemConfigEntries) {
 | 
					                        if ("Favorite" in itemConfigEntries) {
 | 
				
			||||||
@ -184,6 +186,26 @@ export const handleInventoryItemConfigChange = async (
 | 
				
			|||||||
                        if ("IsNew" in itemConfigEntries) {
 | 
					                        if ("IsNew" in itemConfigEntries) {
 | 
				
			||||||
                            inventoryItem.IsNew = itemConfigEntries.IsNew;
 | 
					                            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;
 | 
					                    break;
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,4 @@
 | 
				
			|||||||
import fs from "fs";
 | 
					import { unixTimesInMs } from "@/src/constants/timeConstants";
 | 
				
			||||||
import path from "path";
 | 
					 | 
				
			||||||
import { repoDir } from "@/src/helpers/pathHelper";
 | 
					 | 
				
			||||||
import { CRng, mixSeeds } from "@/src/services/rngService";
 | 
					import { CRng, mixSeeds } from "@/src/services/rngService";
 | 
				
			||||||
import { IMongoDate } from "@/src/types/commonTypes";
 | 
					import { IMongoDate } from "@/src/types/commonTypes";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
@ -9,49 +7,73 @@ import {
 | 
				
			|||||||
    IVendorInfo,
 | 
					    IVendorInfo,
 | 
				
			||||||
    IVendorManifestPreprocessed
 | 
					    IVendorManifestPreprocessed
 | 
				
			||||||
} from "@/src/types/vendorTypes";
 | 
					} from "@/src/types/vendorTypes";
 | 
				
			||||||
import { JSONParse } from "json-with-bigint";
 | 
					 | 
				
			||||||
import { ExportVendors } from "warframe-public-export-plus";
 | 
					import { ExportVendors } from "warframe-public-export-plus";
 | 
				
			||||||
import { unixTimesInMs } from "../constants/timeConstants";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getVendorManifestJson = (name: string): IRawVendorManifest => {
 | 
					import ArchimedeanVendorManifest from "@/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json";
 | 
				
			||||||
    return JSONParse(fs.readFileSync(path.join(repoDir, `static/fixed_responses/getVendorInfo/${name}.json`), "utf-8"));
 | 
					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[] = [
 | 
					const rawVendorManifests: IRawVendorManifest[] = [
 | 
				
			||||||
    getVendorManifestJson("ArchimedeanVendorManifest"),
 | 
					    ArchimedeanVendorManifest,
 | 
				
			||||||
    getVendorManifestJson("DeimosEntratiFragmentVendorProductsManifest"),
 | 
					    DeimosEntratiFragmentVendorProductsManifest,
 | 
				
			||||||
    getVendorManifestJson("DeimosFishmongerVendorManifest"),
 | 
					    DeimosFishmongerVendorManifest,
 | 
				
			||||||
    getVendorManifestJson("DeimosHivemindCommisionsManifestFishmonger"),
 | 
					    DeimosHivemindCommisionsManifestFishmonger,
 | 
				
			||||||
    getVendorManifestJson("DeimosHivemindCommisionsManifestPetVendor"),
 | 
					    DeimosHivemindCommisionsManifestPetVendor,
 | 
				
			||||||
    getVendorManifestJson("DeimosHivemindCommisionsManifestProspector"),
 | 
					    DeimosHivemindCommisionsManifestProspector,
 | 
				
			||||||
    getVendorManifestJson("DeimosHivemindCommisionsManifestTokenVendor"),
 | 
					    DeimosHivemindCommisionsManifestTokenVendor,
 | 
				
			||||||
    getVendorManifestJson("DeimosHivemindCommisionsManifestWeaponsmith"),
 | 
					    DeimosHivemindCommisionsManifestWeaponsmith,
 | 
				
			||||||
    getVendorManifestJson("DeimosHivemindTokenVendorManifest"),
 | 
					    DeimosHivemindTokenVendorManifest,
 | 
				
			||||||
    getVendorManifestJson("DeimosPetVendorManifest"),
 | 
					    DeimosPetVendorManifest,
 | 
				
			||||||
    getVendorManifestJson("DeimosProspectorVendorManifest"),
 | 
					    DeimosProspectorVendorManifest,
 | 
				
			||||||
    getVendorManifestJson("DuviriAcrithisVendorManifest"),
 | 
					    DuviriAcrithisVendorManifest,
 | 
				
			||||||
    getVendorManifestJson("EntratiLabsEntratiLabsCommisionsManifest"),
 | 
					    EntratiLabsEntratiLabsCommisionsManifest,
 | 
				
			||||||
    getVendorManifestJson("EntratiLabsEntratiLabVendorManifest"),
 | 
					    EntratiLabsEntratiLabVendorManifest,
 | 
				
			||||||
    getVendorManifestJson("GuildAdvertisementVendorManifest"), // uses preprocessing
 | 
					    GuildAdvertisementVendorManifest, // uses preprocessing
 | 
				
			||||||
    getVendorManifestJson("HubsIronwakeDondaVendorManifest"), // uses preprocessing
 | 
					    HubsIronwakeDondaVendorManifest, // uses preprocessing
 | 
				
			||||||
    getVendorManifestJson("HubsPerrinSequenceWeaponVendorManifest"),
 | 
					    HubsRailjackCrewMemberVendorManifest,
 | 
				
			||||||
    getVendorManifestJson("HubsRailjackCrewMemberVendorManifest"),
 | 
					    MaskSalesmanManifest,
 | 
				
			||||||
    getVendorManifestJson("MaskSalesmanManifest"),
 | 
					    Nova1999ConquestShopManifest,
 | 
				
			||||||
    getVendorManifestJson("Nova1999ConquestShopManifest"),
 | 
					    OstronFishmongerVendorManifest,
 | 
				
			||||||
    getVendorManifestJson("OstronFishmongerVendorManifest"),
 | 
					    OstronPetVendorManifest,
 | 
				
			||||||
    getVendorManifestJson("OstronPetVendorManifest"),
 | 
					    OstronProspectorVendorManifest,
 | 
				
			||||||
    getVendorManifestJson("OstronProspectorVendorManifest"),
 | 
					    RadioLegionIntermission12VendorManifest,
 | 
				
			||||||
    getVendorManifestJson("RadioLegionIntermission12VendorManifest"),
 | 
					    SolarisDebtTokenVendorManifest,
 | 
				
			||||||
    getVendorManifestJson("SolarisDebtTokenVendorManifest"),
 | 
					    SolarisDebtTokenVendorRepossessionsManifest,
 | 
				
			||||||
    getVendorManifestJson("SolarisDebtTokenVendorRepossessionsManifest"),
 | 
					    SolarisFishmongerVendorManifest,
 | 
				
			||||||
    getVendorManifestJson("SolarisFishmongerVendorManifest"),
 | 
					    SolarisProspectorVendorManifest,
 | 
				
			||||||
    getVendorManifestJson("SolarisProspectorVendorManifest"),
 | 
					    TeshinHardModeVendorManifest, // uses preprocessing
 | 
				
			||||||
    getVendorManifestJson("TeshinHardModeVendorManifest"), // uses preprocessing
 | 
					    ZarimanCommisionsManifestArchimedean
 | 
				
			||||||
    getVendorManifestJson("ZarimanCommisionsManifestArchimedean")
 | 
					 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IGeneratableVendorInfo extends Omit<IVendorInfo, "ItemManifest" | "Expiry"> {
 | 
					interface IGeneratableVendorInfo extends Omit<IVendorInfo, "ItemManifest" | "Expiry"> {
 | 
				
			||||||
    cycleDuration?: number;
 | 
					    cycleStart: number;
 | 
				
			||||||
 | 
					    cycleDuration: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const generatableVendors: IGeneratableVendorInfo[] = [
 | 
					const generatableVendors: IGeneratableVendorInfo[] = [
 | 
				
			||||||
@ -62,6 +84,16 @@ const generatableVendors: IGeneratableVendorInfo[] = [
 | 
				
			|||||||
        RandomSeedType: "VRST_WEAPON",
 | 
					        RandomSeedType: "VRST_WEAPON",
 | 
				
			||||||
        RequiredGoalTag: "",
 | 
					        RequiredGoalTag: "",
 | 
				
			||||||
        WeaponUpgradeValueAttenuationExponent: 2.25,
 | 
					        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
 | 
					        cycleDuration: 4 * unixTimesInMs.day
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // {
 | 
					    // {
 | 
				
			||||||
@ -124,7 +156,7 @@ const preprocessVendorManifest = (originalManifest: IRawVendorManifest): IVendor
 | 
				
			|||||||
const refreshExpiry = (expiry: IMongoDate): number => {
 | 
					const refreshExpiry = (expiry: IMongoDate): number => {
 | 
				
			||||||
    const period = parseInt(expiry.$date.$numberLong);
 | 
					    const period = parseInt(expiry.$date.$numberLong);
 | 
				
			||||||
    if (Date.now() >= period) {
 | 
					    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 iteration = Math.trunc((Date.now() - epoch) / period);
 | 
				
			||||||
        const start = epoch + iteration * period;
 | 
					        const start = epoch + iteration * period;
 | 
				
			||||||
        const end = start + period;
 | 
					        const end = start + period;
 | 
				
			||||||
@ -135,11 +167,11 @@ const refreshExpiry = (expiry: IMongoDate): number => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifestPreprocessed => {
 | 
					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];
 | 
					    const manifest = ExportVendors[vendorInfo.TypeName];
 | 
				
			||||||
    let binThisCycle;
 | 
					    let binThisCycle;
 | 
				
			||||||
    if (manifest.isOneBinPerCycle) {
 | 
					    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);
 | 
					        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.
 | 
					        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) {
 | 
					        if (manifest.isOneBinPerCycle && rawItem.bin != binThisCycle) {
 | 
				
			||||||
            continue;
 | 
					            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 cycleIndex = Math.trunc((Date.now() - EPOCH) / cycleDuration);
 | 
				
			||||||
        const cycleStart = EPOCH + cycleIndex * cycleDuration;
 | 
					        const cycleStart = EPOCH + cycleIndex * cycleDuration;
 | 
				
			||||||
        const cycleEnd = cycleStart + cycleDuration;
 | 
					        const cycleEnd = cycleStart + cycleDuration;
 | 
				
			||||||
@ -181,10 +213,11 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
 | 
				
			|||||||
            items.push(item);
 | 
					            items.push(item);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    delete vendorInfo.cycleDuration;
 | 
					    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
				
			||||||
 | 
					    const { cycleStart, cycleDuration, ...clientVendorInfo } = vendorInfo;
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
        VendorInfo: {
 | 
					        VendorInfo: {
 | 
				
			||||||
            ...vendorInfo,
 | 
					            ...clientVendorInfo,
 | 
				
			||||||
            ItemManifest: items,
 | 
					            ItemManifest: items,
 | 
				
			||||||
            Expiry: { $date: { $numberLong: soonestOfferExpiry.toString() } }
 | 
					            Expiry: { $date: { $numberLong: soonestOfferExpiry.toString() } }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,5 @@
 | 
				
			|||||||
import { Stats, TStatsDatabaseDocument } from "@/src/models/statsModel";
 | 
					import { Stats, TStatsDatabaseDocument } from "@/src/models/statsModel";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    IEnemy,
 | 
					 | 
				
			||||||
    IStatsAdd,
 | 
					    IStatsAdd,
 | 
				
			||||||
    IStatsMax,
 | 
					    IStatsMax,
 | 
				
			||||||
    IStatsSet,
 | 
					    IStatsSet,
 | 
				
			||||||
@ -137,16 +136,21 @@ export const updateStats = async (accountOwnerId: string, payload: IStatsUpdate)
 | 
				
			|||||||
                        case "HEADSHOT":
 | 
					                        case "HEADSHOT":
 | 
				
			||||||
                        case "KILL_ASSIST": {
 | 
					                        case "KILL_ASSIST": {
 | 
				
			||||||
                            playerStats.Enemies ??= [];
 | 
					                            playerStats.Enemies ??= [];
 | 
				
			||||||
                            const enemyStatKey = {
 | 
					                            const enemyStatKey = (
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
                                    KILL_ENEMY: "kills",
 | 
					                                    KILL_ENEMY: "kills",
 | 
				
			||||||
                                    EXECUTE_ENEMY: "executions",
 | 
					                                    EXECUTE_ENEMY: "executions",
 | 
				
			||||||
                                    HEADSHOT: "headshots",
 | 
					                                    HEADSHOT: "headshots",
 | 
				
			||||||
                                    KILL_ASSIST: "assists"
 | 
					                                    KILL_ASSIST: "assists"
 | 
				
			||||||
                            }[category] as "kills" | "executions" | "headshots" | "assists";
 | 
					                                } as const
 | 
				
			||||||
 | 
					                            )[category];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            for (const [type, count] of Object.entries(data as IUploadEntry)) {
 | 
					                            for (const [type, count] of Object.entries(data as IUploadEntry)) {
 | 
				
			||||||
                                const enemy = playerStats.Enemies.find(element => element.type === type);
 | 
					                                let enemy = playerStats.Enemies.find(element => element.type === type);
 | 
				
			||||||
                                if (enemy) {
 | 
					                                if (!enemy) {
 | 
				
			||||||
 | 
					                                    enemy = { type: type };
 | 
				
			||||||
 | 
					                                    playerStats.Enemies.push(enemy);
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
                                if (category === "KILL_ENEMY") {
 | 
					                                if (category === "KILL_ENEMY") {
 | 
				
			||||||
                                    enemy.kills ??= 0;
 | 
					                                    enemy.kills ??= 0;
 | 
				
			||||||
                                    const captureCount = (actionData as IStatsAdd)["CAPTURE_ENEMY"]?.[type];
 | 
					                                    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] ??= 0;
 | 
				
			||||||
                                    enemy[enemyStatKey] += count;
 | 
					                                    enemy[enemyStatKey] += count;
 | 
				
			||||||
                                }
 | 
					                                }
 | 
				
			||||||
                                } else {
 | 
					 | 
				
			||||||
                                    const newEnemy: IEnemy = { type: type };
 | 
					 | 
				
			||||||
                                    newEnemy[enemyStatKey] = count;
 | 
					 | 
				
			||||||
                                    playerStats.Enemies.push(newEnemy);
 | 
					 | 
				
			||||||
                                }
 | 
					 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                            break;
 | 
					                            break;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,14 @@ import { unixTimesInMs } from "@/src/constants/timeConstants";
 | 
				
			|||||||
import { config } from "@/src/services/configService";
 | 
					import { config } from "@/src/services/configService";
 | 
				
			||||||
import { CRng } from "@/src/services/rngService";
 | 
					import { CRng } from "@/src/services/rngService";
 | 
				
			||||||
import { eMissionType, ExportNightwave, ExportRegions } from "warframe-public-export-plus";
 | 
					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 = [
 | 
					const sortieBosses = [
 | 
				
			||||||
    "SORTIE_BOSS_HYENA",
 | 
					    "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[] = [
 | 
					const birthdays: number[] = [
 | 
				
			||||||
    1, // Kaya
 | 
					    1, // Kaya
 | 
				
			||||||
    45, // Lettie
 | 
					    45, // Lettie
 | 
				
			||||||
@ -604,29 +639,10 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
 | 
				
			|||||||
    if (isBeforeNextExpectedWorldStateRefresh(EPOCH + (day + 1) * 86400000)) {
 | 
					    if (isBeforeNextExpectedWorldStateRefresh(EPOCH + (day + 1) * 86400000)) {
 | 
				
			||||||
        worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(day + 1));
 | 
					        worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(day + 1));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    worldState.SeasonInfo.ActiveChallenges.push(getSeasonWeeklyChallenge(week, 0));
 | 
					    pushWeeklyActs(worldState, week);
 | 
				
			||||||
    worldState.SeasonInfo.ActiveChallenges.push(getSeasonWeeklyChallenge(week, 1));
 | 
					    if (isBeforeNextExpectedWorldStateRefresh(weekEnd)) {
 | 
				
			||||||
    worldState.SeasonInfo.ActiveChallenges.push(getSeasonWeeklyHardChallenge(week, 2));
 | 
					        pushWeeklyActs(worldState, week + 1);
 | 
				
			||||||
    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
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Elite Sanctuary Onslaught cycling every week
 | 
					    // Elite Sanctuary Onslaught cycling every week
 | 
				
			||||||
    worldState.NodeOverrides.find(x => x.Node == "SolNode802")!.Seed = week; // unfaithful
 | 
					    worldState.NodeOverrides.find(x => x.Node == "SolNode802")!.Seed = week; // unfaithful
 | 
				
			||||||
@ -941,65 +957,9 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
 | 
				
			|||||||
    pushSortieIfRelevant(worldState.Sorties, day);
 | 
					    pushSortieIfRelevant(worldState.Sorties, day);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Archon Hunt cycling every week
 | 
					    // Archon Hunt cycling every week
 | 
				
			||||||
    // TODO: Handle imminent rollover
 | 
					    worldState.LiteSorties.push(getLiteSortie(week));
 | 
				
			||||||
    {
 | 
					    if (isBeforeNextExpectedWorldStateRefresh(weekEnd)) {
 | 
				
			||||||
        const boss = ["SORTIE_BOSS_AMAR", "SORTIE_BOSS_NIRA", "SORTIE_BOSS_BOREAL"][week % 3];
 | 
					        worldState.LiteSorties.push(getLiteSortie(week + 1));
 | 
				
			||||||
        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
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            ]
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Circuit choices cycling every week
 | 
					    // Circuit choices cycling every week
 | 
				
			||||||
@ -1071,3 +1031,70 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return worldState;
 | 
					    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[];
 | 
					    ShipDecorationsContributed?: ITypeCount[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IFriendInfo {
 | 
					export interface IFriendInfo {
 | 
				
			||||||
    _id: IOid;
 | 
					    _id: IOid;
 | 
				
			||||||
    DisplayName?: string;
 | 
					    DisplayName?: string;
 | 
				
			||||||
    PlatformNames?: string[];
 | 
					    PlatformNames?: string[];
 | 
				
			||||||
    PlatformAccountId?: string;
 | 
					    PlatformAccountId?: string;
 | 
				
			||||||
    Status: number;
 | 
					    Status?: number;
 | 
				
			||||||
    ActiveAvatarImageType?: string;
 | 
					    ActiveAvatarImageType?: string;
 | 
				
			||||||
    LastLogin?: IMongoDate;
 | 
					    LastLogin?: IMongoDate;
 | 
				
			||||||
    PlayerLevel?: number;
 | 
					    PlayerLevel?: number;
 | 
				
			||||||
 | 
				
			|||||||
@ -49,6 +49,8 @@ export interface IInventoryDatabase
 | 
				
			|||||||
            | "PersonalTechProjects"
 | 
					            | "PersonalTechProjects"
 | 
				
			||||||
            | "LastSortieReward"
 | 
					            | "LastSortieReward"
 | 
				
			||||||
            | "LastLiteSortieReward"
 | 
					            | "LastLiteSortieReward"
 | 
				
			||||||
 | 
					            | "CrewMembers"
 | 
				
			||||||
 | 
					            | "QualifyingInvasions"
 | 
				
			||||||
            | TEquipmentKey
 | 
					            | TEquipmentKey
 | 
				
			||||||
        >,
 | 
					        >,
 | 
				
			||||||
        InventoryDatabaseEquipment {
 | 
					        InventoryDatabaseEquipment {
 | 
				
			||||||
@ -83,6 +85,8 @@ export interface IInventoryDatabase
 | 
				
			|||||||
    PersonalTechProjects: IPersonalTechProjectDatabase[];
 | 
					    PersonalTechProjects: IPersonalTechProjectDatabase[];
 | 
				
			||||||
    LastSortieReward?: ILastSortieRewardDatabase[];
 | 
					    LastSortieReward?: ILastSortieRewardDatabase[];
 | 
				
			||||||
    LastLiteSortieReward?: ILastSortieRewardDatabase[];
 | 
					    LastLiteSortieReward?: ILastSortieRewardDatabase[];
 | 
				
			||||||
 | 
					    CrewMembers: ICrewMemberDatabase[];
 | 
				
			||||||
 | 
					    QualifyingInvasions: IInvasionProgressDatabase[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IQuestKeyDatabase {
 | 
					export interface IQuestKeyDatabase {
 | 
				
			||||||
@ -246,9 +250,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
 | 
				
			|||||||
    Guide?: number;
 | 
					    Guide?: number;
 | 
				
			||||||
    Moderator?: boolean;
 | 
					    Moderator?: boolean;
 | 
				
			||||||
    Partner?: boolean;
 | 
					    Partner?: boolean;
 | 
				
			||||||
    Accolades?: {
 | 
					    Accolades?: IAccolades;
 | 
				
			||||||
        Heirloom?: boolean;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    Counselor?: boolean;
 | 
					    Counselor?: boolean;
 | 
				
			||||||
    Upgrades: IUpgradeClient[];
 | 
					    Upgrades: IUpgradeClient[];
 | 
				
			||||||
    EquippedGear: string[];
 | 
					    EquippedGear: string[];
 | 
				
			||||||
@ -270,7 +272,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
 | 
				
			|||||||
    SentientSpawnChanceBoosters: ISentientSpawnChanceBoosters;
 | 
					    SentientSpawnChanceBoosters: ISentientSpawnChanceBoosters;
 | 
				
			||||||
    SupportedSyndicate?: string;
 | 
					    SupportedSyndicate?: string;
 | 
				
			||||||
    Affiliations: IAffiliation[];
 | 
					    Affiliations: IAffiliation[];
 | 
				
			||||||
    QualifyingInvasions: any[];
 | 
					    QualifyingInvasions: IInvasionProgressClient[];
 | 
				
			||||||
    FactionScores: number[];
 | 
					    FactionScores: number[];
 | 
				
			||||||
    ArchwingEnabled?: boolean;
 | 
					    ArchwingEnabled?: boolean;
 | 
				
			||||||
    PendingSpectreLoadouts?: ISpectreLoadout[];
 | 
					    PendingSpectreLoadouts?: ISpectreLoadout[];
 | 
				
			||||||
@ -283,6 +285,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
 | 
				
			|||||||
    CompletedSorties: string[];
 | 
					    CompletedSorties: string[];
 | 
				
			||||||
    LastSortieReward?: ILastSortieRewardClient[];
 | 
					    LastSortieReward?: ILastSortieRewardClient[];
 | 
				
			||||||
    LastLiteSortieReward?: ILastSortieRewardClient[];
 | 
					    LastLiteSortieReward?: ILastSortieRewardClient[];
 | 
				
			||||||
 | 
					    SortieRewardAttenuation?: ISortieRewardAttenuation[];
 | 
				
			||||||
    Drones: IDroneClient[];
 | 
					    Drones: IDroneClient[];
 | 
				
			||||||
    StepSequencers: IStepSequencer[];
 | 
					    StepSequencers: IStepSequencer[];
 | 
				
			||||||
    ActiveAvatarImageType: string;
 | 
					    ActiveAvatarImageType: string;
 | 
				
			||||||
@ -324,7 +327,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
 | 
				
			|||||||
    InfestedFoundry?: IInfestedFoundryClient;
 | 
					    InfestedFoundry?: IInfestedFoundryClient;
 | 
				
			||||||
    BlessingCooldown?: IMongoDate;
 | 
					    BlessingCooldown?: IMongoDate;
 | 
				
			||||||
    CrewShipRawSalvage: ITypeCount[];
 | 
					    CrewShipRawSalvage: ITypeCount[];
 | 
				
			||||||
    CrewMembers: ICrewMember[];
 | 
					    CrewMembers: ICrewMemberClient[];
 | 
				
			||||||
    LotusCustomization: ILotusCustomization;
 | 
					    LotusCustomization: ILotusCustomization;
 | 
				
			||||||
    UseAdultOperatorLoadout?: boolean;
 | 
					    UseAdultOperatorLoadout?: boolean;
 | 
				
			||||||
    NemesisAbandonedRewards: string[];
 | 
					    NemesisAbandonedRewards: string[];
 | 
				
			||||||
@ -461,32 +464,36 @@ export interface ICompletedJob {
 | 
				
			|||||||
    StageCompletions: number[];
 | 
					    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;
 | 
					    ItemType: string;
 | 
				
			||||||
    NemesisFingerprint: number;
 | 
					    NemesisFingerprint: bigint;
 | 
				
			||||||
    Seed: number;
 | 
					    Seed: bigint;
 | 
				
			||||||
    HireDate: IMongoDate;
 | 
					    AssignedRole?: number;
 | 
				
			||||||
    AssignedRole: number;
 | 
					    SkillEfficiency: ICrewMemberSkillEfficiency;
 | 
				
			||||||
    SkillEfficiency: ISkillEfficiency;
 | 
					 | 
				
			||||||
    WeaponConfigIdx: number;
 | 
					    WeaponConfigIdx: number;
 | 
				
			||||||
    WeaponId: IOid;
 | 
					    WeaponId: IOid;
 | 
				
			||||||
    XP: number;
 | 
					    XP: number;
 | 
				
			||||||
    PowersuitType: string;
 | 
					    PowersuitType: string;
 | 
				
			||||||
    Configs: IItemConfig[];
 | 
					    Configs: IItemConfig[];
 | 
				
			||||||
    SecondInCommand: boolean;
 | 
					    SecondInCommand: boolean; // on call
 | 
				
			||||||
    ItemId: IOid;
 | 
					    ItemId: IOid;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ISkillEfficiency {
 | 
					export interface ICrewMemberDatabase extends Omit<ICrewMemberClient, "WeaponId" | "ItemId"> {
 | 
				
			||||||
    PILOTING: ICombat;
 | 
					    WeaponId: Types.ObjectId;
 | 
				
			||||||
    GUNNERY: ICombat;
 | 
					    _id: Types.ObjectId;
 | 
				
			||||||
    ENGINEERING: ICombat;
 | 
					 | 
				
			||||||
    COMBAT: ICombat;
 | 
					 | 
				
			||||||
    SURVIVABILITY: ICombat;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface ICombat {
 | 
					 | 
				
			||||||
    Assigned: number;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export enum InventorySlot {
 | 
					export enum InventorySlot {
 | 
				
			||||||
@ -532,12 +539,12 @@ export interface ICrewShipMembersDatabase {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export interface ICrewShipMemberClient {
 | 
					export interface ICrewShipMemberClient {
 | 
				
			||||||
    ItemId?: IOid;
 | 
					    ItemId?: IOid;
 | 
				
			||||||
    NemesisFingerprint?: number;
 | 
					    NemesisFingerprint?: number | bigint;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ICrewShipMemberDatabase {
 | 
					export interface ICrewShipMemberDatabase {
 | 
				
			||||||
    ItemId?: Types.ObjectId;
 | 
					    ItemId?: Types.ObjectId;
 | 
				
			||||||
    NemesisFingerprint?: number;
 | 
					    NemesisFingerprint?: bigint;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ICrewShipCustomization {
 | 
					export interface ICrewShipCustomization {
 | 
				
			||||||
@ -562,17 +569,18 @@ export type IMiscItem = ITypeCount;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// inventory.CrewShips[0].Weapon
 | 
					// inventory.CrewShips[0].Weapon
 | 
				
			||||||
export interface ICrewShipWeapon {
 | 
					export interface ICrewShipWeapon {
 | 
				
			||||||
    PILOT: ICrewShipPilotWeapon;
 | 
					    PILOT?: ICrewShipWeaponEmplacements;
 | 
				
			||||||
    PORT_GUNS: ICrewShipPortGuns;
 | 
					    PORT_GUNS?: ICrewShipWeaponEmplacements;
 | 
				
			||||||
 | 
					    STARBOARD_GUNS?: ICrewShipWeaponEmplacements;
 | 
				
			||||||
 | 
					    ARTILLERY?: ICrewShipWeaponEmplacements;
 | 
				
			||||||
 | 
					    SCANNER?: ICrewShipWeaponEmplacements;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ICrewShipPilotWeapon {
 | 
					export interface ICrewShipWeaponEmplacements {
 | 
				
			||||||
    PRIMARY_A: IEquipmentSelection;
 | 
					    PRIMARY_A?: IEquipmentSelection;
 | 
				
			||||||
    SECONDARY_A: IEquipmentSelection;
 | 
					    PRIMARY_B?: IEquipmentSelection;
 | 
				
			||||||
}
 | 
					    SECONDARY_A?: IEquipmentSelection;
 | 
				
			||||||
 | 
					    SECONDARY_B?: IEquipmentSelection;
 | 
				
			||||||
export interface ICrewShipPortGuns {
 | 
					 | 
				
			||||||
    PRIMARY_A: IEquipmentSelection;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IDiscoveredMarker {
 | 
					export interface IDiscoveredMarker {
 | 
				
			||||||
@ -668,6 +676,17 @@ export interface IInvasionChainProgress {
 | 
				
			|||||||
    count: number;
 | 
					    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 {
 | 
					export interface IKubrowPetEggClient {
 | 
				
			||||||
    ItemType: string;
 | 
					    ItemType: string;
 | 
				
			||||||
    ExpirationDate: IMongoDate; // seems to be set to 7 days ahead @ 0 UTC
 | 
					    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;
 | 
					    SortieId: Types.ObjectId;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ISortieRewardAttenuation {
 | 
				
			||||||
 | 
					    Tag: string;
 | 
				
			||||||
 | 
					    Atten: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ILibraryDailyTaskInfo {
 | 
					export interface ILibraryDailyTaskInfo {
 | 
				
			||||||
    EnemyTypes: string[];
 | 
					    EnemyTypes: string[];
 | 
				
			||||||
    EnemyLocTag: string;
 | 
					    EnemyLocTag: string;
 | 
				
			||||||
@ -888,6 +912,10 @@ export interface IPendingRecipeClient
 | 
				
			|||||||
    CompletionDate: IMongoDate;
 | 
					    CompletionDate: IMongoDate;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IAccolades {
 | 
				
			||||||
 | 
					    Heirloom?: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IPendingTrade {
 | 
					export interface IPendingTrade {
 | 
				
			||||||
    State: number;
 | 
					    State: number;
 | 
				
			||||||
    SelfReady: boolean;
 | 
					    SelfReady: boolean;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,5 @@
 | 
				
			|||||||
 | 
					import { Types } from "mongoose";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IAccountAndLoginResponseCommons {
 | 
					export interface IAccountAndLoginResponseCommons {
 | 
				
			||||||
    DisplayName: string;
 | 
					    DisplayName: string;
 | 
				
			||||||
    CountryCode: string;
 | 
					    CountryCode: string;
 | 
				
			||||||
@ -56,3 +58,8 @@ export interface IGroup {
 | 
				
			|||||||
    experiment: string;
 | 
					    experiment: string;
 | 
				
			||||||
    experimentGroup: string;
 | 
					    experimentGroup: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IIgnore {
 | 
				
			||||||
 | 
					    ignorer: Types.ObjectId;
 | 
				
			||||||
 | 
					    ignoree: Types.ObjectId;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -6,7 +6,8 @@ import {
 | 
				
			|||||||
    INemesisClient,
 | 
					    INemesisClient,
 | 
				
			||||||
    ITypeCount,
 | 
					    ITypeCount,
 | 
				
			||||||
    IRecentVendorPurchaseClient,
 | 
					    IRecentVendorPurchaseClient,
 | 
				
			||||||
    TEquipmentKey
 | 
					    TEquipmentKey,
 | 
				
			||||||
 | 
					    ICrewMemberClient
 | 
				
			||||||
} from "./inventoryTypes/inventoryTypes";
 | 
					} from "./inventoryTypes/inventoryTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IPurchaseRequest {
 | 
					export interface IPurchaseRequest {
 | 
				
			||||||
@ -47,6 +48,7 @@ export type IInventoryChanges = {
 | 
				
			|||||||
    Nemesis?: Partial<INemesisClient>;
 | 
					    Nemesis?: Partial<INemesisClient>;
 | 
				
			||||||
    NewVendorPurchase?: IRecentVendorPurchaseClient; // >= 38.5.0
 | 
					    NewVendorPurchase?: IRecentVendorPurchaseClient; // >= 38.5.0
 | 
				
			||||||
    RecentVendorPurchases?: IRecentVendorPurchaseClient; // < 38.5.0
 | 
					    RecentVendorPurchases?: IRecentVendorPurchaseClient; // < 38.5.0
 | 
				
			||||||
 | 
					    CrewMembers?: ICrewMemberClient[];
 | 
				
			||||||
} & Record<
 | 
					} & Record<
 | 
				
			||||||
        Exclude<
 | 
					        Exclude<
 | 
				
			||||||
            string,
 | 
					            string,
 | 
				
			||||||
 | 
				
			|||||||
@ -19,7 +19,8 @@ import {
 | 
				
			|||||||
    ICollectibleEntry,
 | 
					    ICollectibleEntry,
 | 
				
			||||||
    IDiscoveredMarker,
 | 
					    IDiscoveredMarker,
 | 
				
			||||||
    ILockedWeaponGroupClient,
 | 
					    ILockedWeaponGroupClient,
 | 
				
			||||||
    ILoadOutPresets
 | 
					    ILoadOutPresets,
 | 
				
			||||||
 | 
					    IInvasionProgressClient
 | 
				
			||||||
} from "./inventoryTypes/inventoryTypes";
 | 
					} from "./inventoryTypes/inventoryTypes";
 | 
				
			||||||
import { IGroup } from "./loginTypes";
 | 
					import { IGroup } from "./loginTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -123,12 +124,15 @@ export type IMissionInventoryUpdateRequest = {
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
    wagerTier?: number; // the index
 | 
					    wagerTier?: number; // the index
 | 
				
			||||||
    creditsFee?: number; // the index
 | 
					    creditsFee?: number; // the index
 | 
				
			||||||
 | 
					    InvasionProgress?: IInvasionProgressClient[];
 | 
				
			||||||
} & {
 | 
					} & {
 | 
				
			||||||
    [K in TEquipmentKey]?: IEquipmentClient[];
 | 
					    [K in TEquipmentKey]?: IEquipmentClient[];
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IRewardInfo {
 | 
					export interface IRewardInfo {
 | 
				
			||||||
    node: string;
 | 
					    node: string;
 | 
				
			||||||
 | 
					    invasionId?: string;
 | 
				
			||||||
 | 
					    invasionAllyFaction?: "FC_GRINEER" | "FC_CORPUS";
 | 
				
			||||||
    sortieId?: string;
 | 
					    sortieId?: string;
 | 
				
			||||||
    sortieTag?: string;
 | 
					    sortieTag?: string;
 | 
				
			||||||
    sortiePrereqs?: string[];
 | 
					    sortiePrereqs?: string[];
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,13 @@
 | 
				
			|||||||
import { IOid } from "@/src/types/commonTypes";
 | 
					import { IOid } from "@/src/types/commonTypes";
 | 
				
			||||||
import { IItemConfig, IOperatorConfigClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
					import { IItemConfig, IOperatorConfigClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
				
			||||||
import { Types } from "mongoose";
 | 
					import { Types } from "mongoose";
 | 
				
			||||||
import { ILoadoutConfigClient } from "./inventoryTypes/inventoryTypes";
 | 
					import {
 | 
				
			||||||
 | 
					    ICrewShipCustomization,
 | 
				
			||||||
 | 
					    ICrewShipMembersClient,
 | 
				
			||||||
 | 
					    ICrewShipWeapon,
 | 
				
			||||||
 | 
					    IFlavourItem,
 | 
				
			||||||
 | 
					    ILoadoutConfigClient
 | 
				
			||||||
 | 
					} from "./inventoryTypes/inventoryTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ISaveLoadoutRequest {
 | 
					export interface ISaveLoadoutRequest {
 | 
				
			||||||
    LoadOuts: ILoadoutClient;
 | 
					    LoadOuts: ILoadoutClient;
 | 
				
			||||||
@ -51,7 +57,16 @@ export interface IItemEntry {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export type IConfigEntry = {
 | 
					export type IConfigEntry = {
 | 
				
			||||||
    [configId in "0" | "1" | "2" | "3" | "4" | "5"]: IItemConfig;
 | 
					    [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">;
 | 
					export type ILoadoutClient = Omit<ILoadoutDatabase, "_id" | "loadoutOwnerId">;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -32,7 +32,7 @@ export interface IVendorInfo {
 | 
				
			|||||||
    TypeName: string;
 | 
					    TypeName: string;
 | 
				
			||||||
    ItemManifest: IItemManifest[];
 | 
					    ItemManifest: IItemManifest[];
 | 
				
			||||||
    PropertyTextHash?: string;
 | 
					    PropertyTextHash?: string;
 | 
				
			||||||
    RandomSeedType?: "VRST_WEAPON";
 | 
					    RandomSeedType?: string;
 | 
				
			||||||
    RequiredGoalTag?: string;
 | 
					    RequiredGoalTag?: string;
 | 
				
			||||||
    WeaponUpgradeValueAttenuationExponent?: number;
 | 
					    WeaponUpgradeValueAttenuationExponent?: number;
 | 
				
			||||||
    Expiry: IMongoDate; // Either a date in the distant future or a period in milliseconds for preprocessing.
 | 
					    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;
 | 
					    Expiry: IMongoDate;
 | 
				
			||||||
    Reward: "/Lotus/Types/Game/MissionDecks/ArchonSortieRewards";
 | 
					    Reward: "/Lotus/Types/Game/MissionDecks/ArchonSortieRewards";
 | 
				
			||||||
    Seed: number;
 | 
					    Seed: number;
 | 
				
			||||||
    Boss: string; // "SORTIE_BOSS_AMAR" | "SORTIE_BOSS_NIRA" | "SORTIE_BOSS_BOREAL"
 | 
					    Boss: "SORTIE_BOSS_AMAR" | "SORTIE_BOSS_NIRA" | "SORTIE_BOSS_BOREAL";
 | 
				
			||||||
    Missions: {
 | 
					    Missions: {
 | 
				
			||||||
        missionType: string;
 | 
					        missionType: string;
 | 
				
			||||||
        node: 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> => {
 | 
					export const parallelForeach = async <T>(data: T[], op: (datum: T) => Promise<void>): Promise<void> => {
 | 
				
			||||||
    const promises: Promise<void>[] = [];
 | 
					    const promises: Promise<void>[] = [];
 | 
				
			||||||
    for (const datum of data) {
 | 
					    for (const datum of data) {
 | 
				
			||||||
 | 
				
			|||||||
@ -1092,5 +1092,11 @@
 | 
				
			|||||||
  "/Lotus/Types/Game/CrewShip/GrineerDestroyer/GrineerDestroyerAvatar",
 | 
					  "/Lotus/Types/Game/CrewShip/GrineerDestroyer/GrineerDestroyerAvatar",
 | 
				
			||||||
  "/Lotus/Types/LevelObjects/Zariman/ZarLootCrateUltraRare",
 | 
					  "/Lotus/Types/LevelObjects/Zariman/ZarLootCrateUltraRare",
 | 
				
			||||||
  "/Lotus/Objects/DomestikDrone/GrineerOceanDomestikDroneMover",
 | 
					  "/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.href = "#";
 | 
				
			||||||
                            a.onclick = function (event) {
 | 
					                            a.onclick = function (event) {
 | 
				
			||||||
                                event.preventDefault();
 | 
					                                event.preventDefault();
 | 
				
			||||||
 | 
					                                document.getElementById(category + "-list").removeChild(tr);
 | 
				
			||||||
                                disposeOfGear(category, item.ItemId.$oid);
 | 
					                                disposeOfGear(category, item.ItemId.$oid);
 | 
				
			||||||
                            };
 | 
					                            };
 | 
				
			||||||
                            a.title = loc("code_remove");
 | 
					                            a.title = loc("code_remove");
 | 
				
			||||||
@ -683,6 +684,7 @@ function updateInventory() {
 | 
				
			|||||||
                                a.href = "#";
 | 
					                                a.href = "#";
 | 
				
			||||||
                                a.onclick = function (event) {
 | 
					                                a.onclick = function (event) {
 | 
				
			||||||
                                    event.preventDefault();
 | 
					                                    event.preventDefault();
 | 
				
			||||||
 | 
					                                    document.getElementById("riven-list").removeChild(tr);
 | 
				
			||||||
                                    disposeOfGear("Upgrades", item.ItemId.$oid);
 | 
					                                    disposeOfGear("Upgrades", item.ItemId.$oid);
 | 
				
			||||||
                                };
 | 
					                                };
 | 
				
			||||||
                                a.title = loc("code_remove");
 | 
					                                a.title = loc("code_remove");
 | 
				
			||||||
@ -723,6 +725,7 @@ function updateInventory() {
 | 
				
			|||||||
                        a.href = "#";
 | 
					                        a.href = "#";
 | 
				
			||||||
                        a.onclick = function (event) {
 | 
					                        a.onclick = function (event) {
 | 
				
			||||||
                            event.preventDefault();
 | 
					                            event.preventDefault();
 | 
				
			||||||
 | 
					                            document.getElementById("mods-list").removeChild(tr);
 | 
				
			||||||
                            disposeOfGear("Upgrades", item.ItemId.$oid);
 | 
					                            disposeOfGear("Upgrades", item.ItemId.$oid);
 | 
				
			||||||
                        };
 | 
					                        };
 | 
				
			||||||
                        a.title = loc("code_remove");
 | 
					                        a.title = loc("code_remove");
 | 
				
			||||||
@ -765,6 +768,7 @@ function updateInventory() {
 | 
				
			|||||||
                            a.href = "#";
 | 
					                            a.href = "#";
 | 
				
			||||||
                            a.onclick = function (event) {
 | 
					                            a.onclick = function (event) {
 | 
				
			||||||
                                event.preventDefault();
 | 
					                                event.preventDefault();
 | 
				
			||||||
 | 
					                                document.getElementById("mods-list").removeChild(tr);
 | 
				
			||||||
                                disposeOfItems("Upgrades", item.ItemType, item.ItemCount);
 | 
					                                disposeOfItems("Upgrades", item.ItemType, item.ItemCount);
 | 
				
			||||||
                            };
 | 
					                            };
 | 
				
			||||||
                            a.title = loc("code_remove");
 | 
					                            a.title = loc("code_remove");
 | 
				
			||||||
@ -1097,8 +1101,6 @@ function disposeOfGear(category, oid) {
 | 
				
			|||||||
            url: "/api/sell.php?" + window.authz,
 | 
					            url: "/api/sell.php?" + window.authz,
 | 
				
			||||||
            contentType: "text/plain",
 | 
					            contentType: "text/plain",
 | 
				
			||||||
            data: JSON.stringify(data)
 | 
					            data: JSON.stringify(data)
 | 
				
			||||||
        }).done(function () {
 | 
					 | 
				
			||||||
            updateInventory();
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -1120,8 +1122,6 @@ function disposeOfItems(category, type, count) {
 | 
				
			|||||||
            url: "/api/sell.php?" + window.authz,
 | 
					            url: "/api/sell.php?" + window.authz,
 | 
				
			||||||
            contentType: "text/plain",
 | 
					            contentType: "text/plain",
 | 
				
			||||||
            data: JSON.stringify(data)
 | 
					            data: JSON.stringify(data)
 | 
				
			||||||
        }).done(function () {
 | 
					 | 
				
			||||||
            updateInventory();
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -1260,6 +1260,8 @@ function doAcquireMod() {
 | 
				
			|||||||
        $("#mod-to-acquire").addClass("is-invalid").focus();
 | 
					        $("#mod-to-acquire").addClass("is-invalid").focus();
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    const count = parseInt($("#mod-count").val());
 | 
				
			||||||
 | 
					    if (count != 0) {
 | 
				
			||||||
        revalidateAuthz(() => {
 | 
					        revalidateAuthz(() => {
 | 
				
			||||||
            $.post({
 | 
					            $.post({
 | 
				
			||||||
                url: "/custom/addItems?" + window.authz,
 | 
					                url: "/custom/addItems?" + window.authz,
 | 
				
			||||||
@ -1267,15 +1269,20 @@ function doAcquireMod() {
 | 
				
			|||||||
                data: JSON.stringify([
 | 
					                data: JSON.stringify([
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        ItemType: uniqueName,
 | 
					                        ItemType: uniqueName,
 | 
				
			||||||
                    ItemCount: parseInt($("#mod-count").val())
 | 
					                        ItemCount: count
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                ])
 | 
					                ])
 | 
				
			||||||
            }).done(function () {
 | 
					            }).done(function () {
 | 
				
			||||||
            document.getElementById("mod-to-acquire").value = "";
 | 
					                if (count > 0) {
 | 
				
			||||||
 | 
					                    toast(loc("code_succAdded"));
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    toast(loc("code_succRemoved"));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                updateInventory();
 | 
					                updateInventory();
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const uiConfigs = [...$("#server-settings input[id]")].map(x => x.id);
 | 
					const uiConfigs = [...$("#server-settings input[id]")].map(x => x.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1315,7 +1322,7 @@ single.getRoute("/webui/cheats").on("beforeload", function () {
 | 
				
			|||||||
    interval = setInterval(() => {
 | 
					    interval = setInterval(() => {
 | 
				
			||||||
        if (window.authz) {
 | 
					        if (window.authz) {
 | 
				
			||||||
            clearInterval(interval);
 | 
					            clearInterval(interval);
 | 
				
			||||||
            fetch("/custom/config?" + window.authz).then(res => {
 | 
					            fetch("/custom/config?" + window.authz).then(async res => {
 | 
				
			||||||
                if (res.status == 200) {
 | 
					                if (res.status == 200) {
 | 
				
			||||||
                    $("#server-settings-no-perms").addClass("d-none");
 | 
					                    $("#server-settings-no-perms").addClass("d-none");
 | 
				
			||||||
                    $("#server-settings").removeClass("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 {
 | 
					                    } else {
 | 
				
			||||||
                        $("#server-settings-no-perms").removeClass("d-none");
 | 
					                        $("#server-settings-no-perms").removeClass("d-none");
 | 
				
			||||||
                        $("#server-settings").addClass("d-none");
 | 
					                        $("#server-settings").addClass("d-none");
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }, 10);
 | 
					    }, 10);
 | 
				
			||||||
 | 
				
			|||||||
@ -138,7 +138,7 @@ dict = {
 | 
				
			|||||||
    cheats_noArgonCrystalDecay: `Argon-Kristalle verschwinden niemals`,
 | 
					    cheats_noArgonCrystalDecay: `Argon-Kristalle verschwinden niemals`,
 | 
				
			||||||
    cheats_noMasteryRankUpCooldown: `Keine Wartezeit beim Meisterschaftsrangaufstieg`,
 | 
					    cheats_noMasteryRankUpCooldown: `Keine Wartezeit beim Meisterschaftsrangaufstieg`,
 | 
				
			||||||
    cheats_noVendorPurchaseLimits: `Keine Kaufbeschränkungen bei Händlern`,
 | 
					    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_noKimCooldowns: `Keine Wartezeit bei KIM`,
 | 
				
			||||||
    cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`,
 | 
					    cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`,
 | 
				
			||||||
    cheats_noResourceExtractorDronesDamage: `Kein Schaden für Ressourcen-Extraktor-Drohnen`,
 | 
					    cheats_noResourceExtractorDronesDamage: `Kein Schaden für Ressourcen-Extraktor-Drohnen`,
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user