chore: send messages after completing quest instead of on account creation #976
							
								
								
									
										8
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -12,7 +12,7 @@
 | 
			
		||||
        "copyfiles": "^2.4.1",
 | 
			
		||||
        "express": "^5",
 | 
			
		||||
        "mongoose": "^8.9.4",
 | 
			
		||||
        "warframe-public-export-plus": "^0.5.27",
 | 
			
		||||
        "warframe-public-export-plus": "^0.5.29",
 | 
			
		||||
        "warframe-riven-info": "^0.1.2",
 | 
			
		||||
        "winston": "^3.17.0",
 | 
			
		||||
        "winston-daily-rotate-file": "^5.0.0"
 | 
			
		||||
@ -4093,9 +4093,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/warframe-public-export-plus": {
 | 
			
		||||
      "version": "0.5.27",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.27.tgz",
 | 
			
		||||
      "integrity": "sha512-fdFfvvhIXAPE7hTGLRsRJZ036vV8Jbd5vhu9STbZykdVLRXt9zeilo+sQThNy+2GchO+QhtTcETCjn1ntJhXZw=="
 | 
			
		||||
      "version": "0.5.29",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.29.tgz",
 | 
			
		||||
      "integrity": "sha512-D61CFiJTr/LIQCvyZ03DgudUBpJDqrOTdNPNeB0kY21lmPHw1auDg4CNW2T1JaG0nL5K1hu9ypvnfXzno2omMA=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/warframe-riven-info": {
 | 
			
		||||
      "version": "0.1.2",
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,7 @@
 | 
			
		||||
    "copyfiles": "^2.4.1",
 | 
			
		||||
    "express": "^5",
 | 
			
		||||
    "mongoose": "^8.9.4",
 | 
			
		||||
    "warframe-public-export-plus": "^0.5.27",
 | 
			
		||||
    "warframe-public-export-plus": "^0.5.29",
 | 
			
		||||
    "warframe-riven-info": "^0.1.2",
 | 
			
		||||
    "winston": "^3.17.0",
 | 
			
		||||
    "winston-daily-rotate-file": "^5.0.0"
 | 
			
		||||
 | 
			
		||||
@ -31,12 +31,12 @@ fs.readdirSync("../static/webui/translations").forEach(file => {
 | 
			
		||||
            if (Object.keys(strings).length > 0) {
 | 
			
		||||
                Object.entries(strings).forEach(([key, value]) => {
 | 
			
		||||
                    if (targetStrings.hasOwnProperty(key)) {
 | 
			
		||||
                        fs.writeSync(fileHandle, `\t${key}: \`${targetStrings[key]}\`,\n`);
 | 
			
		||||
                        fs.writeSync(fileHandle, `    ${key}: \`${targetStrings[key]}\`,\n`);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        fs.writeSync(fileHandle, `\t${key}: \`[UNTRANSLATED] ${value}\`,\n`);
 | 
			
		||||
                        fs.writeSync(fileHandle, `    ${key}: \`[UNTRANSLATED] ${value}\`,\n`);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            } else {
 | 
			
		||||
            } else if (line.length) {
 | 
			
		||||
                fs.writeSync(fileHandle, line + "\n");
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										90
									
								
								src/controllers/api/changeDojoRootController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/controllers/api/changeDojoRootController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,90 @@
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
import { getDojoClient, getGuildForRequest } from "@/src/services/guildService";
 | 
			
		||||
import { logger } from "@/src/utils/logger";
 | 
			
		||||
import { IDojoComponentDatabase } from "@/src/types/guildTypes";
 | 
			
		||||
import { Types } from "mongoose";
 | 
			
		||||
 | 
			
		||||
export const changeDojoRootController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const guild = await getGuildForRequest(req);
 | 
			
		||||
    // At this point, we know that a member of the guild is making this request. Assuming they are allowed to change the root.
 | 
			
		||||
 | 
			
		||||
    const idToNode: Record<string, INode> = {};
 | 
			
		||||
    guild.DojoComponents!.forEach(x => {
 | 
			
		||||
        idToNode[x._id.toString()] = {
 | 
			
		||||
            component: x,
 | 
			
		||||
            parent: undefined,
 | 
			
		||||
            children: []
 | 
			
		||||
        };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let oldRoot: INode | undefined;
 | 
			
		||||
    guild.DojoComponents!.forEach(x => {
 | 
			
		||||
        const node = idToNode[x._id.toString()];
 | 
			
		||||
        if (x.pi) {
 | 
			
		||||
            idToNode[x.pi.toString()].children.push(node);
 | 
			
		||||
            node.parent = idToNode[x.pi.toString()];
 | 
			
		||||
        } else {
 | 
			
		||||
            oldRoot = node;
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    logger.debug("Old tree:\n" + treeToString(oldRoot!));
 | 
			
		||||
 | 
			
		||||
    const newRoot = idToNode[req.query.newRoot as string];
 | 
			
		||||
    recursivelyTurnParentsIntoChildren(newRoot);
 | 
			
		||||
    newRoot.component.pi = undefined;
 | 
			
		||||
    newRoot.component.op = undefined;
 | 
			
		||||
    newRoot.component.pp = undefined;
 | 
			
		||||
    newRoot.parent = undefined;
 | 
			
		||||
 | 
			
		||||
    // Don't even ask me why this is needed because I don't know either
 | 
			
		||||
    const stack: INode[] = [newRoot];
 | 
			
		||||
    let i = 0;
 | 
			
		||||
    const idMap: Record<string, Types.ObjectId> = {};
 | 
			
		||||
    while (stack.length != 0) {
 | 
			
		||||
        const top = stack.shift()!;
 | 
			
		||||
        idMap[top.component._id.toString()] = new Types.ObjectId(
 | 
			
		||||
            (++i).toString(16).padStart(8, "0") + top.component._id.toString().substr(8)
 | 
			
		||||
        );
 | 
			
		||||
        top.children.forEach(x => stack.push(x));
 | 
			
		||||
    }
 | 
			
		||||
    guild.DojoComponents!.forEach(x => {
 | 
			
		||||
        x._id = idMap[x._id.toString()];
 | 
			
		||||
        if (x.pi) {
 | 
			
		||||
            x.pi = idMap[x.pi.toString()];
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    logger.debug("New tree:\n" + treeToString(newRoot));
 | 
			
		||||
 | 
			
		||||
    await guild.save();
 | 
			
		||||
 | 
			
		||||
    res.json(getDojoClient(guild, 0));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface INode {
 | 
			
		||||
    component: IDojoComponentDatabase;
 | 
			
		||||
    parent: INode | undefined;
 | 
			
		||||
    children: INode[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const treeToString = (root: INode, depth: number = 0): string => {
 | 
			
		||||
    let str = " ".repeat(depth * 4) + root.component.pf + " (" + root.component._id.toString() + ")\n";
 | 
			
		||||
    root.children.forEach(x => {
 | 
			
		||||
        str += treeToString(x, depth + 1);
 | 
			
		||||
    });
 | 
			
		||||
    return str;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const recursivelyTurnParentsIntoChildren = (node: INode): void => {
 | 
			
		||||
    if (node.parent!.parent) {
 | 
			
		||||
        recursivelyTurnParentsIntoChildren(node.parent!);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    node.parent!.component.pi = node.component._id;
 | 
			
		||||
    node.parent!.component.op = node.component.pp;
 | 
			
		||||
    node.parent!.component.pp = node.component.op;
 | 
			
		||||
 | 
			
		||||
    node.parent!.parent = node;
 | 
			
		||||
    node.parent!.children.splice(node.parent!.children.indexOf(node), 1);
 | 
			
		||||
    node.children.push(node.parent!);
 | 
			
		||||
};
 | 
			
		||||
@ -1,8 +1,7 @@
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
import { Types } from "mongoose";
 | 
			
		||||
import { Guild } from "@/src/models/guildModel";
 | 
			
		||||
import { IDojoClient, IDojoComponentClient } from "@/src/types/guildTypes";
 | 
			
		||||
import { toOid, toMongoDate } from "@/src/helpers/inventoryHelpers";
 | 
			
		||||
import { getDojoClient } from "@/src/services/guildService";
 | 
			
		||||
 | 
			
		||||
export const getGuildDojoController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const guildId = req.query.guildId as string;
 | 
			
		||||
@ -26,34 +25,5 @@ export const getGuildDojoController: RequestHandler = async (req, res) => {
 | 
			
		||||
        await guild.save();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const dojo: IDojoClient = {
 | 
			
		||||
        _id: { $oid: guildId },
 | 
			
		||||
        Name: guild.Name,
 | 
			
		||||
        Tier: 1,
 | 
			
		||||
        FixedContributions: true,
 | 
			
		||||
        DojoRevision: 1,
 | 
			
		||||
        RevisionTime: Math.round(Date.now() / 1000),
 | 
			
		||||
        Energy: guild.DojoEnergy,
 | 
			
		||||
        Capacity: guild.DojoCapacity,
 | 
			
		||||
        DojoRequestStatus: 0,
 | 
			
		||||
        DojoComponents: []
 | 
			
		||||
    };
 | 
			
		||||
    guild.DojoComponents.forEach(dojoComponent => {
 | 
			
		||||
        const clientComponent: IDojoComponentClient = {
 | 
			
		||||
            id: toOid(dojoComponent._id),
 | 
			
		||||
            pf: dojoComponent.pf,
 | 
			
		||||
            ppf: dojoComponent.ppf,
 | 
			
		||||
            DecoCapacity: 600
 | 
			
		||||
        };
 | 
			
		||||
        if (dojoComponent.pi) {
 | 
			
		||||
            clientComponent.pi = toOid(dojoComponent.pi);
 | 
			
		||||
            clientComponent.op = dojoComponent.op!;
 | 
			
		||||
            clientComponent.pp = dojoComponent.pp!;
 | 
			
		||||
        }
 | 
			
		||||
        if (dojoComponent.CompletionTime) {
 | 
			
		||||
            clientComponent.CompletionTime = toMongoDate(dojoComponent.CompletionTime);
 | 
			
		||||
        }
 | 
			
		||||
        dojo.DojoComponents.push(clientComponent);
 | 
			
		||||
    });
 | 
			
		||||
    res.json(dojo);
 | 
			
		||||
    res.json(getDojoClient(guild, 0));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { getGuildForRequest } from "@/src/services/guildService";
 | 
			
		||||
import { getDojoClient, getGuildForRequest } from "@/src/services/guildService";
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,5 @@ export const queueDojoComponentDestructionController: RequestHandler = async (re
 | 
			
		||||
        guild.DojoEnergy -= room.energy;
 | 
			
		||||
    }
 | 
			
		||||
    await guild.save();
 | 
			
		||||
    res.json({
 | 
			
		||||
        DojoRequestStatus: 1
 | 
			
		||||
    });
 | 
			
		||||
    res.json(getDojoClient(guild, 1));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										18
									
								
								src/controllers/api/setDojoComponentMessageController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/controllers/api/setDojoComponentMessageController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
import { getDojoClient, getGuildForRequest } from "@/src/services/guildService";
 | 
			
		||||
 | 
			
		||||
export const setDojoComponentMessageController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const guild = await getGuildForRequest(req);
 | 
			
		||||
    // At this point, we know that a member of the guild is making this request. Assuming they are allowed to change the message.
 | 
			
		||||
    const component = guild.DojoComponents!.find(x => x._id.equals(req.query.componentId as string))!;
 | 
			
		||||
    const payload = JSON.parse(String(req.body)) as SetDojoComponentMessageRequest;
 | 
			
		||||
    if ("Name" in payload) {
 | 
			
		||||
        component.Name = payload.Name;
 | 
			
		||||
    } else {
 | 
			
		||||
        component.Message = payload.Message;
 | 
			
		||||
    }
 | 
			
		||||
    await guild.save();
 | 
			
		||||
    res.json(getDojoClient(guild, 1));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type SetDojoComponentMessageRequest = { Name: string } | { Message: string };
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
import { IDojoComponentClient } from "@/src/types/guildTypes";
 | 
			
		||||
import { getGuildForRequest } from "@/src/services/guildService";
 | 
			
		||||
import { getDojoClient, getGuildForRequest } from "@/src/services/guildService";
 | 
			
		||||
import { Types } from "mongoose";
 | 
			
		||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
			
		||||
 | 
			
		||||
@ -30,7 +30,5 @@ export const startDojoRecipeController: RequestHandler = async (req, res) => {
 | 
			
		||||
        CompletionTime: new Date(Date.now()) // TOOD: Omit this field & handle the "Collecting Materials" state.
 | 
			
		||||
    });
 | 
			
		||||
    await guild.save();
 | 
			
		||||
    res.json({
 | 
			
		||||
        DojoRequestStatus: 0
 | 
			
		||||
    });
 | 
			
		||||
    res.json(getDojoClient(guild, 0));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,14 +1,15 @@
 | 
			
		||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
			
		||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
			
		||||
import { getStats, uploadStats } from "@/src/services/statsService";
 | 
			
		||||
import { IStatsUpload } from "@/src/types/statTypes";
 | 
			
		||||
import { getStats, updateStats } from "@/src/services/statsService";
 | 
			
		||||
import { IStatsUpdate } from "@/src/types/statTypes";
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
 | 
			
		||||
const uploadController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const payload = getJSONfromString<IStatsUpload>(String(req.body));
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
    const { PS, ...payload } = getJSONfromString<IStatsUpdate>(String(req.body));
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const playerStats = await getStats(accountId);
 | 
			
		||||
    await uploadStats(playerStats, payload);
 | 
			
		||||
    await updateStats(playerStats, payload);
 | 
			
		||||
    res.status(200).end();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -5,14 +5,14 @@ import allScans from "@/static/fixed_responses/allScans.json";
 | 
			
		||||
import { ExportEnemies } from "warframe-public-export-plus";
 | 
			
		||||
import { getInventory } from "@/src/services/inventoryService";
 | 
			
		||||
import { getStats } from "@/src/services/statsService";
 | 
			
		||||
import { IStatsView } from "@/src/types/statTypes";
 | 
			
		||||
import { IStatsClient } from "@/src/types/statTypes";
 | 
			
		||||
 | 
			
		||||
const viewController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const inventory = await getInventory(accountId, "XPInfo");
 | 
			
		||||
    const playerStats = await getStats(accountId);
 | 
			
		||||
 | 
			
		||||
    const responseJson: IStatsView = playerStats.toJSON();
 | 
			
		||||
    const responseJson = playerStats.toJSON() as IStatsClient;
 | 
			
		||||
    responseJson.Weapons ??= [];
 | 
			
		||||
    for (const item of inventory.XPInfo) {
 | 
			
		||||
        const weaponIndex = responseJson.Weapons.findIndex(element => element.type == item.ItemType);
 | 
			
		||||
 | 
			
		||||
@ -14,6 +14,8 @@ const dojoComponentSchema = new Schema<IDojoComponentDatabase>({
 | 
			
		||||
    pi: Schema.Types.ObjectId,
 | 
			
		||||
    op: String,
 | 
			
		||||
    pp: String,
 | 
			
		||||
    Name: String,
 | 
			
		||||
    Message: String,
 | 
			
		||||
    CompletionTime: Date
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import { Document, Schema, Types, model } from "mongoose";
 | 
			
		||||
import { IEnemy, IMission, IScan, ITutorial, IAbility, IWeapon, IStatsDatabase } from "@/src/types/statTypes";
 | 
			
		||||
import { IEnemy, IMission, IScan, ITutorial, IAbility, IWeapon, IStatsDatabase, IRace } from "@/src/types/statTypes";
 | 
			
		||||
 | 
			
		||||
const abilitySchema = new Schema<IAbility>(
 | 
			
		||||
    {
 | 
			
		||||
@ -58,6 +58,13 @@ const weaponSchema = new Schema<IWeapon>(
 | 
			
		||||
    { _id: false }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const raceSchema = new Schema<IRace>(
 | 
			
		||||
    {
 | 
			
		||||
        highScore: Number
 | 
			
		||||
    },
 | 
			
		||||
    { _id: false }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const statsSchema = new Schema<IStatsDatabase>({
 | 
			
		||||
    accountOwnerId: { type: Schema.Types.ObjectId, required: true },
 | 
			
		||||
    CiphersSolved: Number,
 | 
			
		||||
@ -69,6 +76,8 @@ const statsSchema = new Schema<IStatsDatabase>({
 | 
			
		||||
    MissionsCompleted: Number,
 | 
			
		||||
    MissionsQuit: Number,
 | 
			
		||||
    MissionsFailed: Number,
 | 
			
		||||
    MissionsInterrupted: Number,
 | 
			
		||||
    MissionsDumped: Number,
 | 
			
		||||
    TimePlayedSec: Number,
 | 
			
		||||
    PickupCount: Number,
 | 
			
		||||
    Tutorial: { type: Map, of: tutorialSchema, default: {} },
 | 
			
		||||
@ -81,7 +90,8 @@ const statsSchema = new Schema<IStatsDatabase>({
 | 
			
		||||
    Missions: { type: [missionSchema], default: [] },
 | 
			
		||||
    Deaths: Number,
 | 
			
		||||
    HealCount: Number,
 | 
			
		||||
    ReviveCount: Number
 | 
			
		||||
    ReviveCount: Number,
 | 
			
		||||
    Races: { type: Map, of: raceSchema, default: {} }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
statsSchema.set("toJSON", {
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,7 @@ import { addFriendImageController } from "@/src/controllers/api/addFriendImageCo
 | 
			
		||||
import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController";
 | 
			
		||||
import { archonFusionController } from "@/src/controllers/api/archonFusionController";
 | 
			
		||||
import { artifactsController } from "../controllers/api/artifactsController";
 | 
			
		||||
import { changeDojoRootController } from "../controllers/api/changeDojoRootController";
 | 
			
		||||
import { checkDailyMissionBonusController } from "@/src/controllers/api/checkDailyMissionBonusController";
 | 
			
		||||
import { claimCompletedRecipeController } from "@/src/controllers/api/claimCompletedRecipeController";
 | 
			
		||||
import { clearDialogueHistoryController } from "@/src/controllers/api/clearDialogueHistoryController";
 | 
			
		||||
@ -32,6 +33,8 @@ import { getVendorInfoController } from "@/src/controllers/api/getVendorInfoCont
 | 
			
		||||
import { getVoidProjectionRewardsController } from "@/src/controllers/api/getVoidProjectionRewardsController";
 | 
			
		||||
import { gildWeaponController } from "@/src/controllers/api/gildWeaponController";
 | 
			
		||||
import { giveKeyChainTriggeredItemsController } from "@/src/controllers/api/giveKeyChainTriggeredItemsController";
 | 
			
		||||
import { giveKeyChainTriggeredMessageController } from "@/src/controllers/api/giveKeyChainTriggeredMessageController";
 | 
			
		||||
import { giveQuestKeyRewardController } from "@/src/controllers/api/giveQuestKey";
 | 
			
		||||
import { guildTechController } from "../controllers/api/guildTechController";
 | 
			
		||||
import { hostSessionController } from "@/src/controllers/api/hostSessionController";
 | 
			
		||||
import { hubController } from "@/src/controllers/api/hubController";
 | 
			
		||||
@ -60,6 +63,7 @@ import { sellController } from "@/src/controllers/api/sellController";
 | 
			
		||||
import { setActiveQuestController } from "@/src/controllers/api/setActiveQuestController";
 | 
			
		||||
import { setActiveShipController } from "@/src/controllers/api/setActiveShipController";
 | 
			
		||||
import { setBootLocationController } from "@/src/controllers/api/setBootLocationController";
 | 
			
		||||
import { setDojoComponentMessageController } from "@/src/controllers/api/setDojoComponentMessageController";
 | 
			
		||||
import { setEquippedInstrumentController } from "@/src/controllers/api/setEquippedInstrumentController";
 | 
			
		||||
import { setPlacedDecoInfoController } from "@/src/controllers/api/setPlacedDecoInfoController";
 | 
			
		||||
import { setShipCustomizationsController } from "@/src/controllers/api/setShipCustomizationsController";
 | 
			
		||||
@ -82,8 +86,6 @@ import { updateQuestController } from "@/src/controllers/api/updateQuestControll
 | 
			
		||||
import { updateSessionGetController, updateSessionPostController } from "@/src/controllers/api/updateSessionController";
 | 
			
		||||
import { updateThemeController } from "../controllers/api/updateThemeController";
 | 
			
		||||
import { upgradesController } from "@/src/controllers/api/upgradesController";
 | 
			
		||||
import { giveKeyChainTriggeredMessageController } from "@/src/controllers/api/giveKeyChainTriggeredMessageController";
 | 
			
		||||
import { giveQuestKeyRewardController } from "@/src/controllers/api/giveQuestKey";
 | 
			
		||||
 | 
			
		||||
const apiRouter = express.Router();
 | 
			
		||||
 | 
			
		||||
@ -126,6 +128,7 @@ apiRouter.post("/addFriendImage.php", addFriendImageController);
 | 
			
		||||
apiRouter.post("/arcaneCommon.php", arcaneCommonController);
 | 
			
		||||
apiRouter.post("/archonFusion.php", archonFusionController);
 | 
			
		||||
apiRouter.post("/artifacts.php", artifactsController);
 | 
			
		||||
apiRouter.post("/changeDojoRoot.php", changeDojoRootController);
 | 
			
		||||
apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController);
 | 
			
		||||
apiRouter.post("/clearDialogueHistory.php", clearDialogueHistoryController);
 | 
			
		||||
apiRouter.post("/createGuild.php", createGuildController);
 | 
			
		||||
@ -158,6 +161,7 @@ apiRouter.post("/rerollRandomMod.php", rerollRandomModController);
 | 
			
		||||
apiRouter.post("/saveDialogue.php", saveDialogueController);
 | 
			
		||||
apiRouter.post("/saveLoadout.php", saveLoadoutController);
 | 
			
		||||
apiRouter.post("/sell.php", sellController);
 | 
			
		||||
apiRouter.post("/setDojoComponentMessage.php", setDojoComponentMessageController);
 | 
			
		||||
apiRouter.post("/setEquippedInstrument.php", setEquippedInstrumentController);
 | 
			
		||||
apiRouter.post("/setPlacedDecoInfo.php", setPlacedDecoInfoController);
 | 
			
		||||
apiRouter.post("/setShipCustomizations.php", setShipCustomizationsController);
 | 
			
		||||
@ -174,9 +178,9 @@ apiRouter.post("/trainingResult.php", trainingResultController);
 | 
			
		||||
apiRouter.post("/unlockShipFeature.php", unlockShipFeatureController);
 | 
			
		||||
apiRouter.post("/updateChallengeProgress.php", updateChallengeProgressController);
 | 
			
		||||
apiRouter.post("/updateNodeIntros.php", genericUpdateController);
 | 
			
		||||
apiRouter.post("/updateQuest.php", updateQuestController);
 | 
			
		||||
apiRouter.post("/updateSession.php", updateSessionPostController);
 | 
			
		||||
apiRouter.post("/updateTheme.php", updateThemeController);
 | 
			
		||||
apiRouter.post("/updateQuest.php", updateQuestController);
 | 
			
		||||
apiRouter.post("/upgrades.php", upgradesController);
 | 
			
		||||
 | 
			
		||||
export { apiRouter };
 | 
			
		||||
 | 
			
		||||
@ -3,14 +3,20 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
			
		||||
import { getInventory } from "@/src/services/inventoryService";
 | 
			
		||||
import { Guild } from "@/src/models/guildModel";
 | 
			
		||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
 | 
			
		||||
import { IDojoClient, IDojoComponentClient, IGuildDatabase } from "@/src/types/guildTypes";
 | 
			
		||||
import { Document, Types } from "mongoose";
 | 
			
		||||
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
 | 
			
		||||
 | 
			
		||||
export const getGuildForRequest = async (req: Request) => {
 | 
			
		||||
export const getGuildForRequest = async (req: Request): Promise<TGuildDatabaseDocument> => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const inventory = await getInventory(accountId);
 | 
			
		||||
    return await getGuildForRequestEx(req, inventory);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getGuildForRequestEx = async (req: Request, inventory: TInventoryDatabaseDocument) => {
 | 
			
		||||
export const getGuildForRequestEx = async (
 | 
			
		||||
    req: Request,
 | 
			
		||||
    inventory: TInventoryDatabaseDocument
 | 
			
		||||
): Promise<TGuildDatabaseDocument> => {
 | 
			
		||||
    const guildId = req.query.guildId as string;
 | 
			
		||||
    if (!inventory.GuildId || inventory.GuildId.toString() != guildId) {
 | 
			
		||||
        throw new Error("Account is not in the guild that it has sent a request for");
 | 
			
		||||
@ -21,3 +27,47 @@ export const getGuildForRequestEx = async (req: Request, inventory: TInventoryDa
 | 
			
		||||
    }
 | 
			
		||||
    return guild;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getDojoClient = (guild: TGuildDatabaseDocument, status: number): IDojoClient => {
 | 
			
		||||
    const dojo: IDojoClient = {
 | 
			
		||||
        _id: { $oid: guild._id.toString() },
 | 
			
		||||
        Name: guild.Name,
 | 
			
		||||
        Tier: 1,
 | 
			
		||||
        FixedContributions: true,
 | 
			
		||||
        DojoRevision: 1,
 | 
			
		||||
        RevisionTime: Math.round(Date.now() / 1000),
 | 
			
		||||
        Energy: guild.DojoEnergy,
 | 
			
		||||
        Capacity: guild.DojoCapacity,
 | 
			
		||||
        DojoRequestStatus: status,
 | 
			
		||||
        DojoComponents: []
 | 
			
		||||
    };
 | 
			
		||||
    guild.DojoComponents!.forEach(dojoComponent => {
 | 
			
		||||
        const clientComponent: IDojoComponentClient = {
 | 
			
		||||
            id: toOid(dojoComponent._id),
 | 
			
		||||
            pf: dojoComponent.pf,
 | 
			
		||||
            ppf: dojoComponent.ppf,
 | 
			
		||||
            Name: dojoComponent.Name,
 | 
			
		||||
            Message: dojoComponent.Message,
 | 
			
		||||
            DecoCapacity: 600
 | 
			
		||||
        };
 | 
			
		||||
        if (dojoComponent.pi) {
 | 
			
		||||
            clientComponent.pi = toOid(dojoComponent.pi);
 | 
			
		||||
            clientComponent.op = dojoComponent.op!;
 | 
			
		||||
            clientComponent.pp = dojoComponent.pp!;
 | 
			
		||||
        }
 | 
			
		||||
        if (dojoComponent.CompletionTime) {
 | 
			
		||||
            clientComponent.CompletionTime = toMongoDate(dojoComponent.CompletionTime);
 | 
			
		||||
        }
 | 
			
		||||
        dojo.DojoComponents.push(clientComponent);
 | 
			
		||||
    });
 | 
			
		||||
    return dojo;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line @typescript-eslint/ban-types
 | 
			
		||||
export type TGuildDatabaseDocument = Document<unknown, {}, IGuildDatabase> &
 | 
			
		||||
    IGuildDatabase &
 | 
			
		||||
    Required<{
 | 
			
		||||
        _id: Types.ObjectId;
 | 
			
		||||
    }> & {
 | 
			
		||||
        __v: number;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@ -415,20 +415,21 @@ export const addItem = async (
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        case "Upgrades": {
 | 
			
		||||
            if (typeName.substr(1).split("/")[2] == "CosmeticEnhancers") {
 | 
			
		||||
                // Needed to add Traumatic Peculiar
 | 
			
		||||
                const changes = [
 | 
			
		||||
                    {
 | 
			
		||||
                        ItemType: typeName,
 | 
			
		||||
                        ItemCount: quantity
 | 
			
		||||
                    }
 | 
			
		||||
                ];
 | 
			
		||||
                addMods(inventory, changes);
 | 
			
		||||
                return {
 | 
			
		||||
                    InventoryChanges: {
 | 
			
		||||
                        RawUpgrades: changes
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
            switch (typeName.substr(1).split("/")[2]) {
 | 
			
		||||
                case "Mods": // Legendary Core
 | 
			
		||||
                case "CosmeticEnhancers": // Traumatic Peculiar
 | 
			
		||||
                    const changes = [
 | 
			
		||||
                        {
 | 
			
		||||
                            ItemType: typeName,
 | 
			
		||||
                            ItemCount: quantity
 | 
			
		||||
                        }
 | 
			
		||||
                    ];
 | 
			
		||||
                    addMods(inventory, changes);
 | 
			
		||||
                    return {
 | 
			
		||||
                        InventoryChanges: {
 | 
			
		||||
                            RawUpgrades: changes
 | 
			
		||||
                        }
 | 
			
		||||
                    };
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,7 @@ import MaskSalesmanManifest from "@/static/fixed_responses/getVendorInfo/MaskSal
 | 
			
		||||
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 RadioLegionIntermission11VendorManifest from "@/static/fixed_responses/getVendorInfo/RadioLegionIntermission11VendorManifest.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";
 | 
			
		||||
@ -70,7 +70,7 @@ const vendorManifests: IVendorManifest[] = [
 | 
			
		||||
    OstronFishmongerVendorManifest,
 | 
			
		||||
    OstronPetVendorManifest,
 | 
			
		||||
    OstronProspectorVendorManifest,
 | 
			
		||||
    RadioLegionIntermission11VendorManifest,
 | 
			
		||||
    RadioLegionIntermission12VendorManifest,
 | 
			
		||||
    SolarisDebtTokenVendorManifest,
 | 
			
		||||
    SolarisDebtTokenVendorRepossessionsManifest,
 | 
			
		||||
    SolarisFishmongerVendorManifest,
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,15 @@
 | 
			
		||||
import { Stats, TStatsDatabaseDocument } from "@/src/models/statsModel";
 | 
			
		||||
import { IStatsUpload } from "@/src/types/statTypes";
 | 
			
		||||
import {
 | 
			
		||||
    IEnemy,
 | 
			
		||||
    IStatsAdd,
 | 
			
		||||
    IStatsMax,
 | 
			
		||||
    IStatsSet,
 | 
			
		||||
    IStatsTimers,
 | 
			
		||||
    IStatsUpdate,
 | 
			
		||||
    IUploadEntry,
 | 
			
		||||
    IWeapon
 | 
			
		||||
} from "@/src/types/statTypes";
 | 
			
		||||
import { logger } from "../utils/logger";
 | 
			
		||||
 | 
			
		||||
export const createStats = async (accountId: string): Promise<TStatsDatabaseDocument> => {
 | 
			
		||||
    const stats = new Stats({ accountOwnerId: accountId });
 | 
			
		||||
@ -15,269 +25,383 @@ export const getStats = async (accountOwnerId: string): Promise<TStatsDatabaseDo
 | 
			
		||||
    return stats;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const uploadStats = async (playerStats: TStatsDatabaseDocument, payload: IStatsUpload): Promise<void> => {
 | 
			
		||||
    if (payload.add) {
 | 
			
		||||
        const {
 | 
			
		||||
            MISSION_COMPLETE,
 | 
			
		||||
            PICKUP_ITEM,
 | 
			
		||||
            SCAN,
 | 
			
		||||
            USE_ABILITY,
 | 
			
		||||
            FIRE_WEAPON,
 | 
			
		||||
            HIT_ENTITY_ITEM,
 | 
			
		||||
            HEADSHOT_ITEM,
 | 
			
		||||
            KILL_ENEMY_ITEM,
 | 
			
		||||
            KILL_ENEMY,
 | 
			
		||||
            EXECUTE_ENEMY,
 | 
			
		||||
            HEADSHOT,
 | 
			
		||||
            DIE,
 | 
			
		||||
            MELEE_KILL,
 | 
			
		||||
            INCOME,
 | 
			
		||||
            CIPHER
 | 
			
		||||
        } = payload.add;
 | 
			
		||||
export const updateStats = async (playerStats: TStatsDatabaseDocument, payload: IStatsUpdate): Promise<void> => {
 | 
			
		||||
    const unknownCategories: Record<string, string[]> = {};
 | 
			
		||||
 | 
			
		||||
        if (MISSION_COMPLETE) {
 | 
			
		||||
            for (const [key, value] of Object.entries(MISSION_COMPLETE)) {
 | 
			
		||||
                switch (key) {
 | 
			
		||||
                    case "GS_SUCCESS":
 | 
			
		||||
                        playerStats.MissionsCompleted ??= 0;
 | 
			
		||||
                        playerStats.MissionsCompleted += value;
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "GS_QUIT":
 | 
			
		||||
                        playerStats.MissionsQuit ??= 0;
 | 
			
		||||
                        playerStats.MissionsQuit += value;
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "GS_FAILURE":
 | 
			
		||||
                        playerStats.MissionsFailed ??= 0;
 | 
			
		||||
                        playerStats.MissionsFailed += value;
 | 
			
		||||
                        break;
 | 
			
		||||
    for (const [action, actionData] of Object.entries(payload)) {
 | 
			
		||||
        switch (action) {
 | 
			
		||||
            case "add":
 | 
			
		||||
                for (const [category, data] of Object.entries(actionData as IStatsAdd)) {
 | 
			
		||||
                    switch (category) {
 | 
			
		||||
                        case "MISSION_COMPLETE":
 | 
			
		||||
                            for (const [key, value] of Object.entries(data as IUploadEntry)) {
 | 
			
		||||
                                switch (key) {
 | 
			
		||||
                                    case "GS_SUCCESS":
 | 
			
		||||
                                        playerStats.MissionsCompleted ??= 0;
 | 
			
		||||
                                        playerStats.MissionsCompleted += value;
 | 
			
		||||
                                        break;
 | 
			
		||||
                                    case "GS_QUIT":
 | 
			
		||||
                                        playerStats.MissionsQuit ??= 0;
 | 
			
		||||
                                        playerStats.MissionsQuit += value;
 | 
			
		||||
                                        break;
 | 
			
		||||
                                    case "GS_FAILURE":
 | 
			
		||||
                                        playerStats.MissionsFailed ??= 0;
 | 
			
		||||
                                        playerStats.MissionsFailed += value;
 | 
			
		||||
                                        break;
 | 
			
		||||
                                    case "GS_INTERRUPTED":
 | 
			
		||||
                                        playerStats.MissionsInterrupted ??= 0;
 | 
			
		||||
                                        playerStats.MissionsInterrupted += value;
 | 
			
		||||
                                        break;
 | 
			
		||||
                                    case "GS_DUMPED":
 | 
			
		||||
                                        playerStats.MissionsDumped ??= 0;
 | 
			
		||||
                                        playerStats.MissionsDumped += value;
 | 
			
		||||
                                        break;
 | 
			
		||||
                                    default:
 | 
			
		||||
                                        if (!ignoredCategories.includes(category)) {
 | 
			
		||||
                                            if (!unknownCategories[action]) {
 | 
			
		||||
                                                unknownCategories[action] = [];
 | 
			
		||||
                                            }
 | 
			
		||||
                                            unknownCategories[action].push(category);
 | 
			
		||||
                                        }
 | 
			
		||||
                                        break;
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
 | 
			
		||||
                        case "PICKUP_ITEM":
 | 
			
		||||
                            playerStats.PickupCount ??= 0;
 | 
			
		||||
                            // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
                            for (const [_key, value] of Object.entries(data as IUploadEntry)) {
 | 
			
		||||
                                playerStats.PickupCount += value;
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
 | 
			
		||||
                        case "SCAN":
 | 
			
		||||
                            playerStats.Scans ??= [];
 | 
			
		||||
                            for (const [type, scans] of Object.entries(data as IUploadEntry)) {
 | 
			
		||||
                                const scan = playerStats.Scans.find(element => element.type === type);
 | 
			
		||||
                                if (scan) {
 | 
			
		||||
                                    scan.scans ??= 0;
 | 
			
		||||
                                    scan.scans += scans;
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    playerStats.Scans.push({ type: type, scans });
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
 | 
			
		||||
                        case "USE_ABILITY":
 | 
			
		||||
                            playerStats.Abilities ??= [];
 | 
			
		||||
                            for (const [type, used] of Object.entries(data as IUploadEntry)) {
 | 
			
		||||
                                const ability = playerStats.Abilities.find(element => element.type === type);
 | 
			
		||||
                                if (ability) {
 | 
			
		||||
                                    ability.used ??= 0;
 | 
			
		||||
                                    ability.used += used;
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    playerStats.Abilities.push({ type: type, used });
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
 | 
			
		||||
                        case "FIRE_WEAPON":
 | 
			
		||||
                        case "HIT_ENTITY_ITEM":
 | 
			
		||||
                        case "HEADSHOT_ITEM":
 | 
			
		||||
                        case "KILL_ENEMY_ITEM":
 | 
			
		||||
                            playerStats.Weapons ??= [];
 | 
			
		||||
                            const statKey = {
 | 
			
		||||
                                FIRE_WEAPON: "fired",
 | 
			
		||||
                                HIT_ENTITY_ITEM: "hits",
 | 
			
		||||
                                HEADSHOT_ITEM: "headshots",
 | 
			
		||||
                                KILL_ENEMY_ITEM: "kills"
 | 
			
		||||
                            }[category] as "fired" | "hits" | "headshots" | "kills";
 | 
			
		||||
 | 
			
		||||
                            for (const [type, count] of Object.entries(data as IUploadEntry)) {
 | 
			
		||||
                                const weapon = playerStats.Weapons.find(element => element.type === type);
 | 
			
		||||
                                if (weapon) {
 | 
			
		||||
                                    weapon[statKey] ??= 0;
 | 
			
		||||
                                    weapon[statKey] += count;
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    const newWeapon: IWeapon = { type: type };
 | 
			
		||||
                                    newWeapon[statKey] = count;
 | 
			
		||||
                                    playerStats.Weapons.push(newWeapon);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
 | 
			
		||||
                        case "KILL_ENEMY":
 | 
			
		||||
                        case "EXECUTE_ENEMY":
 | 
			
		||||
                        case "HEADSHOT":
 | 
			
		||||
                            playerStats.Enemies ??= [];
 | 
			
		||||
                            const enemyStatKey = {
 | 
			
		||||
                                KILL_ENEMY: "kills",
 | 
			
		||||
                                EXECUTE_ENEMY: "executions",
 | 
			
		||||
                                HEADSHOT: "headshots"
 | 
			
		||||
                            }[category] as "kills" | "executions" | "headshots";
 | 
			
		||||
 | 
			
		||||
                            for (const [type, count] of Object.entries(data as IUploadEntry)) {
 | 
			
		||||
                                const enemy = playerStats.Enemies.find(element => element.type === type);
 | 
			
		||||
                                if (enemy) {
 | 
			
		||||
                                    enemy[enemyStatKey] ??= 0;
 | 
			
		||||
                                    enemy[enemyStatKey] += count;
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    const newEnemy: IEnemy = { type: type };
 | 
			
		||||
                                    newEnemy[enemyStatKey] = count;
 | 
			
		||||
                                    playerStats.Enemies.push(newEnemy);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
 | 
			
		||||
                        case "DIE":
 | 
			
		||||
                            playerStats.Enemies ??= [];
 | 
			
		||||
                            playerStats.Deaths ??= 0;
 | 
			
		||||
                            for (const [type, deaths] of Object.entries(data as IUploadEntry)) {
 | 
			
		||||
                                playerStats.Deaths += deaths;
 | 
			
		||||
                                const enemy = playerStats.Enemies.find(element => element.type === type);
 | 
			
		||||
                                if (enemy) {
 | 
			
		||||
                                    enemy.deaths ??= 0;
 | 
			
		||||
                                    enemy.deaths += deaths;
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    playerStats.Enemies.push({ type: type, deaths });
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
 | 
			
		||||
                        case "MELEE_KILL":
 | 
			
		||||
                            playerStats.MeleeKills ??= 0;
 | 
			
		||||
                            // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
                            for (const [_key, kills] of Object.entries(data as IUploadEntry)) {
 | 
			
		||||
                                playerStats.MeleeKills += kills;
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
 | 
			
		||||
                        case "INCOME":
 | 
			
		||||
                            playerStats.Income ??= 0;
 | 
			
		||||
                            playerStats.Income += data;
 | 
			
		||||
                            break;
 | 
			
		||||
 | 
			
		||||
                        case "CIPHER":
 | 
			
		||||
                            if (data["0"] > 0) {
 | 
			
		||||
                                playerStats.CiphersFailed ??= 0;
 | 
			
		||||
                                playerStats.CiphersFailed += data["0"];
 | 
			
		||||
                            }
 | 
			
		||||
                            if (data["1"] > 0) {
 | 
			
		||||
                                playerStats.CiphersSolved ??= 0;
 | 
			
		||||
                                playerStats.CiphersSolved += data["1"];
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
 | 
			
		||||
                        default:
 | 
			
		||||
                            if (!ignoredCategories.includes(category)) {
 | 
			
		||||
                                if (!unknownCategories[action]) {
 | 
			
		||||
                                    unknownCategories[action] = [];
 | 
			
		||||
                                }
 | 
			
		||||
                                unknownCategories[action].push(category);
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
        if (PICKUP_ITEM) {
 | 
			
		||||
            for (const value of Object.values(PICKUP_ITEM)) {
 | 
			
		||||
                playerStats.PickupCount ??= 0;
 | 
			
		||||
                playerStats.PickupCount += value;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
            case "timers":
 | 
			
		||||
                for (const [category, data] of Object.entries(actionData as IStatsTimers)) {
 | 
			
		||||
                    switch (category) {
 | 
			
		||||
                        case "EQUIP_WEAPON":
 | 
			
		||||
                            playerStats.Weapons ??= [];
 | 
			
		||||
                            for (const [type, equipTime] of Object.entries(data as IUploadEntry)) {
 | 
			
		||||
                                const weapon = playerStats.Weapons.find(element => element.type === type);
 | 
			
		||||
                                if (weapon) {
 | 
			
		||||
                                    weapon.equipTime ??= 0;
 | 
			
		||||
                                    weapon.equipTime += equipTime;
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    playerStats.Weapons.push({ type: type, equipTime });
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
 | 
			
		||||
        if (SCAN) {
 | 
			
		||||
            playerStats.Scans ??= [];
 | 
			
		||||
            for (const [key, scans] of Object.entries(SCAN)) {
 | 
			
		||||
                const scan = playerStats.Scans.find(element => element.type === key);
 | 
			
		||||
                if (scan) {
 | 
			
		||||
                    scan.scans ??= 0;
 | 
			
		||||
                    scan.scans += scans;
 | 
			
		||||
                } else {
 | 
			
		||||
                    playerStats.Scans.push({ type: key, scans });
 | 
			
		||||
                        case "CURRENT_MISSION_TIME":
 | 
			
		||||
                            playerStats.TimePlayedSec ??= 0;
 | 
			
		||||
                            playerStats.TimePlayedSec += data;
 | 
			
		||||
                            break;
 | 
			
		||||
 | 
			
		||||
                        case "CIPHER_TIME":
 | 
			
		||||
                            playerStats.CipherTime ??= 0;
 | 
			
		||||
                            playerStats.CipherTime += data;
 | 
			
		||||
                            break;
 | 
			
		||||
 | 
			
		||||
                        default:
 | 
			
		||||
                            if (!ignoredCategories.includes(category)) {
 | 
			
		||||
                                if (!unknownCategories[action]) {
 | 
			
		||||
                                    unknownCategories[action] = [];
 | 
			
		||||
                                }
 | 
			
		||||
                                unknownCategories[action].push(category);
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
        if (USE_ABILITY) {
 | 
			
		||||
            playerStats.Abilities ??= [];
 | 
			
		||||
            for (const [key, used] of Object.entries(USE_ABILITY)) {
 | 
			
		||||
                const ability = playerStats.Abilities.find(element => element.type === key);
 | 
			
		||||
                if (ability) {
 | 
			
		||||
                    ability.used ??= 0;
 | 
			
		||||
                    ability.used += used;
 | 
			
		||||
                } else {
 | 
			
		||||
                    playerStats.Abilities.push({ type: key, used });
 | 
			
		||||
            case "max":
 | 
			
		||||
                for (const [category, data] of Object.entries(actionData as IStatsMax)) {
 | 
			
		||||
                    switch (category) {
 | 
			
		||||
                        case "WEAPON_XP":
 | 
			
		||||
                            playerStats.Weapons ??= [];
 | 
			
		||||
                            for (const [type, xp] of Object.entries(data as IUploadEntry)) {
 | 
			
		||||
                                const weapon = playerStats.Weapons.find(element => element.type === type);
 | 
			
		||||
                                if (weapon) {
 | 
			
		||||
                                    if (xp > (weapon.xp ?? 0)) {
 | 
			
		||||
                                        weapon.xp = xp;
 | 
			
		||||
                                    }
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    playerStats.Weapons.push({ type: type, xp });
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
 | 
			
		||||
                        case "MISSION_SCORE":
 | 
			
		||||
                            playerStats.Missions ??= [];
 | 
			
		||||
                            for (const [type, highScore] of Object.entries(data as IUploadEntry)) {
 | 
			
		||||
                                const mission = playerStats.Missions.find(element => element.type === type);
 | 
			
		||||
                                if (mission) {
 | 
			
		||||
                                    if (highScore > mission.highScore) {
 | 
			
		||||
                                        mission.highScore = highScore;
 | 
			
		||||
                                    }
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    playerStats.Missions.push({ type: type, highScore });
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
 | 
			
		||||
                        case "RACE_SCORE":
 | 
			
		||||
                            playerStats.Races ??= new Map();
 | 
			
		||||
 | 
			
		||||
                            for (const [race, highScore] of Object.entries(data as Record<string, number>)) {
 | 
			
		||||
                                const currentRace = playerStats.Races.get(race);
 | 
			
		||||
 | 
			
		||||
                                if (currentRace) {
 | 
			
		||||
                                    if (highScore > currentRace.highScore) {
 | 
			
		||||
                                        playerStats.Races.set(race, { highScore });
 | 
			
		||||
                                    }
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    playerStats.Races.set(race, { highScore });
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            break;
 | 
			
		||||
 | 
			
		||||
                        default:
 | 
			
		||||
                            if (!ignoredCategories.includes(category)) {
 | 
			
		||||
                                if (!unknownCategories[action]) {
 | 
			
		||||
                                    unknownCategories[action] = [];
 | 
			
		||||
                                }
 | 
			
		||||
                                unknownCategories[action].push(category);
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
        if (FIRE_WEAPON) {
 | 
			
		||||
            playerStats.Weapons ??= [];
 | 
			
		||||
            for (const [key, fired] of Object.entries(FIRE_WEAPON)) {
 | 
			
		||||
                const weapon = playerStats.Weapons.find(element => element.type === key);
 | 
			
		||||
                if (weapon) {
 | 
			
		||||
                    weapon.fired ??= 0;
 | 
			
		||||
                    weapon.fired += fired;
 | 
			
		||||
                } else {
 | 
			
		||||
                    playerStats.Weapons.push({ type: key, fired });
 | 
			
		||||
            case "set":
 | 
			
		||||
                for (const [category, value] of Object.entries(actionData as IStatsSet)) {
 | 
			
		||||
                    switch (category) {
 | 
			
		||||
                        case "ELO_RATING":
 | 
			
		||||
                            playerStats.Rating = value;
 | 
			
		||||
                            break;
 | 
			
		||||
 | 
			
		||||
                        case "RANK":
 | 
			
		||||
                            playerStats.Rank = value;
 | 
			
		||||
                            break;
 | 
			
		||||
 | 
			
		||||
                        case "PLAYER_LEVEL":
 | 
			
		||||
                            playerStats.PlayerLevel = value;
 | 
			
		||||
                            break;
 | 
			
		||||
 | 
			
		||||
                        default:
 | 
			
		||||
                            if (!ignoredCategories.includes(category)) {
 | 
			
		||||
                                if (!unknownCategories[action]) {
 | 
			
		||||
                                    unknownCategories[action] = [];
 | 
			
		||||
                                }
 | 
			
		||||
                                unknownCategories[action].push(category);
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
        if (HIT_ENTITY_ITEM) {
 | 
			
		||||
            playerStats.Weapons ??= [];
 | 
			
		||||
            for (const [key, hits] of Object.entries(HIT_ENTITY_ITEM)) {
 | 
			
		||||
                const weapon = playerStats.Weapons.find(element => element.type === key);
 | 
			
		||||
                if (weapon) {
 | 
			
		||||
                    weapon.hits ??= 0;
 | 
			
		||||
                    weapon.hits += hits;
 | 
			
		||||
                } else {
 | 
			
		||||
                    playerStats.Weapons.push({ type: key, hits });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
            case "displayName":
 | 
			
		||||
            case "guildId":
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
        if (HEADSHOT_ITEM) {
 | 
			
		||||
            playerStats.Weapons ??= [];
 | 
			
		||||
            for (const [key, headshots] of Object.entries(HEADSHOT_ITEM)) {
 | 
			
		||||
                const weapon = playerStats.Weapons.find(element => element.type === key);
 | 
			
		||||
                if (weapon) {
 | 
			
		||||
                    weapon.headshots ??= 0;
 | 
			
		||||
                    weapon.headshots += headshots;
 | 
			
		||||
                } else {
 | 
			
		||||
                    playerStats.Weapons.push({ type: key, headshots });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (KILL_ENEMY_ITEM) {
 | 
			
		||||
            playerStats.Weapons ??= [];
 | 
			
		||||
            for (const [key, kills] of Object.entries(KILL_ENEMY_ITEM)) {
 | 
			
		||||
                const weapon = playerStats.Weapons.find(element => element.type === key);
 | 
			
		||||
                if (weapon) {
 | 
			
		||||
                    weapon.kills ??= 0;
 | 
			
		||||
                    weapon.kills += kills;
 | 
			
		||||
                } else {
 | 
			
		||||
                    playerStats.Weapons.push({ type: key, kills });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (KILL_ENEMY) {
 | 
			
		||||
            playerStats.Enemies ??= [];
 | 
			
		||||
            for (const [key, kills] of Object.entries(KILL_ENEMY)) {
 | 
			
		||||
                const enemy = playerStats.Enemies.find(element => element.type === key);
 | 
			
		||||
                if (enemy) {
 | 
			
		||||
                    enemy.kills ??= 0;
 | 
			
		||||
                    enemy.kills += kills;
 | 
			
		||||
                } else {
 | 
			
		||||
                    playerStats.Enemies.push({ type: key, kills });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (EXECUTE_ENEMY) {
 | 
			
		||||
            playerStats.Enemies ??= [];
 | 
			
		||||
            for (const [key, executions] of Object.entries(EXECUTE_ENEMY)) {
 | 
			
		||||
                const enemy = playerStats.Enemies.find(element => element.type === key);
 | 
			
		||||
                if (enemy) {
 | 
			
		||||
                    enemy.executions ??= 0;
 | 
			
		||||
                    enemy.executions += executions;
 | 
			
		||||
                } else {
 | 
			
		||||
                    playerStats.Enemies.push({ type: key, executions });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (HEADSHOT) {
 | 
			
		||||
            playerStats.Enemies ??= [];
 | 
			
		||||
            for (const [key, headshots] of Object.entries(HEADSHOT)) {
 | 
			
		||||
                const enemy = playerStats.Enemies.find(element => element.type === key);
 | 
			
		||||
                if (enemy) {
 | 
			
		||||
                    enemy.headshots ??= 0;
 | 
			
		||||
                    enemy.headshots += headshots;
 | 
			
		||||
                } else {
 | 
			
		||||
                    playerStats.Enemies.push({ type: key, headshots });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (DIE) {
 | 
			
		||||
            playerStats.Enemies ??= [];
 | 
			
		||||
            for (const [key, deaths] of Object.entries(DIE)) {
 | 
			
		||||
                playerStats.Deaths ??= 0;
 | 
			
		||||
                playerStats.Deaths += deaths;
 | 
			
		||||
                const enemy = playerStats.Enemies.find(element => element.type === key);
 | 
			
		||||
                if (enemy) {
 | 
			
		||||
                    enemy.deaths ??= 0;
 | 
			
		||||
                    enemy.deaths += deaths;
 | 
			
		||||
                } else {
 | 
			
		||||
                    playerStats.Enemies.push({ type: key, deaths });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (MELEE_KILL) {
 | 
			
		||||
            playerStats.MeleeKills ??= 0;
 | 
			
		||||
            for (const kills of Object.values(MELEE_KILL)) {
 | 
			
		||||
                playerStats.MeleeKills += kills;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (INCOME) {
 | 
			
		||||
            playerStats.Income ??= 0;
 | 
			
		||||
            playerStats.Income += INCOME;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (CIPHER) {
 | 
			
		||||
            if (CIPHER["0"] > 0) {
 | 
			
		||||
                playerStats.CiphersFailed ??= 0;
 | 
			
		||||
                playerStats.CiphersFailed += CIPHER["0"];
 | 
			
		||||
            }
 | 
			
		||||
            if (CIPHER["1"] > 0) {
 | 
			
		||||
                playerStats.CiphersSolved ??= 0;
 | 
			
		||||
                playerStats.CiphersSolved += CIPHER["1"];
 | 
			
		||||
            }
 | 
			
		||||
            default:
 | 
			
		||||
                logger.debug(`Unknown updateStats action: ${action}`);
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (payload.timers) {
 | 
			
		||||
        const { EQUIP_WEAPON, CURRENT_MISSION_TIME, CIPHER_TIME } = payload.timers;
 | 
			
		||||
 | 
			
		||||
        if (EQUIP_WEAPON) {
 | 
			
		||||
            playerStats.Weapons ??= [];
 | 
			
		||||
            for (const [key, equipTime] of Object.entries(EQUIP_WEAPON)) {
 | 
			
		||||
                const weapon = playerStats.Weapons.find(element => element.type === key);
 | 
			
		||||
                if (weapon) {
 | 
			
		||||
                    weapon.equipTime ??= 0;
 | 
			
		||||
                    weapon.equipTime += equipTime;
 | 
			
		||||
                } else {
 | 
			
		||||
                    playerStats.Weapons.push({ type: key, equipTime });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (CURRENT_MISSION_TIME) {
 | 
			
		||||
            playerStats.TimePlayedSec ??= 0;
 | 
			
		||||
            playerStats.TimePlayedSec += CURRENT_MISSION_TIME;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (CIPHER_TIME) {
 | 
			
		||||
            playerStats.CipherTime ??= 0;
 | 
			
		||||
            playerStats.CipherTime += CIPHER_TIME;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (payload.max) {
 | 
			
		||||
        const { WEAPON_XP, MISSION_SCORE } = payload.max;
 | 
			
		||||
 | 
			
		||||
        if (WEAPON_XP) {
 | 
			
		||||
            playerStats.Weapons ??= [];
 | 
			
		||||
            for (const [key, xp] of Object.entries(WEAPON_XP)) {
 | 
			
		||||
                const weapon = playerStats.Weapons.find(element => element.type === key);
 | 
			
		||||
                if (weapon) {
 | 
			
		||||
                    weapon.xp = xp;
 | 
			
		||||
                } else {
 | 
			
		||||
                    playerStats.Weapons.push({ type: key, xp });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (MISSION_SCORE) {
 | 
			
		||||
            playerStats.Missions ??= [];
 | 
			
		||||
            for (const [key, highScore] of Object.entries(MISSION_SCORE)) {
 | 
			
		||||
                const mission = playerStats.Missions.find(element => element.type === key);
 | 
			
		||||
                if (mission) {
 | 
			
		||||
                    mission.highScore = highScore;
 | 
			
		||||
                } else {
 | 
			
		||||
                    playerStats.Missions.push({ type: key, highScore });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (payload.set) {
 | 
			
		||||
        const { ELO_RATING, RANK, PLAYER_LEVEL } = payload.set;
 | 
			
		||||
        if (ELO_RATING) playerStats.Rating = ELO_RATING;
 | 
			
		||||
        if (RANK) playerStats.Rank = RANK;
 | 
			
		||||
        if (PLAYER_LEVEL) playerStats.PlayerLevel = PLAYER_LEVEL;
 | 
			
		||||
    for (const [action, categories] of Object.entries(unknownCategories)) {
 | 
			
		||||
        logger.debug(`Unknown updateStats ${action} action categories: ${categories.join(", ")}`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await playerStats.save();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const ignoredCategories = [
 | 
			
		||||
    //add action
 | 
			
		||||
    "MISSION_STARTED",
 | 
			
		||||
    "HOST_OS",
 | 
			
		||||
    "CPU_CORES",
 | 
			
		||||
    "CPU_MODEL",
 | 
			
		||||
    "CPU_VENDOR",
 | 
			
		||||
    "GPU_CLASS",
 | 
			
		||||
    "GFX_DRIVER",
 | 
			
		||||
    "GFX_RESOLUTION",
 | 
			
		||||
    "GFX_ASPECT",
 | 
			
		||||
    "GFX_WINDOW",
 | 
			
		||||
    "GPU_VENDOR",
 | 
			
		||||
    "GFX_HDR",
 | 
			
		||||
    "SPEAKER_COUNT",
 | 
			
		||||
    "MISSION_MATCHMAKING",
 | 
			
		||||
    "PLAYER_COUNT",
 | 
			
		||||
    "HOST_MIGRATION",
 | 
			
		||||
    "DESTROY_DECORATION",
 | 
			
		||||
    "MOVEMENT",
 | 
			
		||||
    "RECEIVE_UPGRADE",
 | 
			
		||||
    "EQUIP_COSMETIC",
 | 
			
		||||
    "EQUIP_UPGRADE",
 | 
			
		||||
    "MISSION_TYPE",
 | 
			
		||||
    "MISSION_FACTION",
 | 
			
		||||
    "MISSION_PLAYED",
 | 
			
		||||
    "MISSION_PLAYED_TIME",
 | 
			
		||||
    "CPU_CLOCK",
 | 
			
		||||
    "CPU_FEATURE",
 | 
			
		||||
    "RAM",
 | 
			
		||||
    "ADDR_SPACE",
 | 
			
		||||
    "GFX_SCALE",
 | 
			
		||||
    "LOGINS",
 | 
			
		||||
    "GPU_MODEL",
 | 
			
		||||
    "MEDALS_TOP",
 | 
			
		||||
    "STATS_TIMERS_RESET",
 | 
			
		||||
    "INPUT_ACTIVITY_TIME",
 | 
			
		||||
    "LOGINS_ITEM",
 | 
			
		||||
    "TAKE_DAMAGE",
 | 
			
		||||
    "SQUAD_KILL_ENEMY",
 | 
			
		||||
    "SQUAD_HEADSHOT",
 | 
			
		||||
    "SQUAD_MELEE_KILL",
 | 
			
		||||
    "MELEE_KILL_ITEM",
 | 
			
		||||
    "TAKE_DAMAGE_ITEM",
 | 
			
		||||
    "SQUAD_KILL_ENEMY_ITEM",
 | 
			
		||||
    "SQUAD_HEADSHOT_ITEM",
 | 
			
		||||
    "SQUAD_MELEE_KILL_ITEM",
 | 
			
		||||
    "PRE_DIE",
 | 
			
		||||
    "PRE_DIE_ITEM",
 | 
			
		||||
    "GEAR_USED",
 | 
			
		||||
    "DIE_ITEM",
 | 
			
		||||
 | 
			
		||||
    // timers action
 | 
			
		||||
    "IN_SHIP_TIME",
 | 
			
		||||
    "IN_SHIP_VIEW_TIME",
 | 
			
		||||
    "MISSION_LOAD_TIME",
 | 
			
		||||
    "MISSION_TIME",
 | 
			
		||||
    "REGION_TIME",
 | 
			
		||||
    "PLATFORM_TIME",
 | 
			
		||||
    "PRE_DIE_TIME",
 | 
			
		||||
    "VEHICLE_TIME"
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@ -32,8 +32,10 @@ export interface IDojoComponentClient {
 | 
			
		||||
    pf: string; // Prefab (.level)
 | 
			
		||||
    ppf: string;
 | 
			
		||||
    pi?: IOid; // Parent ID. N/A to root.
 | 
			
		||||
    op?: string; // "Open Portal"? N/A to root.
 | 
			
		||||
    pp?: string; // "Parent Portal"? N/A to root.
 | 
			
		||||
    op?: string; // Name of the door within this room that leads to its parent. N/A to root.
 | 
			
		||||
    pp?: string; // Name of the door within the parent that leads to this room. N/A to root.
 | 
			
		||||
    Name?: string;
 | 
			
		||||
    Message?: string;
 | 
			
		||||
    RegularCredits?: number; // "Collecting Materials" state: Number of credits that were donated.
 | 
			
		||||
    MiscItems?: IMiscItem[]; // "Collecting Materials" state: Resources that were donated.
 | 
			
		||||
    CompletionTime?: IMongoDate;
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { Types } from "mongoose";
 | 
			
		||||
 | 
			
		||||
export interface IStatsView {
 | 
			
		||||
export interface IStatsClient {
 | 
			
		||||
    CiphersSolved?: number;
 | 
			
		||||
    CiphersFailed?: number;
 | 
			
		||||
    CipherTime?: number;
 | 
			
		||||
@ -10,9 +10,11 @@ export interface IStatsView {
 | 
			
		||||
    MissionsCompleted?: number;
 | 
			
		||||
    MissionsQuit?: number;
 | 
			
		||||
    MissionsFailed?: number;
 | 
			
		||||
    MissionsInterrupted?: number;
 | 
			
		||||
    MissionsDumped?: number;
 | 
			
		||||
    TimePlayedSec?: number;
 | 
			
		||||
    PickupCount?: number;
 | 
			
		||||
    Tutorial?: { [key: string]: ITutorial };
 | 
			
		||||
    Tutorial?: Map<string, ITutorial>;
 | 
			
		||||
    Abilities?: IAbility[];
 | 
			
		||||
    Rating?: number;
 | 
			
		||||
    Income?: number;
 | 
			
		||||
@ -23,9 +25,10 @@ export interface IStatsView {
 | 
			
		||||
    Deaths?: number;
 | 
			
		||||
    HealCount?: number;
 | 
			
		||||
    ReviveCount?: number;
 | 
			
		||||
    Races?: Map<string, IRace>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IStatsDatabase extends IStatsView {
 | 
			
		||||
export interface IStatsDatabase extends IStatsClient {
 | 
			
		||||
    accountOwnerId: Types.ObjectId;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -68,7 +71,11 @@ export interface IWeapon {
 | 
			
		||||
    fired?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IStatsUpload {
 | 
			
		||||
export interface IRace {
 | 
			
		||||
    highScore: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IStatsUpdate {
 | 
			
		||||
    displayName: string;
 | 
			
		||||
    guildId?: string;
 | 
			
		||||
    PS?: string;
 | 
			
		||||
@ -128,6 +135,7 @@ export interface IUploadEntry {
 | 
			
		||||
export interface IStatsMax {
 | 
			
		||||
    WEAPON_XP?: IUploadEntry;
 | 
			
		||||
    MISSION_SCORE?: IUploadEntry;
 | 
			
		||||
    RACE_SCORE?: IUploadEntry;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IStatsSet {
 | 
			
		||||
 | 
			
		||||
@ -1,559 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "VendorInfo": {
 | 
			
		||||
    "_id": { "$oid": "66d538400000000000000000" },
 | 
			
		||||
    "TypeName": "/Lotus/Types/Game/VendorManifests/Events/RadioLegionIntermission11VendorManifest",
 | 
			
		||||
    "ItemManifest": [
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperChassisBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 25, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000049" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperSystemsBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 25, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000050" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperHelmetBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 25, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000051" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/OrokinCatalyst",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 75, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000052" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/OrokinReactor",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 75, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000053" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Alertium",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 15, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 5,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000054" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Kuva",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 50, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_3",
 | 
			
		||||
        "QuantityMultiplier": 10000,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000055" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Nightwave/GlassmakerShipDeco",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 60, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "PurchaseQuantityLimit": 1,
 | 
			
		||||
        "AllowMultipurchase": false,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000056" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Helmets/ProteaAltHelmetBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000941" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Helmets/StyanaxAltDHelmetBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000942" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Weapons/Skins/GramSolsticeSkinBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000943" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Helmets/PriestAltHelmetBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000944" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Hoverboard/HoverboardStickerWolf",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 30, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000945" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Helmets/StatlessExcaliburAltHelmetBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000946" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Helmets/StatlessVaubanAltHelmetBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000947" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Helmets/PirateAltBHelmetBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000948" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Helmets/StatlessBansheeAltHelmetBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000949" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Helmets/HarlequinAltHelmetBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000950" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Helmets/NovaSlipstreamHelmetBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000951" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Helmets/NekrosShroudHelmetBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000952" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Helmets/GlassAltHelmetBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000953" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Weapons/Skins/TatsuSolsticeSkinBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000954" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Helmets/StatlessNyxAltHelmetBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000955" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Weapons/Skins/AtomosSolsticeSkinBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000956" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Helmets/StatlessTrinityAltHelmetBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000957" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Helmets/DanteAltHelmetBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000958" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Helmets/StatlessV2EmberAltHelmetBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000959" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Helmets/VaubanHelmetSoldierBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000960" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Helmets/StatlessV2FrostAltHelmetBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000961" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Helmets/StatlessSarynAltHelmetBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000962" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Helmets/ValkyrBastetHelmetBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000963" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Helmets/MirageAltBHelmetBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000964" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Helmets/OberonAltHelmetBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000965" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Helmets/StatlessV2TrinityAltHelmetBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000966" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Helmets/FairyAltTwoHelmetBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000967" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Helmets/DragonAltHelmetBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000968" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Helmets/JadeAltHelmetBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 35, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_0",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000969" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/Aura/PlayerSniperDamageAuraMod",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 20, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_1",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000970" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/Aura/PlayerSprintAuraMod",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 20, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_1",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000971" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Event/Arbitration/InfCrpShockSwarmRifleArbitrationMod",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 20, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_1",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000972" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/Aura/PlayerEnemyRadarAuraMod",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 20, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_1",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000973" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/Nightwave/MagnusNightwaveMod",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 20, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_1",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000974" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/Aura/PlayerEnergyHealthRegenAuraMod",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 20, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_1",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000975" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/Aura/PlayerRifleAmmoAuraMod",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 20, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_1",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000976" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/Aura/InfestationSpeedReductionAuraMod",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 20, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_1",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000977" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Event/Nightwave/NightwaveTiberonAugmentMod",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 20, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_1",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000978" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/Aura/PlayerHealthAuraMod",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 20, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_1",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000979" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Weapons/Skins/GrnHammerBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 30, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_2",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000980" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Weapons/Skins/GrnAxeBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 30, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_2",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000981" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Weapons/Skins/DesertGrinlokSkinBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 30, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_2",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000982" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Weapons/Skins/DaggerAxeBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 30, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_2",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000983" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Weapons/Skins/ShockPlinxSkinBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 30, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_3",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000984" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Clan/GlassmakerEmblemItem",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 30, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_3",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000985" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Sigils/NoraSeasonTwoSigil",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 30, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_3",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000986" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/PhotoboothTileWolfSixPrison",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 45, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_3",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000987" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Weapons/Skins/ShockExergisSkinBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 30, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_3",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000988" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Weapons/HeatDaggerBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 50, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_3",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000989" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Weapons/CeramicDaggerBlueprint",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 50, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_3",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000990" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/PvPMods/Rifle/GrakataUnlimitedAmmo",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 20, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_4",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000991" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/PvPMods/Rifle/SupraHigherAccuracyAiming",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 20, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_4",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000992" }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "StoreItem": "/Lotus/StoreItems/Powersuits/Ranger/RangerQuiverPvPAugmentCard",
 | 
			
		||||
        "ItemPrices": [{ "ItemCount": 20, "ItemType": "/Lotus/Types/Items/MiscItems/NoraIntermissionElevenCreds", "ProductCategory": "MiscItems" }],
 | 
			
		||||
        "Bin": "BIN_4",
 | 
			
		||||
        "QuantityMultiplier": 1,
 | 
			
		||||
        "Expiry": { "$date": { "$numberLong": "2051240400000" } },
 | 
			
		||||
        "AllowMultipurchase": true,
 | 
			
		||||
        "Id": { "$oid": "001400140000000000000993" }
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "Expiry": { "$date": { "$numberLong": "2051240400000" } }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -150,6 +150,14 @@
 | 
			
		||||
      "Seed": 353,
 | 
			
		||||
      "Nodes": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "_id": { "$oid": "663a4fc5ba6f84724fa48060" },
 | 
			
		||||
      "Activation": { "$date": { "$numberLong": "1715097541439" } },
 | 
			
		||||
      "Expiry": { "$date": { "$numberLong": "2000000000000" } },
 | 
			
		||||
      "Tag": "RadioLegionIntermission12Syndicate",
 | 
			
		||||
      "Seed": 353,
 | 
			
		||||
      "Nodes": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "_id": { "$oid": "663a4fc5ba6f84724fa48057" },
 | 
			
		||||
      "Activation": { "$date": { "$numberLong": "1715097541439" } },
 | 
			
		||||
@ -1115,8 +1123,8 @@
 | 
			
		||||
  "SeasonInfo": {
 | 
			
		||||
    "Activation": { "$date": { "$numberLong": "1715796000000" } },
 | 
			
		||||
    "Expiry": { "$date": { "$numberLong": "2000000000000" } },
 | 
			
		||||
    "AffiliationTag": "RadioLegionIntermission11Syndicate",
 | 
			
		||||
    "Season": 13,
 | 
			
		||||
    "AffiliationTag": "RadioLegionIntermission12Syndicate",
 | 
			
		||||
    "Season": 14,
 | 
			
		||||
    "Phase": 0,
 | 
			
		||||
    "Params": "",
 | 
			
		||||
    "ActiveChallenges": [
 | 
			
		||||
 | 
			
		||||
@ -101,7 +101,7 @@
 | 
			
		||||
                            <div class="card-body">
 | 
			
		||||
                                <p class="card-text" id="RegularCredits-owned"></p>
 | 
			
		||||
                                <form class="input-group" onsubmit="doAddCurrency('RegularCredits');return false;">
 | 
			
		||||
                                    <input class="form-control" id="RegularCredits-delta" type="number" value="1000000" step="1000000" />
 | 
			
		||||
                                    <input class="form-control" id="RegularCredits-delta" type="number" value="1000000" />
 | 
			
		||||
                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
			
		||||
                                </form>
 | 
			
		||||
                            </div>
 | 
			
		||||
@ -113,7 +113,7 @@
 | 
			
		||||
                            <div class="card-body">
 | 
			
		||||
                                <p class="card-text" id="PremiumCredits-owned"></p>
 | 
			
		||||
                                <form class="input-group" onsubmit="doAddCurrency('PremiumCredits');return false;">
 | 
			
		||||
                                    <input class="form-control" id="PremiumCredits-delta" type="number" value="100" step="100" />
 | 
			
		||||
                                    <input class="form-control" id="PremiumCredits-delta" type="number" value="100" />
 | 
			
		||||
                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
			
		||||
                                </form>
 | 
			
		||||
                            </div>
 | 
			
		||||
@ -125,7 +125,7 @@
 | 
			
		||||
                            <div class="card-body">
 | 
			
		||||
                                <p class="card-text" id="FusionPoints-owned"></p>
 | 
			
		||||
                                <form class="input-group" onsubmit="doAddCurrency('FusionPoints');return false;">
 | 
			
		||||
                                    <input class="form-control" id="FusionPoints-delta" type="number" value="1000" step="1000" />
 | 
			
		||||
                                    <input class="form-control" id="FusionPoints-delta" type="number" value="1000" />
 | 
			
		||||
                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
			
		||||
                                </form>
 | 
			
		||||
                            </div>
 | 
			
		||||
@ -137,7 +137,7 @@
 | 
			
		||||
                            <div class="card-body">
 | 
			
		||||
                                <p class="card-text" id="PrimeTokens-owned"></p>
 | 
			
		||||
                                <form class="input-group" onsubmit="doAddCurrency('PrimeTokens');return false;">
 | 
			
		||||
                                    <input class="form-control" id="PrimeTokens-delta" type="number" value="1" step="1" />
 | 
			
		||||
                                    <input class="form-control" id="PrimeTokens-delta" type="number" value="1" />
 | 
			
		||||
                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
			
		||||
                                </form>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
@ -1037,6 +1037,7 @@ function doAddAllMods() {
 | 
			
		||||
    for (const child of document.getElementById("datalist-mods").children) {
 | 
			
		||||
        modsAll.add(child.getAttribute("data-key"));
 | 
			
		||||
    }
 | 
			
		||||
    modsAll.delete("/Lotus/Upgrades/Mods/Fusers/LegendaryModFuser");
 | 
			
		||||
 | 
			
		||||
    revalidateAuthz(() => {
 | 
			
		||||
        const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user