merge upstream
This commit is contained in:
		
						commit
						d048cec150
					
				@ -11,9 +11,10 @@ import {
 | 
				
			|||||||
import { getAccountForRequest, getAccountFromSuffixedName, getSuffixedName } from "@/src/services/loginService";
 | 
					import { getAccountForRequest, getAccountFromSuffixedName, getSuffixedName } from "@/src/services/loginService";
 | 
				
			||||||
import { addItems, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
 | 
					import { addItems, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { logger } from "@/src/utils/logger";
 | 
					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 { handleStoreItemAcquisition } from "@/src/services/purchaseService";
 | 
				
			||||||
import { fromStoreItem, isStoreItem } from "@/src/services/itemDataService";
 | 
					import { fromStoreItem, isStoreItem } from "@/src/services/itemDataService";
 | 
				
			||||||
 | 
					import { IOid } from "@/src/types/commonTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const inboxController: RequestHandler = async (req, res) => {
 | 
					export const inboxController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query;
 | 
					    const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query;
 | 
				
			||||||
@ -28,10 +29,10 @@ export const inboxController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await deleteMessageRead(deleteId as string);
 | 
					        await deleteMessageRead(parseOid(deleteId as string));
 | 
				
			||||||
        res.status(200).end();
 | 
					        res.status(200).end();
 | 
				
			||||||
    } else if (messageId) {
 | 
					    } else if (messageId) {
 | 
				
			||||||
        const message = await getMessage(messageId as string);
 | 
					        const message = await getMessage(parseOid(messageId as string));
 | 
				
			||||||
        message.r = true;
 | 
					        message.r = true;
 | 
				
			||||||
        await message.save();
 | 
					        await message.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -50,7 +51,7 @@ export const inboxController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
                inventory,
 | 
					                inventory,
 | 
				
			||||||
                attachmentItems.map(attItem => ({
 | 
					                attachmentItems.map(attItem => ({
 | 
				
			||||||
                    ItemType: isStoreItem(attItem) ? fromStoreItem(attItem) : attItem,
 | 
					                    ItemType: isStoreItem(attItem) ? fromStoreItem(attItem) : attItem,
 | 
				
			||||||
                    ItemCount: attItem in ExportGear ? (ExportGear[attItem].purchaseQuantity ?? 1) : 1
 | 
					                    ItemCount: 1
 | 
				
			||||||
                })),
 | 
					                })),
 | 
				
			||||||
                inventoryChanges
 | 
					                inventoryChanges
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
@ -100,7 +101,7 @@ export const inboxController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        await createNewEventMessages(req);
 | 
					        await createNewEventMessages(req);
 | 
				
			||||||
        const messages = await Inbox.find({ ownerId: accountId }).sort({ date: 1 });
 | 
					        const messages = await Inbox.find({ ownerId: accountId }).sort({ date: 1 });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const latestClientMessage = messages.find(m => m._id.toString() === latestClientMessageId);
 | 
					        const latestClientMessage = messages.find(m => m._id.toString() === parseOid(latestClientMessageId as string));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!latestClientMessage) {
 | 
					        if (!latestClientMessage) {
 | 
				
			||||||
            logger.debug(`this should only happen after DeleteAllRead `);
 | 
					            logger.debug(`this should only happen after DeleteAllRead `);
 | 
				
			||||||
@ -123,3 +124,11 @@ export const inboxController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        res.json({ Inbox: inbox });
 | 
					        res.json({ Inbox: inbox });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 33.6.0 has query arguments like lastMessage={"$oid":"68112baebf192e786d1502bb"} instead of lastMessage=68112baebf192e786d1502bb
 | 
				
			||||||
 | 
					const parseOid = (oid: string): string => {
 | 
				
			||||||
 | 
					    if (oid[0] == "{") {
 | 
				
			||||||
 | 
					        return (JSON.parse(oid) as IOid).$oid;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return oid;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -106,9 +106,16 @@ export const inventoryController: RequestHandler = async (request, response) =>
 | 
				
			|||||||
        const currentDuviriMood = Math.trunc(Date.now() / 7200000);
 | 
					        const currentDuviriMood = Math.trunc(Date.now() / 7200000);
 | 
				
			||||||
        if (lastSyncDuviriMood != currentDuviriMood) {
 | 
					        if (lastSyncDuviriMood != currentDuviriMood) {
 | 
				
			||||||
            logger.debug(`refreshing duviri seed`);
 | 
					            logger.debug(`refreshing duviri seed`);
 | 
				
			||||||
 | 
					            if (!inventory.DuviriInfo) {
 | 
				
			||||||
 | 
					                inventory.DuviriInfo = {
 | 
				
			||||||
 | 
					                    Seed: generateRewardSeed(),
 | 
				
			||||||
 | 
					                    NumCompletions: 0
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
                inventory.DuviriInfo.Seed = generateRewardSeed();
 | 
					                inventory.DuviriInfo.Seed = generateRewardSeed();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    inventory.LastInventorySync = new Types.ObjectId();
 | 
					    inventory.LastInventorySync = new Types.ObjectId();
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,7 @@ import { Account } from "@/src/models/loginModel";
 | 
				
			|||||||
import { createAccount, isCorrectPassword, isNameTaken } from "@/src/services/loginService";
 | 
					import { createAccount, isCorrectPassword, isNameTaken } from "@/src/services/loginService";
 | 
				
			||||||
import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes";
 | 
					import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes";
 | 
				
			||||||
import { logger } from "@/src/utils/logger";
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
 | 
					import { version_compare } from "@/src/services/worldStateService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const loginController: RequestHandler = async (request, response) => {
 | 
					export const loginController: RequestHandler = async (request, response) => {
 | 
				
			||||||
    const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
 | 
					    const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
 | 
				
			||||||
@ -94,12 +95,10 @@ export const loginController: RequestHandler = async (request, response) => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const createLoginResponse = (myAddress: string, account: IDatabaseAccountJson, buildLabel: string): ILoginResponse => {
 | 
					const createLoginResponse = (myAddress: string, account: IDatabaseAccountJson, buildLabel: string): ILoginResponse => {
 | 
				
			||||||
    return {
 | 
					    const resp: ILoginResponse = {
 | 
				
			||||||
        id: account.id,
 | 
					        id: account.id,
 | 
				
			||||||
        DisplayName: account.DisplayName,
 | 
					        DisplayName: account.DisplayName,
 | 
				
			||||||
        CountryCode: account.CountryCode,
 | 
					        CountryCode: account.CountryCode,
 | 
				
			||||||
        ClientType: account.ClientType,
 | 
					 | 
				
			||||||
        CrossPlatformAllowed: account.CrossPlatformAllowed,
 | 
					 | 
				
			||||||
        ForceLogoutVersion: account.ForceLogoutVersion,
 | 
					        ForceLogoutVersion: account.ForceLogoutVersion,
 | 
				
			||||||
        AmazonAuthToken: account.AmazonAuthToken,
 | 
					        AmazonAuthToken: account.AmazonAuthToken,
 | 
				
			||||||
        AmazonRefreshToken: account.AmazonRefreshToken,
 | 
					        AmazonRefreshToken: account.AmazonRefreshToken,
 | 
				
			||||||
@ -108,11 +107,20 @@ const createLoginResponse = (myAddress: string, account: IDatabaseAccountJson, b
 | 
				
			|||||||
        Nonce: account.Nonce,
 | 
					        Nonce: account.Nonce,
 | 
				
			||||||
        Groups: [],
 | 
					        Groups: [],
 | 
				
			||||||
        IRC: config.myIrcAddresses ?? [myAddress],
 | 
					        IRC: config.myIrcAddresses ?? [myAddress],
 | 
				
			||||||
        platformCDNs: [`https://${myAddress}/`],
 | 
					 | 
				
			||||||
        HUB: `https://${myAddress}/api/`,
 | 
					 | 
				
			||||||
        NRS: config.NRS,
 | 
					        NRS: config.NRS,
 | 
				
			||||||
        DTLS: 99,
 | 
					        DTLS: 99,
 | 
				
			||||||
        BuildLabel: buildLabel,
 | 
					        BuildLabel: buildLabel
 | 
				
			||||||
        MatchmakingBuildId: buildConfig.matchmakingBuildId
 | 
					 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					    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 { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
					import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
				
			||||||
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
					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();
 | 
					    await inventory.save();
 | 
				
			||||||
    res.end();
 | 
					    res.end();
 | 
				
			||||||
 | 
				
			|||||||
@ -10,6 +10,10 @@ export const toMongoDate = (date: Date): IMongoDate => {
 | 
				
			|||||||
    return { $date: { $numberLong: date.getTime().toString() } };
 | 
					    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> = {
 | 
					export const kubrowWeights: Record<TRarity, number> = {
 | 
				
			||||||
    COMMON: 6,
 | 
					    COMMON: 6,
 | 
				
			||||||
    UNCOMMON: 4,
 | 
					    UNCOMMON: 4,
 | 
				
			||||||
 | 
				
			|||||||
@ -391,8 +391,8 @@ MailboxSchema.set("toJSON", {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const DuviriInfoSchema = new Schema<IDuviriInfo>(
 | 
					const DuviriInfoSchema = new Schema<IDuviriInfo>(
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        Seed: BigInt,
 | 
					        Seed: { type: BigInt, required: true },
 | 
				
			||||||
        NumCompletions: { type: Number, default: 0 }
 | 
					        NumCompletions: { type: Number, required: true }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        _id: false,
 | 
					        _id: false,
 | 
				
			||||||
@ -1688,9 +1688,9 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
 | 
				
			|||||||
        //Like BossAladV,BossCaptainVor come for you on missions % chance
 | 
					        //Like BossAladV,BossCaptainVor come for you on missions % chance
 | 
				
			||||||
        DeathMarks: { type: [String], default: [] },
 | 
					        DeathMarks: { type: [String], default: [] },
 | 
				
			||||||
        //Zanuka
 | 
					        //Zanuka
 | 
				
			||||||
        Harvestable: Boolean,
 | 
					        Harvestable: { type: Boolean, default: true },
 | 
				
			||||||
        //Grustag three
 | 
					        //Grustag three
 | 
				
			||||||
        DeathSquadable: Boolean,
 | 
					        DeathSquadable: { type: Boolean, default: true },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        EndlessXP: { type: [endlessXpProgressSchema], default: undefined },
 | 
					        EndlessXP: { type: [endlessXpProgressSchema], default: undefined },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -69,6 +69,7 @@ import {
 | 
				
			|||||||
import { createShip } from "./shipService";
 | 
					import { createShip } from "./shipService";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    catbrowDetails,
 | 
					    catbrowDetails,
 | 
				
			||||||
 | 
					    fromMongoDate,
 | 
				
			||||||
    kubrowDetails,
 | 
					    kubrowDetails,
 | 
				
			||||||
    kubrowFurPatternsWeights,
 | 
					    kubrowFurPatternsWeights,
 | 
				
			||||||
    kubrowWeights,
 | 
					    kubrowWeights,
 | 
				
			||||||
@ -486,6 +487,10 @@ export const addItem = async (
 | 
				
			|||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (typeName in ExportGear) {
 | 
					    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 = [
 | 
					        const consumablesChanges = [
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                ItemType: typeName,
 | 
					                ItemType: typeName,
 | 
				
			||||||
@ -1471,21 +1476,20 @@ export const addEmailItem = async (
 | 
				
			|||||||
    return inventoryChanges;
 | 
					    return inventoryChanges;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//TODO: wrong id is not erroring
 | 
					export const applyClientEquipmentUpdates = (
 | 
				
			||||||
export const addGearExpByCategory = (
 | 
					 | 
				
			||||||
    inventory: TInventoryDatabaseDocument,
 | 
					    inventory: TInventoryDatabaseDocument,
 | 
				
			||||||
    gearArray: IEquipmentClient[],
 | 
					    gearArray: IEquipmentClient[],
 | 
				
			||||||
    categoryName: TEquipmentKey
 | 
					    categoryName: TEquipmentKey
 | 
				
			||||||
): void => {
 | 
					): void => {
 | 
				
			||||||
    const category = inventory[categoryName];
 | 
					    const category = inventory[categoryName];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    gearArray.forEach(({ ItemId, XP }) => {
 | 
					    gearArray.forEach(({ ItemId, XP, InfestationDate }) => {
 | 
				
			||||||
        if (!XP) {
 | 
					        const item = category.id(ItemId.$oid);
 | 
				
			||||||
            return;
 | 
					        if (!item) {
 | 
				
			||||||
 | 
					            throw new Error(`No item with id ${ItemId.$oid} in ${categoryName}`);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const item = category.id(ItemId.$oid);
 | 
					        if (XP) {
 | 
				
			||||||
        if (item) {
 | 
					 | 
				
			||||||
            item.XP ??= 0;
 | 
					            item.XP ??= 0;
 | 
				
			||||||
            item.XP += XP;
 | 
					            item.XP += XP;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1500,6 +1504,10 @@ export const addGearExpByCategory = (
 | 
				
			|||||||
                });
 | 
					                });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (InfestationDate) {
 | 
				
			||||||
 | 
					            item.InfestationDate = fromMongoDate(InfestationDate);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -21,7 +21,6 @@ import {
 | 
				
			|||||||
    addFocusXpIncreases,
 | 
					    addFocusXpIncreases,
 | 
				
			||||||
    addFusionPoints,
 | 
					    addFusionPoints,
 | 
				
			||||||
    addFusionTreasures,
 | 
					    addFusionTreasures,
 | 
				
			||||||
    addGearExpByCategory,
 | 
					 | 
				
			||||||
    addItem,
 | 
					    addItem,
 | 
				
			||||||
    addLevelKeys,
 | 
					    addLevelKeys,
 | 
				
			||||||
    addLoreFragmentScans,
 | 
					    addLoreFragmentScans,
 | 
				
			||||||
@ -32,6 +31,7 @@ import {
 | 
				
			|||||||
    addShipDecorations,
 | 
					    addShipDecorations,
 | 
				
			||||||
    addSkin,
 | 
					    addSkin,
 | 
				
			||||||
    addStanding,
 | 
					    addStanding,
 | 
				
			||||||
 | 
					    applyClientEquipmentUpdates,
 | 
				
			||||||
    combineInventoryChanges,
 | 
					    combineInventoryChanges,
 | 
				
			||||||
    generateRewardSeed,
 | 
					    generateRewardSeed,
 | 
				
			||||||
    getCalendarProgress,
 | 
					    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) {
 | 
				
			||||||
        if (inventoryUpdates.RewardInfo.periodicMissionTag) {
 | 
					        if (inventoryUpdates.RewardInfo.periodicMissionTag) {
 | 
				
			||||||
@ -537,6 +505,23 @@ export const addMissionInventoryUpdates = async (
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
                break;
 | 
					                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": {
 | 
					            case "LockedWeaponGroup": {
 | 
				
			||||||
                inventory.LockedWeaponGroup = {
 | 
					                inventory.LockedWeaponGroup = {
 | 
				
			||||||
                    s: new Types.ObjectId(value.s.$oid),
 | 
					                    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,
 | 
					                    m: value.m ? new Types.ObjectId(value.m.$oid) : undefined,
 | 
				
			||||||
                    sn: value.sn ? new Types.ObjectId(value.sn.$oid) : undefined
 | 
					                    sn: value.sn ? new Types.ObjectId(value.sn.$oid) : undefined
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
 | 
					                inventory.Harvestable = false;
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            case "UnlockWeapons": {
 | 
					            case "UnlockWeapons": {
 | 
				
			||||||
                inventory.LockedWeaponGroup = undefined;
 | 
					                inventory.LockedWeaponGroup = undefined;
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            case "IncHarvester": {
 | 
				
			||||||
 | 
					                inventory.Harvestable = true;
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            case "CurrentLoadOutIds": {
 | 
					            case "CurrentLoadOutIds": {
 | 
				
			||||||
                if (value.LoadOuts) {
 | 
					                if (value.LoadOuts) {
 | 
				
			||||||
                    const loadout = await Loadout.findOne({ loadoutOwnerId: inventory.accountOwnerId });
 | 
					                    const loadout = await Loadout.findOne({ loadoutOwnerId: inventory.accountOwnerId });
 | 
				
			||||||
@ -611,7 +601,7 @@ export const addMissionInventoryUpdates = async (
 | 
				
			|||||||
            case "duviriCaveOffers": {
 | 
					            case "duviriCaveOffers": {
 | 
				
			||||||
                // Duviri cave offers (generated with the duviri seed) change after completing one of its game modes (not when aborting).
 | 
					                // Duviri cave offers (generated with the duviri seed) change after completing one of its game modes (not when aborting).
 | 
				
			||||||
                if (inventoryUpdates.MissionStatus != "GS_QUIT") {
 | 
					                if (inventoryUpdates.MissionStatus != "GS_QUIT") {
 | 
				
			||||||
                    inventory.DuviriInfo.Seed = generateRewardSeed();
 | 
					                    inventory.DuviriInfo!.Seed = generateRewardSeed();
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -670,9 +660,8 @@ export const addMissionInventoryUpdates = async (
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            default:
 | 
					            default:
 | 
				
			||||||
                // Equipment XP updates
 | 
					 | 
				
			||||||
                if (equipmentKeys.includes(key as TEquipmentKey)) {
 | 
					                if (equipmentKeys.includes(key as TEquipmentKey)) {
 | 
				
			||||||
                    addGearExpByCategory(inventory, value as IEquipmentClient[], key as TEquipmentKey);
 | 
					                    applyClientEquipmentUpdates(inventory, value as IEquipmentClient[], key as TEquipmentKey);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            // if (
 | 
					            // if (
 | 
				
			||||||
 | 
				
			|||||||
@ -6,7 +6,7 @@ export interface IRngResult {
 | 
				
			|||||||
    probability: number;
 | 
					    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)];
 | 
					    return arr[Math.floor(Math.random() * arr.length)];
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -113,7 +113,7 @@ export class CRng {
 | 
				
			|||||||
        return min;
 | 
					        return min;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    randomElement<T>(arr: T[]): T | undefined {
 | 
					    randomElement<T>(arr: readonly T[]): T | undefined {
 | 
				
			||||||
        return arr[Math.floor(this.random() * arr.length)];
 | 
					        return arr[Math.floor(this.random() * arr.length)];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -145,7 +145,7 @@ export class SRng {
 | 
				
			|||||||
        return min;
 | 
					        return min;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    randomElement<T>(arr: T[]): T | undefined {
 | 
					    randomElement<T>(arr: readonly T[]): T | undefined {
 | 
				
			||||||
        return arr[this.randomInt(0, arr.length - 1)];
 | 
					        return arr[this.randomInt(0, arr.length - 1)];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -33,9 +33,11 @@ const sortieBosses = [
 | 
				
			|||||||
    "SORTIE_BOSS_LEPHANTIS",
 | 
					    "SORTIE_BOSS_LEPHANTIS",
 | 
				
			||||||
    "SORTIE_BOSS_INFALAD",
 | 
					    "SORTIE_BOSS_INFALAD",
 | 
				
			||||||
    "SORTIE_BOSS_CORRUPTED_VOR"
 | 
					    "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_HYENA: "FC_CORPUS",
 | 
				
			||||||
    SORTIE_BOSS_KELA: "FC_GRINEER",
 | 
					    SORTIE_BOSS_KELA: "FC_GRINEER",
 | 
				
			||||||
    SORTIE_BOSS_VOR: "FC_GRINEER",
 | 
					    SORTIE_BOSS_VOR: "FC_GRINEER",
 | 
				
			||||||
@ -74,21 +76,22 @@ const sortieFactionToSpecialMissionTileset: Record<string, string> = {
 | 
				
			|||||||
    FC_INFESTATION: "CorpusShipTileset"
 | 
					    FC_INFESTATION: "CorpusShipTileset"
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const sortieBossNode: Record<string, string> = {
 | 
					const sortieBossNode: Record<Exclude<TSortieBoss, "SORTIE_BOSS_CORRUPTED_VOR">, 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",
 | 
					 | 
				
			||||||
    SORTIE_BOSS_ALAD: "SolNode53",
 | 
					    SORTIE_BOSS_ALAD: "SolNode53",
 | 
				
			||||||
    SORTIE_BOSS_AMBULAS: "SolNode51",
 | 
					    SORTIE_BOSS_AMBULAS: "SolNode51",
 | 
				
			||||||
    SORTIE_BOSS_NEF: "SettlementNode20",
 | 
					    SORTIE_BOSS_HEK: "SolNode24",
 | 
				
			||||||
    SORTIE_BOSS_RAPTOR: "SolNode210",
 | 
					    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_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 = [
 | 
					const eidolonJobs = [
 | 
				
			||||||
@ -270,6 +273,7 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
 | 
				
			|||||||
            key in sortieTilesets
 | 
					            key in sortieTilesets
 | 
				
			||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
            if (
 | 
					            if (
 | 
				
			||||||
 | 
					                value.missionIndex != 0 && // Assassination will be decided independently
 | 
				
			||||||
                value.missionIndex != 5 && // Sorties do not have capture missions
 | 
					                value.missionIndex != 5 && // Sorties do not have capture missions
 | 
				
			||||||
                !availableMissionIndexes.includes(value.missionIndex)
 | 
					                !availableMissionIndexes.includes(value.missionIndex)
 | 
				
			||||||
            ) {
 | 
					            ) {
 | 
				
			||||||
@ -310,20 +314,10 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
 | 
				
			|||||||
                sortieFactionToSpecialMissionTileset[sortieBossToFaction[boss]]
 | 
					                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 filteredModifiers = modifiers.filter(mod => mod !== "SORTIE_MODIFIER_MELEE_ONLY");
 | 
				
			||||||
            const modifierType = rng.randomElement(filteredModifiers)!;
 | 
					            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({
 | 
					            selectedNodes.push({
 | 
				
			||||||
                missionType: "MT_ASSASSINATION",
 | 
					                missionType: "MT_ASSASSINATION",
 | 
				
			||||||
                modifierType,
 | 
					                modifierType,
 | 
				
			||||||
@ -332,7 +326,6 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
 | 
				
			|||||||
            });
 | 
					            });
 | 
				
			||||||
            continue;
 | 
					            continue;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const missionType = eMissionType[missionIndex].tag;
 | 
					        const missionType = eMissionType[missionIndex].tag;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -724,6 +717,11 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
 | 
				
			|||||||
        SyndicateMissions: [...staticWorldState.SyndicateMissions]
 | 
					        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) {
 | 
					    if (config.worldState?.starDays) {
 | 
				
			||||||
        worldState.Goals.push({
 | 
					        worldState.Goals.push({
 | 
				
			||||||
            _id: { $oid: "67a4dcce2a198564d62e1647" },
 | 
					            _id: { $oid: "67a4dcce2a198564d62e1647" },
 | 
				
			||||||
@ -1227,3 +1225,20 @@ export const isArchwingMission = (node: IRegion): boolean => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    return false;
 | 
					    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[];
 | 
					    OperatorLoadOuts: IOperatorConfigClient[];
 | 
				
			||||||
    KahlLoadOuts: IOperatorConfigClient[];
 | 
					    KahlLoadOuts: IOperatorConfigClient[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    DuviriInfo: IDuviriInfo;
 | 
					    DuviriInfo?: IDuviriInfo;
 | 
				
			||||||
    Mailbox?: IMailboxClient;
 | 
					    Mailbox?: IMailboxClient;
 | 
				
			||||||
    SubscribedToEmails: number;
 | 
					    SubscribedToEmails: number;
 | 
				
			||||||
    Created: IMongoDate;
 | 
					    Created: IMongoDate;
 | 
				
			||||||
 | 
				
			|||||||
@ -3,8 +3,8 @@ import { Types } from "mongoose";
 | 
				
			|||||||
export interface IAccountAndLoginResponseCommons {
 | 
					export interface IAccountAndLoginResponseCommons {
 | 
				
			||||||
    DisplayName: string;
 | 
					    DisplayName: string;
 | 
				
			||||||
    CountryCode: string;
 | 
					    CountryCode: string;
 | 
				
			||||||
    ClientType: string;
 | 
					    ClientType?: string;
 | 
				
			||||||
    CrossPlatformAllowed: boolean;
 | 
					    CrossPlatformAllowed?: boolean;
 | 
				
			||||||
    ForceLogoutVersion: number;
 | 
					    ForceLogoutVersion: number;
 | 
				
			||||||
    AmazonAuthToken?: string;
 | 
					    AmazonAuthToken?: string;
 | 
				
			||||||
    AmazonRefreshToken?: string;
 | 
					    AmazonRefreshToken?: string;
 | 
				
			||||||
@ -46,7 +46,7 @@ export interface ILoginResponse extends IAccountAndLoginResponseCommons {
 | 
				
			|||||||
    id: string;
 | 
					    id: string;
 | 
				
			||||||
    Groups: IGroup[];
 | 
					    Groups: IGroup[];
 | 
				
			||||||
    BuildLabel: string;
 | 
					    BuildLabel: string;
 | 
				
			||||||
    MatchmakingBuildId: string;
 | 
					    MatchmakingBuildId?: string;
 | 
				
			||||||
    platformCDNs?: string[];
 | 
					    platformCDNs?: string[];
 | 
				
			||||||
    NRS?: string[];
 | 
					    NRS?: string[];
 | 
				
			||||||
    DTLS: number;
 | 
					    DTLS: number;
 | 
				
			||||||
 | 
				
			|||||||
@ -130,6 +130,7 @@ export type IMissionInventoryUpdateRequest = {
 | 
				
			|||||||
    }[];
 | 
					    }[];
 | 
				
			||||||
    KubrowPetEggs?: IKubrowPetEggClient[];
 | 
					    KubrowPetEggs?: IKubrowPetEggClient[];
 | 
				
			||||||
    DiscoveredMarkers?: IDiscoveredMarker[];
 | 
					    DiscoveredMarkers?: IDiscoveredMarker[];
 | 
				
			||||||
 | 
					    BrandedSuits?: IOid; // sent when captured by g3
 | 
				
			||||||
    LockedWeaponGroup?: ILockedWeaponGroupClient; // sent when captured by zanuka
 | 
					    LockedWeaponGroup?: ILockedWeaponGroupClient; // sent when captured by zanuka
 | 
				
			||||||
    UnlockWeapons?: boolean; // sent when recovered weapons from zanuka capture
 | 
					    UnlockWeapons?: boolean; // sent when recovered weapons from zanuka capture
 | 
				
			||||||
    IncHarvester?: 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[];
 | 
					    LiteSorties: ILiteSortie[];
 | 
				
			||||||
    SyndicateMissions: ISyndicateMissionInfo[];
 | 
					    SyndicateMissions: ISyndicateMissionInfo[];
 | 
				
			||||||
    GlobalUpgrades: IGlobalUpgrade[];
 | 
					    GlobalUpgrades: IGlobalUpgrade[];
 | 
				
			||||||
 | 
					    ActiveMissions: IFissure[];
 | 
				
			||||||
    NodeOverrides: INodeOverride[];
 | 
					    NodeOverrides: INodeOverride[];
 | 
				
			||||||
    EndlessXpChoices: IEndlessXpChoice[];
 | 
					    EndlessXpChoices: IEndlessXpChoice[];
 | 
				
			||||||
    SeasonInfo: {
 | 
					    SeasonInfo: {
 | 
				
			||||||
@ -71,6 +72,18 @@ export interface IGlobalUpgrade {
 | 
				
			|||||||
    LocalizeDescTag: string;
 | 
					    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 {
 | 
					export interface INodeOverride {
 | 
				
			||||||
    _id: IOid;
 | 
					    _id: IOid;
 | 
				
			||||||
    Activation?: IMongoDate;
 | 
					    Activation?: IMongoDate;
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user