chore: update docker stuff #1961
@ -11,9 +11,10 @@ import {
 | 
			
		||||
import { getAccountForRequest, getAccountFromSuffixedName, getSuffixedName } from "@/src/services/loginService";
 | 
			
		||||
import { addItems, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
 | 
			
		||||
import { logger } from "@/src/utils/logger";
 | 
			
		||||
import { ExportFlavour, ExportGear } from "warframe-public-export-plus";
 | 
			
		||||
import { ExportFlavour } from "warframe-public-export-plus";
 | 
			
		||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
 | 
			
		||||
import { fromStoreItem, isStoreItem } from "@/src/services/itemDataService";
 | 
			
		||||
import { IOid } from "@/src/types/commonTypes";
 | 
			
		||||
 | 
			
		||||
export const inboxController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query;
 | 
			
		||||
@ -28,10 +29,10 @@ export const inboxController: RequestHandler = async (req, res) => {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await deleteMessageRead(deleteId as string);
 | 
			
		||||
        await deleteMessageRead(parseOid(deleteId as string));
 | 
			
		||||
        res.status(200).end();
 | 
			
		||||
    } else if (messageId) {
 | 
			
		||||
        const message = await getMessage(messageId as string);
 | 
			
		||||
        const message = await getMessage(parseOid(messageId as string));
 | 
			
		||||
        message.r = true;
 | 
			
		||||
        await message.save();
 | 
			
		||||
 | 
			
		||||
@ -50,7 +51,7 @@ export const inboxController: RequestHandler = async (req, res) => {
 | 
			
		||||
                inventory,
 | 
			
		||||
                attachmentItems.map(attItem => ({
 | 
			
		||||
                    ItemType: isStoreItem(attItem) ? fromStoreItem(attItem) : attItem,
 | 
			
		||||
                    ItemCount: attItem in ExportGear ? (ExportGear[attItem].purchaseQuantity ?? 1) : 1
 | 
			
		||||
                    ItemCount: 1
 | 
			
		||||
                })),
 | 
			
		||||
                inventoryChanges
 | 
			
		||||
            );
 | 
			
		||||
@ -100,7 +101,7 @@ export const inboxController: RequestHandler = async (req, res) => {
 | 
			
		||||
        await createNewEventMessages(req);
 | 
			
		||||
        const messages = await Inbox.find({ ownerId: accountId }).sort({ date: 1 });
 | 
			
		||||
 | 
			
		||||
        const latestClientMessage = messages.find(m => m._id.toString() === latestClientMessageId);
 | 
			
		||||
        const latestClientMessage = messages.find(m => m._id.toString() === parseOid(latestClientMessageId as string));
 | 
			
		||||
 | 
			
		||||
        if (!latestClientMessage) {
 | 
			
		||||
            logger.debug(`this should only happen after DeleteAllRead `);
 | 
			
		||||
@ -123,3 +124,11 @@ export const inboxController: RequestHandler = async (req, res) => {
 | 
			
		||||
        res.json({ Inbox: inbox });
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 33.6.0 has query arguments like lastMessage={"$oid":"68112baebf192e786d1502bb"} instead of lastMessage=68112baebf192e786d1502bb
 | 
			
		||||
const parseOid = (oid: string): string => {
 | 
			
		||||
    if (oid[0] == "{") {
 | 
			
		||||
        return (JSON.parse(oid) as IOid).$oid;
 | 
			
		||||
    }
 | 
			
		||||
    return oid;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -106,9 +106,16 @@ export const inventoryController: RequestHandler = async (request, response) =>
 | 
			
		||||
        const currentDuviriMood = Math.trunc(Date.now() / 7200000);
 | 
			
		||||
        if (lastSyncDuviriMood != currentDuviriMood) {
 | 
			
		||||
            logger.debug(`refreshing duviri seed`);
 | 
			
		||||
            if (!inventory.DuviriInfo) {
 | 
			
		||||
                inventory.DuviriInfo = {
 | 
			
		||||
                    Seed: generateRewardSeed(),
 | 
			
		||||
                    NumCompletions: 0
 | 
			
		||||
                };
 | 
			
		||||
            } else {
 | 
			
		||||
                inventory.DuviriInfo.Seed = generateRewardSeed();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    inventory.LastInventorySync = new Types.ObjectId();
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@ import { Account } from "@/src/models/loginModel";
 | 
			
		||||
import { createAccount, isCorrectPassword, isNameTaken } from "@/src/services/loginService";
 | 
			
		||||
import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes";
 | 
			
		||||
import { logger } from "@/src/utils/logger";
 | 
			
		||||
import { version_compare } from "@/src/services/worldStateService";
 | 
			
		||||
 | 
			
		||||
export const loginController: RequestHandler = async (request, response) => {
 | 
			
		||||
    const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
 | 
			
		||||
@ -94,12 +95,10 @@ export const loginController: RequestHandler = async (request, response) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const createLoginResponse = (myAddress: string, account: IDatabaseAccountJson, buildLabel: string): ILoginResponse => {
 | 
			
		||||
    return {
 | 
			
		||||
    const resp: ILoginResponse = {
 | 
			
		||||
        id: account.id,
 | 
			
		||||
        DisplayName: account.DisplayName,
 | 
			
		||||
        CountryCode: account.CountryCode,
 | 
			
		||||
        ClientType: account.ClientType,
 | 
			
		||||
        CrossPlatformAllowed: account.CrossPlatformAllowed,
 | 
			
		||||
        ForceLogoutVersion: account.ForceLogoutVersion,
 | 
			
		||||
        AmazonAuthToken: account.AmazonAuthToken,
 | 
			
		||||
        AmazonRefreshToken: account.AmazonRefreshToken,
 | 
			
		||||
@ -108,11 +107,20 @@ const createLoginResponse = (myAddress: string, account: IDatabaseAccountJson, b
 | 
			
		||||
        Nonce: account.Nonce,
 | 
			
		||||
        Groups: [],
 | 
			
		||||
        IRC: config.myIrcAddresses ?? [myAddress],
 | 
			
		||||
        platformCDNs: [`https://${myAddress}/`],
 | 
			
		||||
        HUB: `https://${myAddress}/api/`,
 | 
			
		||||
        NRS: config.NRS,
 | 
			
		||||
        DTLS: 99,
 | 
			
		||||
        BuildLabel: buildLabel,
 | 
			
		||||
        MatchmakingBuildId: buildConfig.matchmakingBuildId
 | 
			
		||||
        BuildLabel: buildLabel
 | 
			
		||||
    };
 | 
			
		||||
    if (version_compare(buildLabel, "2022.04.29.12.53") >= 0) {
 | 
			
		||||
        resp.ClientType = account.ClientType;
 | 
			
		||||
        if (version_compare(buildLabel, "2022.09.06.19.24") >= 0) {
 | 
			
		||||
            resp.CrossPlatformAllowed = account.CrossPlatformAllowed;
 | 
			
		||||
            resp.HUB = `https://${myAddress}/api/`;
 | 
			
		||||
            resp.MatchmakingBuildId = buildConfig.matchmakingBuildId;
 | 
			
		||||
            if (version_compare(buildLabel, "2023.04.25.23.40") >= 0) {
 | 
			
		||||
                resp.platformCDNs = [`https://${myAddress}/`];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return resp;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { addGearExpByCategory, getInventory } from "@/src/services/inventoryService";
 | 
			
		||||
import { applyClientEquipmentUpdates, getInventory } from "@/src/services/inventoryService";
 | 
			
		||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
			
		||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
			
		||||
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
			
		||||
@ -20,7 +20,7 @@ export const addXpController: RequestHandler = async (req, res) => {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        addGearExpByCategory(inventory, gear, category as TEquipmentKey);
 | 
			
		||||
        applyClientEquipmentUpdates(inventory, gear, category as TEquipmentKey);
 | 
			
		||||
    }
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,10 @@ export const toMongoDate = (date: Date): IMongoDate => {
 | 
			
		||||
    return { $date: { $numberLong: date.getTime().toString() } };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const fromMongoDate = (date: IMongoDate): Date => {
 | 
			
		||||
    return new Date(parseInt(date.$date.$numberLong));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const kubrowWeights: Record<TRarity, number> = {
 | 
			
		||||
    COMMON: 6,
 | 
			
		||||
    UNCOMMON: 4,
 | 
			
		||||
 | 
			
		||||
@ -391,8 +391,8 @@ MailboxSchema.set("toJSON", {
 | 
			
		||||
 | 
			
		||||
const DuviriInfoSchema = new Schema<IDuviriInfo>(
 | 
			
		||||
    {
 | 
			
		||||
        Seed: BigInt,
 | 
			
		||||
        NumCompletions: { type: Number, default: 0 }
 | 
			
		||||
        Seed: { type: BigInt, required: true },
 | 
			
		||||
        NumCompletions: { type: Number, required: true }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        _id: false,
 | 
			
		||||
@ -1688,9 +1688,9 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
 | 
			
		||||
        //Like BossAladV,BossCaptainVor come for you on missions % chance
 | 
			
		||||
        DeathMarks: { type: [String], default: [] },
 | 
			
		||||
        //Zanuka
 | 
			
		||||
        Harvestable: Boolean,
 | 
			
		||||
        Harvestable: { type: Boolean, default: true },
 | 
			
		||||
        //Grustag three
 | 
			
		||||
        DeathSquadable: Boolean,
 | 
			
		||||
        DeathSquadable: { type: Boolean, default: true },
 | 
			
		||||
 | 
			
		||||
        EndlessXP: { type: [endlessXpProgressSchema], default: undefined },
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -69,6 +69,7 @@ import {
 | 
			
		||||
import { createShip } from "./shipService";
 | 
			
		||||
import {
 | 
			
		||||
    catbrowDetails,
 | 
			
		||||
    fromMongoDate,
 | 
			
		||||
    kubrowDetails,
 | 
			
		||||
    kubrowFurPatternsWeights,
 | 
			
		||||
    kubrowWeights,
 | 
			
		||||
@ -486,6 +487,10 @@ export const addItem = async (
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
    if (typeName in ExportGear) {
 | 
			
		||||
        // Multipling by purchase quantity for gear because:
 | 
			
		||||
        // - The Saya's Vigil scanner message has it as a non-counted attachment.
 | 
			
		||||
        // - Blueprints for Ancient Protector Specter, Shield Osprey Specter, etc. have num=1 despite giving their purchaseQuantity.
 | 
			
		||||
        quantity *= ExportGear[typeName].purchaseQuantity ?? 1;
 | 
			
		||||
        const consumablesChanges = [
 | 
			
		||||
            {
 | 
			
		||||
                ItemType: typeName,
 | 
			
		||||
@ -1471,21 +1476,20 @@ export const addEmailItem = async (
 | 
			
		||||
    return inventoryChanges;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//TODO: wrong id is not erroring
 | 
			
		||||
export const addGearExpByCategory = (
 | 
			
		||||
export const applyClientEquipmentUpdates = (
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
    gearArray: IEquipmentClient[],
 | 
			
		||||
    categoryName: TEquipmentKey
 | 
			
		||||
): void => {
 | 
			
		||||
    const category = inventory[categoryName];
 | 
			
		||||
 | 
			
		||||
    gearArray.forEach(({ ItemId, XP }) => {
 | 
			
		||||
        if (!XP) {
 | 
			
		||||
            return;
 | 
			
		||||
    gearArray.forEach(({ ItemId, XP, InfestationDate }) => {
 | 
			
		||||
        const item = category.id(ItemId.$oid);
 | 
			
		||||
        if (!item) {
 | 
			
		||||
            throw new Error(`No item with id ${ItemId.$oid} in ${categoryName}`);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const item = category.id(ItemId.$oid);
 | 
			
		||||
        if (item) {
 | 
			
		||||
        if (XP) {
 | 
			
		||||
            item.XP ??= 0;
 | 
			
		||||
            item.XP += XP;
 | 
			
		||||
 | 
			
		||||
@ -1500,6 +1504,10 @@ export const addGearExpByCategory = (
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (InfestationDate) {
 | 
			
		||||
            item.InfestationDate = fromMongoDate(InfestationDate);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,6 @@ import {
 | 
			
		||||
    addFocusXpIncreases,
 | 
			
		||||
    addFusionPoints,
 | 
			
		||||
    addFusionTreasures,
 | 
			
		||||
    addGearExpByCategory,
 | 
			
		||||
    addItem,
 | 
			
		||||
    addLevelKeys,
 | 
			
		||||
    addLoreFragmentScans,
 | 
			
		||||
@ -32,6 +31,7 @@ import {
 | 
			
		||||
    addShipDecorations,
 | 
			
		||||
    addSkin,
 | 
			
		||||
    addStanding,
 | 
			
		||||
    applyClientEquipmentUpdates,
 | 
			
		||||
    combineInventoryChanges,
 | 
			
		||||
    generateRewardSeed,
 | 
			
		||||
    getCalendarProgress,
 | 
			
		||||
@ -143,38 +143,6 @@ export const addMissionInventoryUpdates = async (
 | 
			
		||||
                ]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Somewhat heuristically detect G3 capture:
 | 
			
		||||
        // - https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1365
 | 
			
		||||
        // - https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1694
 | 
			
		||||
        // - https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1724
 | 
			
		||||
        if (
 | 
			
		||||
            inventoryUpdates.MissionFailed &&
 | 
			
		||||
            inventoryUpdates.MissionStatus == "GS_FAILURE" &&
 | 
			
		||||
            inventoryUpdates.ObjectiveReached &&
 | 
			
		||||
            !inventoryUpdates.LockedWeaponGroup &&
 | 
			
		||||
            !inventory.LockedWeaponGroup &&
 | 
			
		||||
            !inventoryUpdates.LevelKeyName
 | 
			
		||||
        ) {
 | 
			
		||||
            const loadout = (await Loadout.findById(inventory.LoadOutPresets, "NORMAL"))!;
 | 
			
		||||
            const config = loadout.NORMAL.id(inventory.CurrentLoadOutIds[0].$oid)!;
 | 
			
		||||
            const SuitId = new Types.ObjectId(config.s!.ItemId.$oid);
 | 
			
		||||
 | 
			
		||||
            inventory.BrandedSuits ??= [];
 | 
			
		||||
            if (!inventory.BrandedSuits.find(x => x.equals(SuitId))) {
 | 
			
		||||
                inventory.BrandedSuits.push(SuitId);
 | 
			
		||||
 | 
			
		||||
                await createMessage(inventory.accountOwnerId, [
 | 
			
		||||
                    {
 | 
			
		||||
                        sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
 | 
			
		||||
                        msg: "/Lotus/Language/G1Quests/BrandedMessage",
 | 
			
		||||
                        sub: "/Lotus/Language/G1Quests/BrandedTitle",
 | 
			
		||||
                        att: ["/Lotus/Types/Recipes/Components/BrandRemovalBlueprint"],
 | 
			
		||||
                        highPriority: true // TOVERIFY: I cannot find any content of this within the last 10 years so I can only assume that highPriority is set (it certainly would make sense), but I just don't know for sure that it is so on live.
 | 
			
		||||
                    }
 | 
			
		||||
                ]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (inventoryUpdates.RewardInfo) {
 | 
			
		||||
        if (inventoryUpdates.RewardInfo.periodicMissionTag) {
 | 
			
		||||
@ -537,6 +505,23 @@ export const addMissionInventoryUpdates = async (
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            case "BrandedSuits": {
 | 
			
		||||
                inventory.BrandedSuits ??= [];
 | 
			
		||||
                if (!inventory.BrandedSuits.find(x => x.equals(value.$oid))) {
 | 
			
		||||
                    inventory.BrandedSuits.push(new Types.ObjectId(value.$oid));
 | 
			
		||||
 | 
			
		||||
                    await createMessage(inventory.accountOwnerId, [
 | 
			
		||||
                        {
 | 
			
		||||
                            sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
 | 
			
		||||
                            msg: "/Lotus/Language/G1Quests/BrandedMessage",
 | 
			
		||||
                            sub: "/Lotus/Language/G1Quests/BrandedTitle",
 | 
			
		||||
                            att: ["/Lotus/Types/Recipes/Components/BrandRemovalBlueprint"],
 | 
			
		||||
                            highPriority: true // TOVERIFY: I cannot find any content of this within the last 10 years so I can only assume that highPriority is set (it certainly would make sense), but I just don't know for sure that it is so on live.
 | 
			
		||||
                        }
 | 
			
		||||
                    ]);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            case "LockedWeaponGroup": {
 | 
			
		||||
                inventory.LockedWeaponGroup = {
 | 
			
		||||
                    s: new Types.ObjectId(value.s.$oid),
 | 
			
		||||
@ -545,12 +530,17 @@ export const addMissionInventoryUpdates = async (
 | 
			
		||||
                    m: value.m ? new Types.ObjectId(value.m.$oid) : undefined,
 | 
			
		||||
                    sn: value.sn ? new Types.ObjectId(value.sn.$oid) : undefined
 | 
			
		||||
                };
 | 
			
		||||
                inventory.Harvestable = false;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            case "UnlockWeapons": {
 | 
			
		||||
                inventory.LockedWeaponGroup = undefined;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            case "IncHarvester": {
 | 
			
		||||
                inventory.Harvestable = true;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            case "CurrentLoadOutIds": {
 | 
			
		||||
                if (value.LoadOuts) {
 | 
			
		||||
                    const loadout = await Loadout.findOne({ loadoutOwnerId: inventory.accountOwnerId });
 | 
			
		||||
@ -611,7 +601,7 @@ export const addMissionInventoryUpdates = async (
 | 
			
		||||
            case "duviriCaveOffers": {
 | 
			
		||||
                // Duviri cave offers (generated with the duviri seed) change after completing one of its game modes (not when aborting).
 | 
			
		||||
                if (inventoryUpdates.MissionStatus != "GS_QUIT") {
 | 
			
		||||
                    inventory.DuviriInfo.Seed = generateRewardSeed();
 | 
			
		||||
                    inventory.DuviriInfo!.Seed = generateRewardSeed();
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
@ -670,9 +660,8 @@ export const addMissionInventoryUpdates = async (
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                // Equipment XP updates
 | 
			
		||||
                if (equipmentKeys.includes(key as TEquipmentKey)) {
 | 
			
		||||
                    addGearExpByCategory(inventory, value as IEquipmentClient[], key as TEquipmentKey);
 | 
			
		||||
                    applyClientEquipmentUpdates(inventory, value as IEquipmentClient[], key as TEquipmentKey);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            // if (
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,7 @@ export interface IRngResult {
 | 
			
		||||
    probability: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const getRandomElement = <T>(arr: T[]): T | undefined => {
 | 
			
		||||
export const getRandomElement = <T>(arr: readonly T[]): T | undefined => {
 | 
			
		||||
    return arr[Math.floor(Math.random() * arr.length)];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -113,7 +113,7 @@ export class CRng {
 | 
			
		||||
        return min;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    randomElement<T>(arr: T[]): T | undefined {
 | 
			
		||||
    randomElement<T>(arr: readonly T[]): T | undefined {
 | 
			
		||||
        return arr[Math.floor(this.random() * arr.length)];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -145,7 +145,7 @@ export class SRng {
 | 
			
		||||
        return min;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    randomElement<T>(arr: T[]): T | undefined {
 | 
			
		||||
    randomElement<T>(arr: readonly T[]): T | undefined {
 | 
			
		||||
        return arr[this.randomInt(0, arr.length - 1)];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -33,9 +33,11 @@ const sortieBosses = [
 | 
			
		||||
    "SORTIE_BOSS_LEPHANTIS",
 | 
			
		||||
    "SORTIE_BOSS_INFALAD",
 | 
			
		||||
    "SORTIE_BOSS_CORRUPTED_VOR"
 | 
			
		||||
];
 | 
			
		||||
] as const;
 | 
			
		||||
 | 
			
		||||
const sortieBossToFaction: Record<string, string> = {
 | 
			
		||||
type TSortieBoss = (typeof sortieBosses)[number];
 | 
			
		||||
 | 
			
		||||
const sortieBossToFaction: Record<TSortieBoss, string> = {
 | 
			
		||||
    SORTIE_BOSS_HYENA: "FC_CORPUS",
 | 
			
		||||
    SORTIE_BOSS_KELA: "FC_GRINEER",
 | 
			
		||||
    SORTIE_BOSS_VOR: "FC_GRINEER",
 | 
			
		||||
@ -74,21 +76,22 @@ const sortieFactionToSpecialMissionTileset: Record<string, string> = {
 | 
			
		||||
    FC_INFESTATION: "CorpusShipTileset"
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const sortieBossNode: Record<string, string> = {
 | 
			
		||||
    SORTIE_BOSS_HYENA: "SolNode127",
 | 
			
		||||
    SORTIE_BOSS_KELA: "SolNode193",
 | 
			
		||||
    SORTIE_BOSS_VOR: "SolNode108",
 | 
			
		||||
    SORTIE_BOSS_RUK: "SolNode32",
 | 
			
		||||
    SORTIE_BOSS_HEK: "SolNode24",
 | 
			
		||||
    SORTIE_BOSS_KRIL: "SolNode99",
 | 
			
		||||
    SORTIE_BOSS_TYL: "SolNode105",
 | 
			
		||||
    SORTIE_BOSS_JACKAL: "SolNode104",
 | 
			
		||||
const sortieBossNode: Record<Exclude<TSortieBoss, "SORTIE_BOSS_CORRUPTED_VOR">, string> = {
 | 
			
		||||
    SORTIE_BOSS_ALAD: "SolNode53",
 | 
			
		||||
    SORTIE_BOSS_AMBULAS: "SolNode51",
 | 
			
		||||
    SORTIE_BOSS_NEF: "SettlementNode20",
 | 
			
		||||
    SORTIE_BOSS_RAPTOR: "SolNode210",
 | 
			
		||||
    SORTIE_BOSS_HEK: "SolNode24",
 | 
			
		||||
    SORTIE_BOSS_HYENA: "SolNode127",
 | 
			
		||||
    SORTIE_BOSS_INFALAD: "SolNode166",
 | 
			
		||||
    SORTIE_BOSS_JACKAL: "SolNode104",
 | 
			
		||||
    SORTIE_BOSS_KELA: "SolNode193",
 | 
			
		||||
    SORTIE_BOSS_KRIL: "SolNode99",
 | 
			
		||||
    SORTIE_BOSS_LEPHANTIS: "SolNode712",
 | 
			
		||||
    SORTIE_BOSS_INFALAD: "SolNode705"
 | 
			
		||||
    SORTIE_BOSS_NEF: "SettlementNode20",
 | 
			
		||||
    SORTIE_BOSS_PHORID: "SolNode171",
 | 
			
		||||
    SORTIE_BOSS_RAPTOR: "SolNode210",
 | 
			
		||||
    SORTIE_BOSS_RUK: "SolNode32",
 | 
			
		||||
    SORTIE_BOSS_TYL: "SolNode105",
 | 
			
		||||
    SORTIE_BOSS_VOR: "SolNode108"
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const eidolonJobs = [
 | 
			
		||||
@ -270,6 +273,7 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
 | 
			
		||||
            key in sortieTilesets
 | 
			
		||||
        ) {
 | 
			
		||||
            if (
 | 
			
		||||
                value.missionIndex != 0 && // Assassination will be decided independently
 | 
			
		||||
                value.missionIndex != 5 && // Sorties do not have capture missions
 | 
			
		||||
                !availableMissionIndexes.includes(value.missionIndex)
 | 
			
		||||
            ) {
 | 
			
		||||
@ -310,20 +314,10 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
 | 
			
		||||
                sortieFactionToSpecialMissionTileset[sortieBossToFaction[boss]]
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        if (i == 2 && rng.randomInt(0, 2) == 2) {
 | 
			
		||||
        if (i == 2 && boss != "SORTIE_BOSS_CORRUPTED_VOR" && rng.randomInt(0, 2) == 2) {
 | 
			
		||||
            const filteredModifiers = modifiers.filter(mod => mod !== "SORTIE_MODIFIER_MELEE_ONLY");
 | 
			
		||||
            const modifierType = rng.randomElement(filteredModifiers)!;
 | 
			
		||||
 | 
			
		||||
            if (boss == "SORTIE_BOSS_PHORID") {
 | 
			
		||||
                selectedNodes.push({
 | 
			
		||||
                    missionType: "MT_ASSASSINATION",
 | 
			
		||||
                    modifierType,
 | 
			
		||||
                    node,
 | 
			
		||||
                    tileset: sortieTilesets[node as keyof typeof sortieTilesets]
 | 
			
		||||
                });
 | 
			
		||||
                nodes.splice(randomIndex, 1);
 | 
			
		||||
                continue;
 | 
			
		||||
            } else if (sortieBossNode[boss]) {
 | 
			
		||||
            selectedNodes.push({
 | 
			
		||||
                missionType: "MT_ASSASSINATION",
 | 
			
		||||
                modifierType,
 | 
			
		||||
@ -332,7 +326,6 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
 | 
			
		||||
            });
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const missionType = eMissionType[missionIndex].tag;
 | 
			
		||||
 | 
			
		||||
@ -724,6 +717,11 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
 | 
			
		||||
        SyndicateMissions: [...staticWorldState.SyndicateMissions]
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Omit void fissures for versions prior to Whispers in the Walls to avoid errors with the unknown deimos nodes having void fissures.
 | 
			
		||||
    if (buildLabel && version_compare(buildLabel, "2023.11.06.13.39") <= 0) {
 | 
			
		||||
        worldState.ActiveMissions = [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (config.worldState?.starDays) {
 | 
			
		||||
        worldState.Goals.push({
 | 
			
		||||
            _id: { $oid: "67a4dcce2a198564d62e1647" },
 | 
			
		||||
@ -1227,3 +1225,20 @@ export const isArchwingMission = (node: IRegion): boolean => {
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const version_compare = (a: string, b: string): number => {
 | 
			
		||||
    const a_digits = a
 | 
			
		||||
        .split("/")[0]
 | 
			
		||||
        .split(".")
 | 
			
		||||
        .map(x => parseInt(x));
 | 
			
		||||
    const b_digits = b
 | 
			
		||||
        .split("/")[0]
 | 
			
		||||
        .split(".")
 | 
			
		||||
        .map(x => parseInt(x));
 | 
			
		||||
    for (let i = 0; i != a_digits.length; ++i) {
 | 
			
		||||
        if (a_digits[i] != b_digits[i]) {
 | 
			
		||||
            return a_digits[i] > b_digits[i] ? 1 : -1;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -202,7 +202,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
 | 
			
		||||
    OperatorLoadOuts: IOperatorConfigClient[];
 | 
			
		||||
    KahlLoadOuts: IOperatorConfigClient[];
 | 
			
		||||
 | 
			
		||||
    DuviriInfo: IDuviriInfo;
 | 
			
		||||
    DuviriInfo?: IDuviriInfo;
 | 
			
		||||
    Mailbox?: IMailboxClient;
 | 
			
		||||
    SubscribedToEmails: number;
 | 
			
		||||
    Created: IMongoDate;
 | 
			
		||||
 | 
			
		||||
@ -3,8 +3,8 @@ import { Types } from "mongoose";
 | 
			
		||||
export interface IAccountAndLoginResponseCommons {
 | 
			
		||||
    DisplayName: string;
 | 
			
		||||
    CountryCode: string;
 | 
			
		||||
    ClientType: string;
 | 
			
		||||
    CrossPlatformAllowed: boolean;
 | 
			
		||||
    ClientType?: string;
 | 
			
		||||
    CrossPlatformAllowed?: boolean;
 | 
			
		||||
    ForceLogoutVersion: number;
 | 
			
		||||
    AmazonAuthToken?: string;
 | 
			
		||||
    AmazonRefreshToken?: string;
 | 
			
		||||
@ -46,7 +46,7 @@ export interface ILoginResponse extends IAccountAndLoginResponseCommons {
 | 
			
		||||
    id: string;
 | 
			
		||||
    Groups: IGroup[];
 | 
			
		||||
    BuildLabel: string;
 | 
			
		||||
    MatchmakingBuildId: string;
 | 
			
		||||
    MatchmakingBuildId?: string;
 | 
			
		||||
    platformCDNs?: string[];
 | 
			
		||||
    NRS?: string[];
 | 
			
		||||
    DTLS: number;
 | 
			
		||||
 | 
			
		||||
@ -130,6 +130,7 @@ export type IMissionInventoryUpdateRequest = {
 | 
			
		||||
    }[];
 | 
			
		||||
    KubrowPetEggs?: IKubrowPetEggClient[];
 | 
			
		||||
    DiscoveredMarkers?: IDiscoveredMarker[];
 | 
			
		||||
    BrandedSuits?: IOid; // sent when captured by g3
 | 
			
		||||
    LockedWeaponGroup?: ILockedWeaponGroupClient; // sent when captured by zanuka
 | 
			
		||||
    UnlockWeapons?: boolean; // sent when recovered weapons from zanuka capture
 | 
			
		||||
    IncHarvester?: boolean; // sent when recovered weapons from zanuka capture
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,7 @@ export interface IWorldState {
 | 
			
		||||
    LiteSorties: ILiteSortie[];
 | 
			
		||||
    SyndicateMissions: ISyndicateMissionInfo[];
 | 
			
		||||
    GlobalUpgrades: IGlobalUpgrade[];
 | 
			
		||||
    ActiveMissions: IFissure[];
 | 
			
		||||
    NodeOverrides: INodeOverride[];
 | 
			
		||||
    EndlessXpChoices: IEndlessXpChoice[];
 | 
			
		||||
    SeasonInfo: {
 | 
			
		||||
@ -71,6 +72,18 @@ export interface IGlobalUpgrade {
 | 
			
		||||
    LocalizeDescTag: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IFissure {
 | 
			
		||||
    _id: IOid;
 | 
			
		||||
    Region: number;
 | 
			
		||||
    Seed: number;
 | 
			
		||||
    Activation: IMongoDate;
 | 
			
		||||
    Expiry: IMongoDate;
 | 
			
		||||
    Node: string;
 | 
			
		||||
    MissionType: string;
 | 
			
		||||
    Modifier: string;
 | 
			
		||||
    Hard?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface INodeOverride {
 | 
			
		||||
    _id: IOid;
 | 
			
		||||
    Activation?: IMongoDate;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user