chore(webui): improving inconsistent, long string #2344
@ -1,4 +1,4 @@
 | 
				
			|||||||
FROM node:18-alpine3.19
 | 
					FROM node:24-alpine3.21
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENV APP_MONGODB_URL=mongodb://mongodb:27017/openWF
 | 
					ENV APP_MONGODB_URL=mongodb://mongodb:27017/openWF
 | 
				
			||||||
ENV APP_MY_ADDRESS=localhost
 | 
					ENV APP_MY_ADDRESS=localhost
 | 
				
			||||||
 | 
				
			|||||||
@ -33,3 +33,4 @@ SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [confi
 | 
				
			|||||||
  - `RadioLegion2Syndicate` for The Emissary
 | 
					  - `RadioLegion2Syndicate` for The Emissary
 | 
				
			||||||
  - `RadioLegionIntermissionSyndicate` for Intermission I
 | 
					  - `RadioLegionIntermissionSyndicate` for Intermission I
 | 
				
			||||||
  - `RadioLegionSyndicate` for The Wolf of Saturn Six
 | 
					  - `RadioLegionSyndicate` for The Wolf of Saturn Six
 | 
				
			||||||
 | 
					- `worldState.circuitGameModes` can be provided with an array of valid game modes (`Survival`, `VoidFlood`, `Excavation`, `Defense`, `Exterminate`, `Assassination`, `Alchemy`)
 | 
				
			||||||
 | 
				
			|||||||
@ -58,7 +58,8 @@
 | 
				
			|||||||
    "starDays": true,
 | 
					    "starDays": true,
 | 
				
			||||||
    "eidolonOverride": "",
 | 
					    "eidolonOverride": "",
 | 
				
			||||||
    "vallisOverride": "",
 | 
					    "vallisOverride": "",
 | 
				
			||||||
    "nightwaveOverride": ""
 | 
					    "nightwaveOverride": "",
 | 
				
			||||||
 | 
					    "circuitGameModes": null
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "dev": {
 | 
					  "dev": {
 | 
				
			||||||
    "keepVendorsExpired": false
 | 
					    "keepVendorsExpired": false
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										8
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -18,7 +18,7 @@
 | 
				
			|||||||
        "morgan": "^1.10.0",
 | 
					        "morgan": "^1.10.0",
 | 
				
			||||||
        "ncp": "^2.0.0",
 | 
					        "ncp": "^2.0.0",
 | 
				
			||||||
        "typescript": "^5.5",
 | 
					        "typescript": "^5.5",
 | 
				
			||||||
        "warframe-public-export-plus": "^0.5.67",
 | 
					        "warframe-public-export-plus": "^0.5.68",
 | 
				
			||||||
        "warframe-riven-info": "^0.1.2",
 | 
					        "warframe-riven-info": "^0.1.2",
 | 
				
			||||||
        "winston": "^3.17.0",
 | 
					        "winston": "^3.17.0",
 | 
				
			||||||
        "winston-daily-rotate-file": "^5.0.0"
 | 
					        "winston-daily-rotate-file": "^5.0.0"
 | 
				
			||||||
@ -3814,9 +3814,9 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/warframe-public-export-plus": {
 | 
					    "node_modules/warframe-public-export-plus": {
 | 
				
			||||||
      "version": "0.5.67",
 | 
					      "version": "0.5.68",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.67.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.68.tgz",
 | 
				
			||||||
      "integrity": "sha512-LsnZD2E5PTA+5MK9kDGvM/hFDtg8sb0EwQ4hKH5ILqrSgz30a9W8785v77RSsL1AEVF8dfb/lZcSTCJq1DZHzQ=="
 | 
					      "integrity": "sha512-KMmwCVeQ4k+EN73UZqxnM+qQdPsST8geWoJCP7US5LT6JcRxa8ptmqYXwCzaLtckBLZyVbamsxKZAxPPJckxsA=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/warframe-riven-info": {
 | 
					    "node_modules/warframe-riven-info": {
 | 
				
			||||||
      "version": "0.1.2",
 | 
					      "version": "0.1.2",
 | 
				
			||||||
 | 
				
			|||||||
@ -25,7 +25,7 @@
 | 
				
			|||||||
    "morgan": "^1.10.0",
 | 
					    "morgan": "^1.10.0",
 | 
				
			||||||
    "ncp": "^2.0.0",
 | 
					    "ncp": "^2.0.0",
 | 
				
			||||||
    "typescript": "^5.5",
 | 
					    "typescript": "^5.5",
 | 
				
			||||||
    "warframe-public-export-plus": "^0.5.67",
 | 
					    "warframe-public-export-plus": "^0.5.68",
 | 
				
			||||||
    "warframe-riven-info": "^0.1.2",
 | 
					    "warframe-riven-info": "^0.1.2",
 | 
				
			||||||
    "winston": "^3.17.0",
 | 
					    "winston": "^3.17.0",
 | 
				
			||||||
    "winston-daily-rotate-file": "^5.0.0"
 | 
					    "winston-daily-rotate-file": "^5.0.0"
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										107
									
								
								src/controllers/api/crewShipFusionController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								src/controllers/api/crewShipFusionController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,107 @@
 | 
				
			|||||||
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { addMiscItems, freeUpSlot, getInventory, updateCurrency } from "@/src/services/inventoryService";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
 | 
					import { IOid } from "@/src/types/commonTypes";
 | 
				
			||||||
 | 
					import { ICrewShipComponentFingerprint, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
				
			||||||
 | 
					import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
				
			||||||
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { ExportCustoms, ExportDojoRecipes } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const crewShipFusionController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
 | 
					    const payload = getJSONfromString<ICrewShipFusionRequest>(String(req.body));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const isWeapon = inventory.CrewShipWeapons.id(payload.PartA.$oid);
 | 
				
			||||||
 | 
					    const itemA = isWeapon ?? inventory.CrewShipWeaponSkins.id(payload.PartA.$oid)!;
 | 
				
			||||||
 | 
					    const category = isWeapon ? "CrewShipWeapons" : "CrewShipWeaponSkins";
 | 
				
			||||||
 | 
					    const salvageCategory = isWeapon ? "CrewShipSalvagedWeapons" : "CrewShipSalvagedWeaponSkins";
 | 
				
			||||||
 | 
					    const itemB = inventory[payload.SourceRecipe ? salvageCategory : category].id(payload.PartB.$oid)!;
 | 
				
			||||||
 | 
					    const tierA = itemA.ItemType.charCodeAt(itemA.ItemType.length - 1) - 65;
 | 
				
			||||||
 | 
					    const tierB = itemB.ItemType.charCodeAt(itemB.ItemType.length - 1) - 65;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const inventoryChanges: IInventoryChanges = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Charge partial repair cost if fusing with an identified but unrepaired part
 | 
				
			||||||
 | 
					    if (payload.SourceRecipe) {
 | 
				
			||||||
 | 
					        const recipe = ExportDojoRecipes.research[payload.SourceRecipe];
 | 
				
			||||||
 | 
					        updateCurrency(inventory, Math.round(recipe.price * 0.4), false, inventoryChanges);
 | 
				
			||||||
 | 
					        const miscItemChanges = recipe.ingredients.map(x => ({ ...x, ItemCount: Math.round(x.ItemCount * -0.4) }));
 | 
				
			||||||
 | 
					        addMiscItems(inventory, miscItemChanges);
 | 
				
			||||||
 | 
					        inventoryChanges.MiscItems = miscItemChanges;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Remove inferior item
 | 
				
			||||||
 | 
					    if (payload.SourceRecipe) {
 | 
				
			||||||
 | 
					        inventory[salvageCategory].pull({ _id: payload.PartB.$oid });
 | 
				
			||||||
 | 
					        inventoryChanges.RemovedIdItems = [{ ItemId: payload.PartB }];
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        const inferiorId = tierA < tierB ? payload.PartA : payload.PartB;
 | 
				
			||||||
 | 
					        inventory[category].pull({ _id: inferiorId.$oid });
 | 
				
			||||||
 | 
					        inventoryChanges.RemovedIdItems = [{ ItemId: inferiorId }];
 | 
				
			||||||
 | 
					        freeUpSlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS);
 | 
				
			||||||
 | 
					        inventoryChanges[InventorySlot.RJ_COMPONENT_AND_ARMAMENTS] = { count: -1, platinum: 0, Slots: 1 };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Upgrade superior item
 | 
				
			||||||
 | 
					    const superiorItem = tierA < tierB ? itemB : itemA;
 | 
				
			||||||
 | 
					    const inferiorItem = tierA < tierB ? itemA : itemB;
 | 
				
			||||||
 | 
					    const fingerprint: ICrewShipComponentFingerprint = JSON.parse(
 | 
				
			||||||
 | 
					        superiorItem.UpgradeFingerprint!
 | 
				
			||||||
 | 
					    ) as ICrewShipComponentFingerprint;
 | 
				
			||||||
 | 
					    const inferiorFingerprint: ICrewShipComponentFingerprint = inferiorItem.UpgradeFingerprint
 | 
				
			||||||
 | 
					        ? (JSON.parse(inferiorItem.UpgradeFingerprint) as ICrewShipComponentFingerprint)
 | 
				
			||||||
 | 
					        : { compat: "", buffs: [] };
 | 
				
			||||||
 | 
					    if (isWeapon) {
 | 
				
			||||||
 | 
					        for (let i = 0; i != fingerprint.buffs.length; ++i) {
 | 
				
			||||||
 | 
					            const buffA = fingerprint.buffs[i];
 | 
				
			||||||
 | 
					            const buffB = i < inferiorFingerprint.buffs.length ? inferiorFingerprint.buffs[i] : undefined;
 | 
				
			||||||
 | 
					            const fvalA = buffA.Value / 0x3fffffff;
 | 
				
			||||||
 | 
					            const fvalB = (buffB?.Value ?? 0) / 0x3fffffff;
 | 
				
			||||||
 | 
					            const percA = 0.3 + fvalA * (0.6 - 0.3);
 | 
				
			||||||
 | 
					            const percB = 0.3 + fvalB * (0.6 - 0.3);
 | 
				
			||||||
 | 
					            const newPerc = Math.min(0.6, Math.max(percA, percB) * FUSE_MULTIPLIERS[Math.abs(tierA - tierB)]);
 | 
				
			||||||
 | 
					            const newFval = (newPerc - 0.3) / (0.6 - 0.3);
 | 
				
			||||||
 | 
					            buffA.Value = Math.trunc(newFval * 0x3fffffff);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        const superiorMeta = ExportCustoms[superiorItem.ItemType].randomisedUpgrades ?? [];
 | 
				
			||||||
 | 
					        const inferiorMeta = ExportCustoms[inferiorItem.ItemType].randomisedUpgrades ?? [];
 | 
				
			||||||
 | 
					        for (let i = 0; i != inferiorFingerprint.buffs.length; ++i) {
 | 
				
			||||||
 | 
					            const buffA = fingerprint.buffs[i];
 | 
				
			||||||
 | 
					            const buffB = inferiorFingerprint.buffs[i];
 | 
				
			||||||
 | 
					            const fvalA = buffA.Value / 0x3fffffff;
 | 
				
			||||||
 | 
					            const fvalB = buffB.Value / 0x3fffffff;
 | 
				
			||||||
 | 
					            const rangeA = superiorMeta[i].range;
 | 
				
			||||||
 | 
					            const rangeB = inferiorMeta[i].range;
 | 
				
			||||||
 | 
					            const percA = rangeA[0] + fvalA * (rangeA[1] - rangeA[0]);
 | 
				
			||||||
 | 
					            const percB = rangeB[0] + fvalB * (rangeB[1] - rangeB[0]);
 | 
				
			||||||
 | 
					            const newPerc = Math.min(rangeA[1], Math.max(percA, percB) * FUSE_MULTIPLIERS[Math.abs(tierA - tierB)]);
 | 
				
			||||||
 | 
					            const newFval = (newPerc - rangeA[0]) / (rangeA[1] - rangeA[0]);
 | 
				
			||||||
 | 
					            buffA.Value = Math.trunc(newFval * 0x3fffffff);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (inferiorFingerprint.SubroutineIndex) {
 | 
				
			||||||
 | 
					            const useSuperiorSubroutine = tierA < tierB ? !payload.UseSubroutineA : payload.UseSubroutineA;
 | 
				
			||||||
 | 
					            if (!useSuperiorSubroutine) {
 | 
				
			||||||
 | 
					                fingerprint.SubroutineIndex = inferiorFingerprint.SubroutineIndex;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    superiorItem.UpgradeFingerprint = JSON.stringify(fingerprint);
 | 
				
			||||||
 | 
					    // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
				
			||||||
 | 
					    inventoryChanges[category] = [superiorItem.toJSON() as any];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					    res.json({
 | 
				
			||||||
 | 
					        InventoryChanges: inventoryChanges
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ICrewShipFusionRequest {
 | 
				
			||||||
 | 
					    PartA: IOid;
 | 
				
			||||||
 | 
					    PartB: IOid;
 | 
				
			||||||
 | 
					    SourceRecipe: string;
 | 
				
			||||||
 | 
					    UseSubroutineA: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const FUSE_MULTIPLIERS = [1.1, 1.05, 1.02];
 | 
				
			||||||
@ -11,7 +11,7 @@ import {
 | 
				
			|||||||
    scaleRequiredCount,
 | 
					    scaleRequiredCount,
 | 
				
			||||||
    setGuildTechLogState
 | 
					    setGuildTechLogState
 | 
				
			||||||
} from "@/src/services/guildService";
 | 
					} from "@/src/services/guildService";
 | 
				
			||||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
					import { ExportDojoRecipes, ExportRailjackWeapons } from "warframe-public-export-plus";
 | 
				
			||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
					import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    addCrewShipWeaponSkin,
 | 
					    addCrewShipWeaponSkin,
 | 
				
			||||||
@ -442,6 +442,7 @@ const finishComponentRepair = (
 | 
				
			|||||||
        ...(category == "CrewShipWeaponSkins"
 | 
					        ...(category == "CrewShipWeaponSkins"
 | 
				
			||||||
            ? addCrewShipWeaponSkin(inventory, salvageItem.ItemType, salvageItem.UpgradeFingerprint)
 | 
					            ? addCrewShipWeaponSkin(inventory, salvageItem.ItemType, salvageItem.UpgradeFingerprint)
 | 
				
			||||||
            : addEquipment(inventory, category, salvageItem.ItemType, {
 | 
					            : addEquipment(inventory, category, salvageItem.ItemType, {
 | 
				
			||||||
 | 
					                  UpgradeType: ExportRailjackWeapons[salvageItem.ItemType].defaultUpgrades?.[0].ItemType,
 | 
				
			||||||
                  UpgradeFingerprint: salvageItem.UpgradeFingerprint
 | 
					                  UpgradeFingerprint: salvageItem.UpgradeFingerprint
 | 
				
			||||||
              })),
 | 
					              })),
 | 
				
			||||||
        ...occupySlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS, false)
 | 
					        ...occupySlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS, false)
 | 
				
			||||||
 | 
				
			|||||||
@ -17,6 +17,7 @@ import {
 | 
				
			|||||||
    IKnifeResponse
 | 
					    IKnifeResponse
 | 
				
			||||||
} from "@/src/helpers/nemesisHelpers";
 | 
					} from "@/src/helpers/nemesisHelpers";
 | 
				
			||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
					import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
 | 
					import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
 | 
				
			||||||
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
 | 
					import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
 | 
				
			||||||
import { freeUpSlot, getInventory } from "@/src/services/inventoryService";
 | 
					import { freeUpSlot, getInventory } from "@/src/services/inventoryService";
 | 
				
			||||||
import { getAccountForRequest } from "@/src/services/loginService";
 | 
					import { getAccountForRequest } from "@/src/services/loginService";
 | 
				
			||||||
@ -202,16 +203,28 @@ export const nemesisController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            guess[body.position].result = correct ? GUESS_CORRECT : GUESS_INCORRECT;
 | 
					            guess[body.position].result = correct ? GUESS_CORRECT : GUESS_INCORRECT;
 | 
				
			||||||
            inventory.Nemesis!.GuessHistory[inventory.Nemesis!.GuessHistory.length - 1] = encodeNemesisGuess(guess);
 | 
					            inventory.Nemesis!.GuessHistory[inventory.Nemesis!.GuessHistory.length - 1] = encodeNemesisGuess(guess);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Increase rank if incorrect
 | 
					            const response: INemesisRequiemResponse = {};
 | 
				
			||||||
            let RankIncrease: number | undefined;
 | 
					            if (correct) {
 | 
				
			||||||
            if (!correct) {
 | 
					                if (body.position == 2) {
 | 
				
			||||||
                RankIncrease = 1;
 | 
					                    // That was all 3 guesses correct, nemesis is now weakened.
 | 
				
			||||||
 | 
					                    inventory.Nemesis!.InfNodes = [
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            Node: getNemesisManifest(inventory.Nemesis!.manifest).showdownNode,
 | 
				
			||||||
 | 
					                            Influence: 1
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    ];
 | 
				
			||||||
 | 
					                    inventory.Nemesis!.Weakened = true;
 | 
				
			||||||
 | 
					                    await consumePasscodeModCharges(inventory, response);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // Guess was incorrect, increase rank
 | 
				
			||||||
 | 
					                response.RankIncrease = 1;
 | 
				
			||||||
                const manifest = getNemesisManifest(inventory.Nemesis!.manifest);
 | 
					                const manifest = getNemesisManifest(inventory.Nemesis!.manifest);
 | 
				
			||||||
                inventory.Nemesis!.Rank = Math.min(inventory.Nemesis!.Rank + 1, manifest.systemIndexes.length - 1);
 | 
					                inventory.Nemesis!.Rank = Math.min(inventory.Nemesis!.Rank + 1, manifest.systemIndexes.length - 1);
 | 
				
			||||||
                inventory.Nemesis!.InfNodes = getInfNodes(manifest, inventory.Nemesis!.Rank);
 | 
					                inventory.Nemesis!.InfNodes = getInfNodes(manifest, inventory.Nemesis!.Rank);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            await inventory.save();
 | 
					            await inventory.save();
 | 
				
			||||||
            res.json({ RankIncrease });
 | 
					            res.json(response);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    } else if ((req.query.mode as string) == "rs") {
 | 
					    } else if ((req.query.mode as string) == "rs") {
 | 
				
			||||||
        // report spawn; POST but no application data in body
 | 
					        // report spawn; POST but no application data in body
 | 
				
			||||||
@ -299,20 +312,11 @@ export const nemesisController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        ];
 | 
					        ];
 | 
				
			||||||
        inventory.Nemesis!.Weakened = true;
 | 
					        inventory.Nemesis!.Weakened = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const response: IKnifeResponse & { target: INemesisClient } = {
 | 
					        const response: INemesisWeakenResponse = {
 | 
				
			||||||
            target: inventory.toJSON<IInventoryClient>().Nemesis!
 | 
					            target: inventory.toJSON<IInventoryClient>().Nemesis!
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Consume charge of the correct requiem mod(s)
 | 
					        await consumePasscodeModCharges(inventory, response);
 | 
				
			||||||
        const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!;
 | 
					 | 
				
			||||||
        const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid);
 | 
					 | 
				
			||||||
        const dataknifeConfigIndex = dataknifeLoadout?.s?.mod ?? 0;
 | 
					 | 
				
			||||||
        const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex].Upgrades!;
 | 
					 | 
				
			||||||
        const modTypes = getNemesisPasscodeModTypes(inventory.Nemesis!);
 | 
					 | 
				
			||||||
        for (const modType of modTypes) {
 | 
					 | 
				
			||||||
            const upgrade = getKnifeUpgrade(inventory, dataknifeUpgrades, modType);
 | 
					 | 
				
			||||||
            consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await inventory.save();
 | 
					        await inventory.save();
 | 
				
			||||||
        res.json(response);
 | 
					        res.json(response);
 | 
				
			||||||
@ -370,11 +374,19 @@ interface INemesisRequiemRequest {
 | 
				
			|||||||
    knife?: IKnife;
 | 
					    knife?: IKnife;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface INemesisRequiemResponse extends IKnifeResponse {
 | 
				
			||||||
 | 
					    RankIncrease?: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// interface INemesisWeakenRequest {
 | 
					// interface INemesisWeakenRequest {
 | 
				
			||||||
//     target: INemesisClient;
 | 
					//     target: INemesisClient;
 | 
				
			||||||
//     knife: IKnife;
 | 
					//     knife: IKnife;
 | 
				
			||||||
// }
 | 
					// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface INemesisWeakenResponse extends IKnifeResponse {
 | 
				
			||||||
 | 
					    target: INemesisClient;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IKnife {
 | 
					interface IKnife {
 | 
				
			||||||
    Item: IEquipmentClient;
 | 
					    Item: IEquipmentClient;
 | 
				
			||||||
    Skins: IWeaponSkinClient[];
 | 
					    Skins: IWeaponSkinClient[];
 | 
				
			||||||
@ -383,3 +395,18 @@ interface IKnife {
 | 
				
			|||||||
    AttachedUpgrades: IUpgradeClient[];
 | 
					    AttachedUpgrades: IUpgradeClient[];
 | 
				
			||||||
    HiddenWhenHolstered: boolean;
 | 
					    HiddenWhenHolstered: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const consumePasscodeModCharges = async (
 | 
				
			||||||
 | 
					    inventory: TInventoryDatabaseDocument,
 | 
				
			||||||
 | 
					    response: IKnifeResponse
 | 
				
			||||||
 | 
					): Promise<void> => {
 | 
				
			||||||
 | 
					    const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!;
 | 
				
			||||||
 | 
					    const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid);
 | 
				
			||||||
 | 
					    const dataknifeConfigIndex = dataknifeLoadout?.s?.mod ?? 0;
 | 
				
			||||||
 | 
					    const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex].Upgrades!;
 | 
				
			||||||
 | 
					    const modTypes = getNemesisPasscodeModTypes(inventory.Nemesis!);
 | 
				
			||||||
 | 
					    for (const modType of modTypes) {
 | 
				
			||||||
 | 
					        const upgrade = getKnifeUpgrade(inventory, dataknifeUpgrades, modType);
 | 
				
			||||||
 | 
					        consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -11,8 +11,11 @@ export const updateChallengeProgressController: RequestHandler = async (req, res
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const inventory = await getInventory(
 | 
					    const inventory = await getInventory(
 | 
				
			||||||
        account._id.toString(),
 | 
					        account._id.toString(),
 | 
				
			||||||
        "ChallengeProgress SeasonChallengeHistory Affiliations"
 | 
					        "ChallengesFixVersion ChallengeProgress SeasonChallengeHistory Affiliations"
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					    if (challenges.ChallengesFixVersion !== undefined) {
 | 
				
			||||||
 | 
					        inventory.ChallengesFixVersion = challenges.ChallengesFixVersion;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    let affiliationMods: IAffiliationMods[] = [];
 | 
					    let affiliationMods: IAffiliationMods[] = [];
 | 
				
			||||||
    if (challenges.ChallengeProgress) {
 | 
					    if (challenges.ChallengeProgress) {
 | 
				
			||||||
        affiliationMods = addChallenges(
 | 
					        affiliationMods = addChallenges(
 | 
				
			||||||
@ -40,6 +43,7 @@ export const updateChallengeProgressController: RequestHandler = async (req, res
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IUpdateChallengeProgressRequest {
 | 
					interface IUpdateChallengeProgressRequest {
 | 
				
			||||||
 | 
					    ChallengesFixVersion?: number;
 | 
				
			||||||
    ChallengeProgress?: IChallengeProgress[];
 | 
					    ChallengeProgress?: IChallengeProgress[];
 | 
				
			||||||
    SeasonChallengeHistory?: ISeasonChallenge[];
 | 
					    SeasonChallengeHistory?: ISeasonChallenge[];
 | 
				
			||||||
    SeasonChallengeCompletions?: ISeasonChallenge[];
 | 
					    SeasonChallengeCompletions?: ISeasonChallenge[];
 | 
				
			||||||
 | 
				
			|||||||
@ -1703,7 +1703,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
 | 
				
			|||||||
        LastInventorySync: Schema.Types.ObjectId,
 | 
					        LastInventorySync: Schema.Types.ObjectId,
 | 
				
			||||||
        Mailbox: MailboxSchema,
 | 
					        Mailbox: MailboxSchema,
 | 
				
			||||||
        HandlerPoints: Number,
 | 
					        HandlerPoints: Number,
 | 
				
			||||||
        ChallengesFixVersion: { type: Number, default: 6 },
 | 
					        ChallengesFixVersion: Number,
 | 
				
			||||||
        PlayedParkourTutorial: Boolean,
 | 
					        PlayedParkourTutorial: Boolean,
 | 
				
			||||||
        //ActiveLandscapeTraps: [Schema.Types.Mixed],
 | 
					        //ActiveLandscapeTraps: [Schema.Types.Mixed],
 | 
				
			||||||
        //RepVotes: [Schema.Types.Mixed],
 | 
					        //RepVotes: [Schema.Types.Mixed],
 | 
				
			||||||
 | 
				
			|||||||
@ -33,6 +33,7 @@ import { createAllianceController } from "@/src/controllers/api/createAllianceCo
 | 
				
			|||||||
import { createGuildController } from "@/src/controllers/api/createGuildController";
 | 
					import { createGuildController } from "@/src/controllers/api/createGuildController";
 | 
				
			||||||
import { creditsController } from "@/src/controllers/api/creditsController";
 | 
					import { creditsController } from "@/src/controllers/api/creditsController";
 | 
				
			||||||
import { crewMembersController } from "@/src/controllers/api/crewMembersController";
 | 
					import { crewMembersController } from "@/src/controllers/api/crewMembersController";
 | 
				
			||||||
 | 
					import { crewShipFusionController } from "@/src/controllers/api/crewShipFusionController";
 | 
				
			||||||
import { crewShipIdentifySalvageController } from "@/src/controllers/api/crewShipIdentifySalvageController";
 | 
					import { crewShipIdentifySalvageController } from "@/src/controllers/api/crewShipIdentifySalvageController";
 | 
				
			||||||
import { customizeGuildRanksController } from "@/src/controllers/api/customizeGuildRanksController";
 | 
					import { customizeGuildRanksController } from "@/src/controllers/api/customizeGuildRanksController";
 | 
				
			||||||
import { customObstacleCourseLeaderboardController } from "@/src/controllers/api/customObstacleCourseLeaderboardController";
 | 
					import { customObstacleCourseLeaderboardController } from "@/src/controllers/api/customObstacleCourseLeaderboardController";
 | 
				
			||||||
@ -247,6 +248,7 @@ apiRouter.post("/contributeToVault.php", contributeToVaultController);
 | 
				
			|||||||
apiRouter.post("/createAlliance.php", createAllianceController);
 | 
					apiRouter.post("/createAlliance.php", createAllianceController);
 | 
				
			||||||
apiRouter.post("/createGuild.php", createGuildController);
 | 
					apiRouter.post("/createGuild.php", createGuildController);
 | 
				
			||||||
apiRouter.post("/crewMembers.php", crewMembersController);
 | 
					apiRouter.post("/crewMembers.php", crewMembersController);
 | 
				
			||||||
 | 
					apiRouter.post("/crewShipFusion.php", crewShipFusionController);
 | 
				
			||||||
apiRouter.post("/crewShipIdentifySalvage.php", crewShipIdentifySalvageController);
 | 
					apiRouter.post("/crewShipIdentifySalvage.php", crewShipIdentifySalvageController);
 | 
				
			||||||
apiRouter.post("/customizeGuildRanks.php", customizeGuildRanksController);
 | 
					apiRouter.post("/customizeGuildRanks.php", customizeGuildRanksController);
 | 
				
			||||||
apiRouter.post("/customObstacleCourseLeaderboard.php", customObstacleCourseLeaderboardController);
 | 
					apiRouter.post("/customObstacleCourseLeaderboard.php", customObstacleCourseLeaderboardController);
 | 
				
			||||||
 | 
				
			|||||||
@ -65,6 +65,7 @@ interface IConfig {
 | 
				
			|||||||
        eidolonOverride?: string;
 | 
					        eidolonOverride?: string;
 | 
				
			||||||
        vallisOverride?: string;
 | 
					        vallisOverride?: string;
 | 
				
			||||||
        nightwaveOverride?: string;
 | 
					        nightwaveOverride?: string;
 | 
				
			||||||
 | 
					        circuitGameModes?: string[];
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    dev?: {
 | 
					    dev?: {
 | 
				
			||||||
        keepVendorsExpired?: boolean;
 | 
					        keepVendorsExpired?: boolean;
 | 
				
			||||||
 | 
				
			|||||||
@ -50,14 +50,17 @@ export const createNewEventMessages = async (req: Request): Promise<void> => {
 | 
				
			|||||||
    await account.save();
 | 
					    await account.save();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const createMessage = async (accountId: string | Types.ObjectId, messages: IMessageCreationTemplate[]) => {
 | 
					export const createMessage = async (
 | 
				
			||||||
 | 
					    accountId: string | Types.ObjectId,
 | 
				
			||||||
 | 
					    messages: IMessageCreationTemplate[]
 | 
				
			||||||
 | 
					): Promise<HydratedDocument<IMessageDatabase>[]> => {
 | 
				
			||||||
    const ownerIdMessages = messages.map(m => ({
 | 
					    const ownerIdMessages = messages.map(m => ({
 | 
				
			||||||
        ...m,
 | 
					        ...m,
 | 
				
			||||||
        ownerId: accountId
 | 
					        ownerId: accountId
 | 
				
			||||||
    }));
 | 
					    }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const savedMessages = await Inbox.insertMany(ownerIdMessages);
 | 
					    const savedMessages = await Inbox.insertMany(ownerIdMessages);
 | 
				
			||||||
    return savedMessages;
 | 
					    return savedMessages as HydratedDocument<IMessageDatabase>[];
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IMessageCreationTemplate extends Omit<IMessageDatabase, "_id" | "date" | "ownerId"> {
 | 
					export interface IMessageCreationTemplate extends Omit<IMessageDatabase, "_id" | "date" | "ownerId"> {
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,6 @@ import { isDev } from "@/src/helpers/pathHelper";
 | 
				
			|||||||
import { catBreadHash } from "@/src/helpers/stringHelpers";
 | 
					import { catBreadHash } from "@/src/helpers/stringHelpers";
 | 
				
			||||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
 | 
					import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
 | 
				
			||||||
import { mixSeeds, SRng } from "@/src/services/rngService";
 | 
					import { mixSeeds, SRng } from "@/src/services/rngService";
 | 
				
			||||||
import { IMongoDate } from "@/src/types/commonTypes";
 | 
					 | 
				
			||||||
import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes";
 | 
					import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes";
 | 
				
			||||||
import { logger } from "@/src/utils/logger";
 | 
					import { logger } from "@/src/utils/logger";
 | 
				
			||||||
import { ExportVendors, IRange, IVendor, IVendorOffer } from "warframe-public-export-plus";
 | 
					import { ExportVendors, IRange, IVendor, IVendorOffer } from "warframe-public-export-plus";
 | 
				
			||||||
@ -25,7 +24,6 @@ import Nova1999ConquestShopManifest from "@/static/fixed_responses/getVendorInfo
 | 
				
			|||||||
import OstronPetVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json";
 | 
					import OstronPetVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json";
 | 
				
			||||||
import SolarisDebtTokenVendorRepossessionsManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json";
 | 
					import SolarisDebtTokenVendorRepossessionsManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json";
 | 
				
			||||||
import Temple1999VendorManifest from "@/static/fixed_responses/getVendorInfo/Temple1999VendorManifest.json";
 | 
					import Temple1999VendorManifest from "@/static/fixed_responses/getVendorInfo/Temple1999VendorManifest.json";
 | 
				
			||||||
import TeshinHardModeVendorManifest from "@/static/fixed_responses/getVendorInfo/TeshinHardModeVendorManifest.json";
 | 
					 | 
				
			||||||
import ZarimanCommisionsManifestArchimedean from "@/static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json";
 | 
					import ZarimanCommisionsManifestArchimedean from "@/static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const rawVendorManifests: IVendorManifest[] = [
 | 
					const rawVendorManifests: IVendorManifest[] = [
 | 
				
			||||||
@ -46,7 +44,6 @@ const rawVendorManifests: IVendorManifest[] = [
 | 
				
			|||||||
    OstronPetVendorManifest,
 | 
					    OstronPetVendorManifest,
 | 
				
			||||||
    SolarisDebtTokenVendorRepossessionsManifest,
 | 
					    SolarisDebtTokenVendorRepossessionsManifest,
 | 
				
			||||||
    Temple1999VendorManifest,
 | 
					    Temple1999VendorManifest,
 | 
				
			||||||
    TeshinHardModeVendorManifest, // uses preprocessing
 | 
					 | 
				
			||||||
    ZarimanCommisionsManifestArchimedean
 | 
					    ZarimanCommisionsManifestArchimedean
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -87,12 +84,16 @@ const gcd = (a: number, b: number): number => {
 | 
				
			|||||||
const getCycleDuration = (manifest: IVendor): number => {
 | 
					const getCycleDuration = (manifest: IVendor): number => {
 | 
				
			||||||
    let dur = 0;
 | 
					    let dur = 0;
 | 
				
			||||||
    for (const item of manifest.items) {
 | 
					    for (const item of manifest.items) {
 | 
				
			||||||
        if (typeof item.durationHours != "number") {
 | 
					        if (item.alwaysOffered) {
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const durationHours = item.rotatedWeekly ? 168 : item.durationHours;
 | 
				
			||||||
 | 
					        if (typeof durationHours != "number") {
 | 
				
			||||||
            dur = 1;
 | 
					            dur = 1;
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (dur != item.durationHours) {
 | 
					        if (dur != durationHours) {
 | 
				
			||||||
            dur = gcd(dur, item.durationHours);
 | 
					            dur = gcd(dur, durationHours);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return dur * unixTimesInMs.hour;
 | 
					    return dur * unixTimesInMs.hour;
 | 
				
			||||||
@ -101,7 +102,7 @@ const getCycleDuration = (manifest: IVendor): number => {
 | 
				
			|||||||
export const getVendorManifestByTypeName = (typeName: string): IVendorManifest | undefined => {
 | 
					export const getVendorManifestByTypeName = (typeName: string): IVendorManifest | undefined => {
 | 
				
			||||||
    for (const vendorManifest of rawVendorManifests) {
 | 
					    for (const vendorManifest of rawVendorManifests) {
 | 
				
			||||||
        if (vendorManifest.VendorInfo.TypeName == typeName) {
 | 
					        if (vendorManifest.VendorInfo.TypeName == typeName) {
 | 
				
			||||||
            return preprocessVendorManifest(vendorManifest);
 | 
					            return vendorManifest;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    for (const vendorInfo of generatableVendors) {
 | 
					    for (const vendorInfo of generatableVendors) {
 | 
				
			||||||
@ -124,7 +125,7 @@ export const getVendorManifestByTypeName = (typeName: string): IVendorManifest |
 | 
				
			|||||||
export const getVendorManifestByOid = (oid: string): IVendorManifest | undefined => {
 | 
					export const getVendorManifestByOid = (oid: string): IVendorManifest | undefined => {
 | 
				
			||||||
    for (const vendorManifest of rawVendorManifests) {
 | 
					    for (const vendorManifest of rawVendorManifests) {
 | 
				
			||||||
        if (vendorManifest.VendorInfo._id.$oid == oid) {
 | 
					        if (vendorManifest.VendorInfo._id.$oid == oid) {
 | 
				
			||||||
            return preprocessVendorManifest(vendorManifest);
 | 
					            return vendorManifest;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    for (const vendorInfo of generatableVendors) {
 | 
					    for (const vendorInfo of generatableVendors) {
 | 
				
			||||||
@ -183,30 +184,6 @@ export const applyStandingToVendorManifest = (
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const preprocessVendorManifest = (originalManifest: IVendorManifest): IVendorManifest => {
 | 
					 | 
				
			||||||
    if (Date.now() >= parseInt(originalManifest.VendorInfo.Expiry.$date.$numberLong)) {
 | 
					 | 
				
			||||||
        const manifest = structuredClone(originalManifest);
 | 
					 | 
				
			||||||
        const info = manifest.VendorInfo;
 | 
					 | 
				
			||||||
        refreshExpiry(info.Expiry);
 | 
					 | 
				
			||||||
        for (const offer of info.ItemManifest) {
 | 
					 | 
				
			||||||
            refreshExpiry(offer.Expiry);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return manifest;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return originalManifest;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const refreshExpiry = (expiry: IMongoDate): void => {
 | 
					 | 
				
			||||||
    const period = parseInt(expiry.$date.$numberLong);
 | 
					 | 
				
			||||||
    if (Date.now() >= period) {
 | 
					 | 
				
			||||||
        const epoch = 1734307200_000; // Monday (for weekly schedules)
 | 
					 | 
				
			||||||
        const iteration = Math.trunc((Date.now() - epoch) / period);
 | 
					 | 
				
			||||||
        const start = epoch + iteration * period;
 | 
					 | 
				
			||||||
        const end = start + period;
 | 
					 | 
				
			||||||
        expiry.$date.$numberLong = end.toString();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const toRange = (value: IRange | number): IRange => {
 | 
					const toRange = (value: IRange | number): IRange => {
 | 
				
			||||||
    if (typeof value == "number") {
 | 
					    if (typeof value == "number") {
 | 
				
			||||||
        return { minValue: value, maxValue: value };
 | 
					        return { minValue: value, maxValue: value };
 | 
				
			||||||
@ -230,6 +207,18 @@ const getCycleDurationRange = (manifest: IVendor): IRange | undefined => {
 | 
				
			|||||||
    return res.maxValue != 0 ? res : undefined;
 | 
					    return res.maxValue != 0 ? res : undefined;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type TOfferId = string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getOfferId = (offer: IVendorOffer | IItemManifest): TOfferId => {
 | 
				
			||||||
 | 
					    if ("storeItem" in offer) {
 | 
				
			||||||
 | 
					        // IVendorOffer
 | 
				
			||||||
 | 
					        return offer.storeItem + "x" + offer.quantity;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        // IItemManifest
 | 
				
			||||||
 | 
					        return offer.StoreItem + "x" + offer.QuantityMultiplier;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const vendorManifestCache: Record<string, IVendorManifest> = {};
 | 
					const vendorManifestCache: Record<string, IVendorManifest> = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifest => {
 | 
					const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifest => {
 | 
				
			||||||
@ -270,7 +259,8 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
 | 
				
			|||||||
        const rng = new SRng(mixSeeds(vendorSeed, cycleIndex));
 | 
					        const rng = new SRng(mixSeeds(vendorSeed, cycleIndex));
 | 
				
			||||||
        const offersToAdd: IVendorOffer[] = [];
 | 
					        const offersToAdd: IVendorOffer[] = [];
 | 
				
			||||||
        if (!manifest.isOneBinPerCycle) {
 | 
					        if (!manifest.isOneBinPerCycle) {
 | 
				
			||||||
            const remainingItemCapacity: Record<string, number> = {};
 | 
					            // Compute vendor requirements, subtracting existing offers
 | 
				
			||||||
 | 
					            const remainingItemCapacity: Record<TOfferId, number> = {};
 | 
				
			||||||
            const missingItemsPerBin: Record<number, number> = {};
 | 
					            const missingItemsPerBin: Record<number, number> = {};
 | 
				
			||||||
            let numOffersThatNeedToMatchABin = 0;
 | 
					            let numOffersThatNeedToMatchABin = 0;
 | 
				
			||||||
            if (manifest.numItemsPerBin) {
 | 
					            if (manifest.numItemsPerBin) {
 | 
				
			||||||
@ -280,56 +270,59 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            for (const item of manifest.items) {
 | 
					            for (const item of manifest.items) {
 | 
				
			||||||
                remainingItemCapacity[item.storeItem] = 1 + item.duplicates;
 | 
					                remainingItemCapacity[getOfferId(item)] = 1 + item.duplicates;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            for (const offer of info.ItemManifest) {
 | 
					            for (const offer of info.ItemManifest) {
 | 
				
			||||||
                remainingItemCapacity[offer.StoreItem] -= 1;
 | 
					                remainingItemCapacity[getOfferId(offer)] -= 1;
 | 
				
			||||||
                const bin = parseInt(offer.Bin.substring(4));
 | 
					                const bin = parseInt(offer.Bin.substring(4));
 | 
				
			||||||
                if (missingItemsPerBin[bin]) {
 | 
					                if (missingItemsPerBin[bin]) {
 | 
				
			||||||
                    missingItemsPerBin[bin] -= 1;
 | 
					                    missingItemsPerBin[bin] -= 1;
 | 
				
			||||||
                    numOffersThatNeedToMatchABin -= 1;
 | 
					                    numOffersThatNeedToMatchABin -= 1;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (manifest.numItems && manifest.items.length != manifest.numItems.minValue) {
 | 
					
 | 
				
			||||||
                const numItemsTarget = rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue);
 | 
					            // Add permanent offers
 | 
				
			||||||
 | 
					            let numUncountedOffers = 0;
 | 
				
			||||||
 | 
					            let offset = 0;
 | 
				
			||||||
 | 
					            for (const item of manifest.items) {
 | 
				
			||||||
 | 
					                if (item.alwaysOffered || item.rotatedWeekly) {
 | 
				
			||||||
 | 
					                    ++numUncountedOffers;
 | 
				
			||||||
 | 
					                    const id = getOfferId(item);
 | 
				
			||||||
 | 
					                    if (remainingItemCapacity[id] != 0) {
 | 
				
			||||||
 | 
					                        remainingItemCapacity[id] -= 1;
 | 
				
			||||||
 | 
					                        offersToAdd.push(item);
 | 
				
			||||||
 | 
					                        ++offset;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Add counted offers
 | 
				
			||||||
 | 
					            if (manifest.numItems) {
 | 
				
			||||||
 | 
					                const useRng = manifest.numItems.minValue != manifest.numItems.maxValue;
 | 
				
			||||||
 | 
					                const numItemsTarget =
 | 
				
			||||||
 | 
					                    numUncountedOffers +
 | 
				
			||||||
 | 
					                    (useRng
 | 
				
			||||||
 | 
					                        ? rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue)
 | 
				
			||||||
 | 
					                        : manifest.numItems.minValue);
 | 
				
			||||||
 | 
					                let i = 0;
 | 
				
			||||||
                while (info.ItemManifest.length + offersToAdd.length < numItemsTarget) {
 | 
					                while (info.ItemManifest.length + offersToAdd.length < numItemsTarget) {
 | 
				
			||||||
                    // TODO: Consider item probability weightings
 | 
					                    const item = useRng ? rng.randomElement(manifest.items)! : manifest.items[i++];
 | 
				
			||||||
                    const item = rng.randomElement(manifest.items)!;
 | 
					 | 
				
			||||||
                    if (
 | 
					                    if (
 | 
				
			||||||
                        remainingItemCapacity[item.storeItem] != 0 &&
 | 
					                        !item.alwaysOffered &&
 | 
				
			||||||
 | 
					                        remainingItemCapacity[getOfferId(item)] != 0 &&
 | 
				
			||||||
                        (numOffersThatNeedToMatchABin == 0 || missingItemsPerBin[item.bin])
 | 
					                        (numOffersThatNeedToMatchABin == 0 || missingItemsPerBin[item.bin])
 | 
				
			||||||
                    ) {
 | 
					                    ) {
 | 
				
			||||||
                        remainingItemCapacity[item.storeItem] -= 1;
 | 
					                        remainingItemCapacity[getOfferId(item)] -= 1;
 | 
				
			||||||
                        if (missingItemsPerBin[item.bin]) {
 | 
					                        if (missingItemsPerBin[item.bin]) {
 | 
				
			||||||
                            missingItemsPerBin[item.bin] -= 1;
 | 
					                            missingItemsPerBin[item.bin] -= 1;
 | 
				
			||||||
                            numOffersThatNeedToMatchABin -= 1;
 | 
					                            numOffersThatNeedToMatchABin -= 1;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        offersToAdd.push(item);
 | 
					                        offersToAdd.splice(offset, 0, item);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    if (i == manifest.items.length) {
 | 
				
			||||||
 | 
					                        i = 0;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                for (const item of manifest.items) {
 | 
					 | 
				
			||||||
                    if (!item.alwaysOffered && remainingItemCapacity[item.storeItem] != 0) {
 | 
					 | 
				
			||||||
                        remainingItemCapacity[item.storeItem] -= 1;
 | 
					 | 
				
			||||||
                        offersToAdd.push(item);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                for (const e of Object.entries(remainingItemCapacity)) {
 | 
					 | 
				
			||||||
                    const item = manifest.items.find(x => x.storeItem == e[0])!;
 | 
					 | 
				
			||||||
                    if (!item.alwaysOffered) {
 | 
					 | 
				
			||||||
                        while (e[1] != 0) {
 | 
					 | 
				
			||||||
                            e[1] -= 1;
 | 
					 | 
				
			||||||
                            offersToAdd.push(item);
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                for (const item of manifest.items) {
 | 
					 | 
				
			||||||
                    if (item.alwaysOffered && remainingItemCapacity[item.storeItem] != 0) {
 | 
					 | 
				
			||||||
                        remainingItemCapacity[item.storeItem] -= 1;
 | 
					 | 
				
			||||||
                        offersToAdd.push(item);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                offersToAdd.reverse();
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            const binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now.
 | 
					            const binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now.
 | 
				
			||||||
@ -342,16 +335,21 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
 | 
				
			|||||||
        const cycleStart = cycleOffset + cycleIndex * cycleDuration;
 | 
					        const cycleStart = cycleOffset + cycleIndex * cycleDuration;
 | 
				
			||||||
        for (const rawItem of offersToAdd) {
 | 
					        for (const rawItem of offersToAdd) {
 | 
				
			||||||
            const durationHoursRange = toRange(rawItem.durationHours ?? cycleDuration);
 | 
					            const durationHoursRange = toRange(rawItem.durationHours ?? cycleDuration);
 | 
				
			||||||
            const expiry =
 | 
					            const expiry = rawItem.alwaysOffered
 | 
				
			||||||
                cycleStart +
 | 
					                ? 2051240400_000
 | 
				
			||||||
                rng.randomInt(durationHoursRange.minValue, durationHoursRange.maxValue) * unixTimesInMs.hour;
 | 
					                : cycleStart +
 | 
				
			||||||
 | 
					                  (rawItem.rotatedWeekly
 | 
				
			||||||
 | 
					                      ? unixTimesInMs.week
 | 
				
			||||||
 | 
					                      : rng.randomInt(durationHoursRange.minValue, durationHoursRange.maxValue) * unixTimesInMs.hour);
 | 
				
			||||||
            const item: IItemManifest = {
 | 
					            const item: IItemManifest = {
 | 
				
			||||||
                StoreItem: rawItem.storeItem,
 | 
					                StoreItem: rawItem.storeItem,
 | 
				
			||||||
                ItemPrices: rawItem.itemPrices?.map(itemPrice => ({ ...itemPrice, ProductCategory: "MiscItems" })),
 | 
					                ItemPrices: rawItem.itemPrices?.map(itemPrice => ({ ...itemPrice, ProductCategory: "MiscItems" })),
 | 
				
			||||||
                Bin: "BIN_" + rawItem.bin,
 | 
					                Bin: "BIN_" + rawItem.bin,
 | 
				
			||||||
                QuantityMultiplier: rawItem.quantity,
 | 
					                QuantityMultiplier: rawItem.quantity,
 | 
				
			||||||
                Expiry: { $date: { $numberLong: expiry.toString() } },
 | 
					                Expiry: { $date: { $numberLong: expiry.toString() } },
 | 
				
			||||||
                AllowMultipurchase: false,
 | 
					                PurchaseQuantityLimit: rawItem.purchaseLimit,
 | 
				
			||||||
 | 
					                RotatedWeekly: rawItem.rotatedWeekly,
 | 
				
			||||||
 | 
					                AllowMultipurchase: rawItem.purchaseLimit !== 1,
 | 
				
			||||||
                Id: {
 | 
					                Id: {
 | 
				
			||||||
                    $oid:
 | 
					                    $oid:
 | 
				
			||||||
                        ((cycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") +
 | 
					                        ((cycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") +
 | 
				
			||||||
@ -422,6 +420,13 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (isDev) {
 | 
					if (isDev) {
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					        getCycleDuration(ExportVendors["/Lotus/Types/Game/VendorManifests/Hubs/TeshinHardModeVendorManifest"]) !=
 | 
				
			||||||
 | 
					        unixTimesInMs.week
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        logger.warn(`getCycleDuration self test failed`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const ads = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest")!
 | 
					    const ads = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest")!
 | 
				
			||||||
        .VendorInfo.ItemManifest;
 | 
					        .VendorInfo.ItemManifest;
 | 
				
			||||||
    if (
 | 
					    if (
 | 
				
			||||||
 | 
				
			|||||||
@ -16,8 +16,10 @@ import {
 | 
				
			|||||||
    ISortie,
 | 
					    ISortie,
 | 
				
			||||||
    ISortieMission,
 | 
					    ISortieMission,
 | 
				
			||||||
    ISyndicateMissionInfo,
 | 
					    ISyndicateMissionInfo,
 | 
				
			||||||
 | 
					    ITmp,
 | 
				
			||||||
    IVoidStorm,
 | 
					    IVoidStorm,
 | 
				
			||||||
    IWorldState
 | 
					    IWorldState,
 | 
				
			||||||
 | 
					    TCircuitGameMode
 | 
				
			||||||
} from "../types/worldStateTypes";
 | 
					} from "../types/worldStateTypes";
 | 
				
			||||||
import { version_compare } from "../helpers/inventoryHelpers";
 | 
					import { version_compare } from "../helpers/inventoryHelpers";
 | 
				
			||||||
import { logger } from "../utils/logger";
 | 
					import { logger } from "../utils/logger";
 | 
				
			||||||
@ -1297,12 +1299,15 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
 | 
				
			|||||||
        pushVoidStorms(worldState.VoidStorms, hour);
 | 
					        pushVoidStorms(worldState.VoidStorms, hour);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Sentient Anomaly cycling every 30 minutes
 | 
					    // Sentient Anomaly + Xtra Cheese cycles
 | 
				
			||||||
    const halfHour = Math.trunc(timeMs / (unixTimesInMs.hour / 2));
 | 
					    const halfHour = Math.trunc(timeMs / (unixTimesInMs.hour / 2));
 | 
				
			||||||
    const tmp = {
 | 
					    const hourInSeconds = 3600;
 | 
				
			||||||
 | 
					    const cheeseInterval = hourInSeconds * 8;
 | 
				
			||||||
 | 
					    const cheeseDuration = hourInSeconds * 2;
 | 
				
			||||||
 | 
					    const cheeseIndex = Math.trunc(timeSecs / cheeseInterval);
 | 
				
			||||||
 | 
					    const tmp: ITmp = {
 | 
				
			||||||
        cavabegin: "1690761600",
 | 
					        cavabegin: "1690761600",
 | 
				
			||||||
        PurchasePlatformLockEnabled: true,
 | 
					        PurchasePlatformLockEnabled: true,
 | 
				
			||||||
        tcsn: true,
 | 
					 | 
				
			||||||
        pgr: {
 | 
					        pgr: {
 | 
				
			||||||
            ts: "1732572900",
 | 
					            ts: "1732572900",
 | 
				
			||||||
            en: "CUSTOM DECALS @ ZEVILA",
 | 
					            en: "CUSTOM DECALS @ ZEVILA",
 | 
				
			||||||
@ -1323,8 +1328,16 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
 | 
				
			|||||||
        },
 | 
					        },
 | 
				
			||||||
        ennnd: true,
 | 
					        ennnd: true,
 | 
				
			||||||
        mbrt: true,
 | 
					        mbrt: true,
 | 
				
			||||||
 | 
					        fbst: {
 | 
				
			||||||
 | 
					            a: cheeseIndex * cheeseInterval, // This has a bug where the client shows a negative time for "Xtra cheese starts in ..." until it refreshes the world state. This is because we're only providing the new activation as soon as that time/date is reached. However, this is 100% faithful to live.
 | 
				
			||||||
 | 
					            e: cheeseIndex * cheeseInterval + cheeseDuration,
 | 
				
			||||||
 | 
					            n: (cheeseIndex + 1) * cheeseInterval
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        sfn: [550, 553, 554, 555][halfHour % 4]
 | 
					        sfn: [550, 553, 554, 555][halfHour % 4]
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					    if (Array.isArray(config.worldState?.circuitGameModes)) {
 | 
				
			||||||
 | 
					        tmp.edg = config.worldState.circuitGameModes as TCircuitGameMode[];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    worldState.Tmp = JSON.stringify(tmp);
 | 
					    worldState.Tmp = JSON.stringify(tmp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return worldState;
 | 
					    return worldState;
 | 
				
			||||||
 | 
				
			|||||||
@ -234,7 +234,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
 | 
				
			|||||||
    HandlerPoints: number;
 | 
					    HandlerPoints: number;
 | 
				
			||||||
    MiscItems: IMiscItem[];
 | 
					    MiscItems: IMiscItem[];
 | 
				
			||||||
    HasOwnedVoidProjectionsPreviously?: boolean;
 | 
					    HasOwnedVoidProjectionsPreviously?: boolean;
 | 
				
			||||||
    ChallengesFixVersion: number;
 | 
					    ChallengesFixVersion?: number;
 | 
				
			||||||
    ChallengeProgress: IChallengeProgress[];
 | 
					    ChallengeProgress: IChallengeProgress[];
 | 
				
			||||||
    RawUpgrades: IRawUpgrade[];
 | 
					    RawUpgrades: IRawUpgrade[];
 | 
				
			||||||
    ReceivedStartingGear: boolean;
 | 
					    ReceivedStartingGear: boolean;
 | 
				
			||||||
 | 
				
			|||||||
@ -191,3 +191,48 @@ export interface ICalendarEvent {
 | 
				
			|||||||
    dialogueName?: string;
 | 
					    dialogueName?: string;
 | 
				
			||||||
    dialogueConvo?: string;
 | 
					    dialogueConvo?: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type TCircuitGameMode =
 | 
				
			||||||
 | 
					    | "Survival"
 | 
				
			||||||
 | 
					    | "VoidFlood"
 | 
				
			||||||
 | 
					    | "Excavation"
 | 
				
			||||||
 | 
					    | "Defense"
 | 
				
			||||||
 | 
					    | "Exterminate"
 | 
				
			||||||
 | 
					    | "Assassination"
 | 
				
			||||||
 | 
					    | "Alchemy";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ITmp {
 | 
				
			||||||
 | 
					    cavabegin: string;
 | 
				
			||||||
 | 
					    PurchasePlatformLockEnabled: boolean; // Seems unused
 | 
				
			||||||
 | 
					    pgr: IPgr;
 | 
				
			||||||
 | 
					    ennnd?: boolean; // True if 1999 demo is available (no effect for >=38.6.0)
 | 
				
			||||||
 | 
					    mbrt?: boolean; // Related to mobile app rating request
 | 
				
			||||||
 | 
					    fbst: IFbst;
 | 
				
			||||||
 | 
					    sfn: number;
 | 
				
			||||||
 | 
					    edg?: TCircuitGameMode[]; // The Circuit game modes overwrite
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IPgr {
 | 
				
			||||||
 | 
					    ts: string;
 | 
				
			||||||
 | 
					    en: string;
 | 
				
			||||||
 | 
					    fr: string;
 | 
				
			||||||
 | 
					    it: string;
 | 
				
			||||||
 | 
					    de: string;
 | 
				
			||||||
 | 
					    es: string;
 | 
				
			||||||
 | 
					    pt: string;
 | 
				
			||||||
 | 
					    ru: string;
 | 
				
			||||||
 | 
					    pl: string;
 | 
				
			||||||
 | 
					    uk: string;
 | 
				
			||||||
 | 
					    tr: string;
 | 
				
			||||||
 | 
					    ja: string;
 | 
				
			||||||
 | 
					    zh: string;
 | 
				
			||||||
 | 
					    ko: string;
 | 
				
			||||||
 | 
					    tc: string;
 | 
				
			||||||
 | 
					    th: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IFbst {
 | 
				
			||||||
 | 
					    a: number;
 | 
				
			||||||
 | 
					    e: number;
 | 
				
			||||||
 | 
					    n: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,603 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
  "VendorInfo": {
 | 
					 | 
				
			||||||
    "_id": {
 | 
					 | 
				
			||||||
      "$oid": "63ed01efbdaa38891767bac9"
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "TypeName": "/Lotus/Types/Game/VendorManifests/Hubs/TeshinHardModeVendorManifest",
 | 
					 | 
				
			||||||
    "ItemManifest": [
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/OperatorArmour/HardMode/OperatorTeshinArmsBlueprint",
 | 
					 | 
				
			||||||
        "ItemPrices": [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "ItemCount": 15,
 | 
					 | 
				
			||||||
            "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence",
 | 
					 | 
				
			||||||
            "ProductCategory": "MiscItems"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "Bin": "BIN_0",
 | 
					 | 
				
			||||||
        "QuantityMultiplier": 1,
 | 
					 | 
				
			||||||
        "Expiry": {
 | 
					 | 
				
			||||||
          "$date": {
 | 
					 | 
				
			||||||
            "$numberLong": "2051240400000"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "AllowMultipurchase": true,
 | 
					 | 
				
			||||||
        "Id": {
 | 
					 | 
				
			||||||
          "$oid": "66fd60b20ba592c4c95e9947"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/OperatorArmour/HardMode/OperatorTeshinBodyBlueprint",
 | 
					 | 
				
			||||||
        "ItemPrices": [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "ItemCount": 25,
 | 
					 | 
				
			||||||
            "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence",
 | 
					 | 
				
			||||||
            "ProductCategory": "MiscItems"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "Bin": "BIN_0",
 | 
					 | 
				
			||||||
        "QuantityMultiplier": 1,
 | 
					 | 
				
			||||||
        "Expiry": {
 | 
					 | 
				
			||||||
          "$date": {
 | 
					 | 
				
			||||||
            "$numberLong": "2051240400000"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "AllowMultipurchase": true,
 | 
					 | 
				
			||||||
        "Id": {
 | 
					 | 
				
			||||||
          "$oid": "66fd60b20ba592c4c95e9948"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/OperatorArmour/HardMode/OperatorTeshinHeadBlueprint",
 | 
					 | 
				
			||||||
        "ItemPrices": [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "ItemCount": 20,
 | 
					 | 
				
			||||||
            "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence",
 | 
					 | 
				
			||||||
            "ProductCategory": "MiscItems"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "Bin": "BIN_0",
 | 
					 | 
				
			||||||
        "QuantityMultiplier": 1,
 | 
					 | 
				
			||||||
        "Expiry": {
 | 
					 | 
				
			||||||
          "$date": {
 | 
					 | 
				
			||||||
            "$numberLong": "2051240400000"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "AllowMultipurchase": true,
 | 
					 | 
				
			||||||
        "Id": {
 | 
					 | 
				
			||||||
          "$oid": "66fd60b20ba592c4c95e9949"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/OperatorArmour/HardMode/OperatorTeshinLegsBlueprint",
 | 
					 | 
				
			||||||
        "ItemPrices": [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "ItemCount": 25,
 | 
					 | 
				
			||||||
            "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence",
 | 
					 | 
				
			||||||
            "ProductCategory": "MiscItems"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "Bin": "BIN_0",
 | 
					 | 
				
			||||||
        "QuantityMultiplier": 1,
 | 
					 | 
				
			||||||
        "Expiry": {
 | 
					 | 
				
			||||||
          "$date": {
 | 
					 | 
				
			||||||
            "$numberLong": "2051240400000"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "AllowMultipurchase": true,
 | 
					 | 
				
			||||||
        "Id": {
 | 
					 | 
				
			||||||
          "$oid": "66fd60b20ba592c4c95e994a"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/WeaponPrimaryArcaneUnlocker",
 | 
					 | 
				
			||||||
        "ItemPrices": [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "ItemCount": 15,
 | 
					 | 
				
			||||||
            "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence",
 | 
					 | 
				
			||||||
            "ProductCategory": "MiscItems"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "Bin": "BIN_0",
 | 
					 | 
				
			||||||
        "QuantityMultiplier": 1,
 | 
					 | 
				
			||||||
        "Expiry": {
 | 
					 | 
				
			||||||
          "$date": {
 | 
					 | 
				
			||||||
            "$numberLong": "2051240400000"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "AllowMultipurchase": true,
 | 
					 | 
				
			||||||
        "Id": {
 | 
					 | 
				
			||||||
          "$oid": "66fd60b20ba592c4c95e994b"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/WeaponSecondaryArcaneUnlocker",
 | 
					 | 
				
			||||||
        "ItemPrices": [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "ItemCount": 15,
 | 
					 | 
				
			||||||
            "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence",
 | 
					 | 
				
			||||||
            "ProductCategory": "MiscItems"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "Bin": "BIN_0",
 | 
					 | 
				
			||||||
        "QuantityMultiplier": 1,
 | 
					 | 
				
			||||||
        "Expiry": {
 | 
					 | 
				
			||||||
          "$date": {
 | 
					 | 
				
			||||||
            "$numberLong": "2051240400000"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "AllowMultipurchase": true,
 | 
					 | 
				
			||||||
        "Id": {
 | 
					 | 
				
			||||||
          "$oid": "66fd60b20ba592c4c95e994c"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Components/FormaStanceBlueprint",
 | 
					 | 
				
			||||||
        "ItemPrices": [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "ItemCount": 10,
 | 
					 | 
				
			||||||
            "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence",
 | 
					 | 
				
			||||||
            "ProductCategory": "MiscItems"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "Bin": "BIN_0",
 | 
					 | 
				
			||||||
        "QuantityMultiplier": 1,
 | 
					 | 
				
			||||||
        "Expiry": {
 | 
					 | 
				
			||||||
          "$date": {
 | 
					 | 
				
			||||||
            "$numberLong": "2051240400000"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "AllowMultipurchase": true,
 | 
					 | 
				
			||||||
        "Id": {
 | 
					 | 
				
			||||||
          "$oid": "66fd60b20ba592c4c95e994d"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Effects/OrbsEphemera",
 | 
					 | 
				
			||||||
        "ItemPrices": [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "ItemCount": 3,
 | 
					 | 
				
			||||||
            "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence",
 | 
					 | 
				
			||||||
            "ProductCategory": "MiscItems"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "Bin": "BIN_0",
 | 
					 | 
				
			||||||
        "QuantityMultiplier": 1,
 | 
					 | 
				
			||||||
        "Expiry": {
 | 
					 | 
				
			||||||
          "$date": {
 | 
					 | 
				
			||||||
            "$numberLong": "2051240400000"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "AllowMultipurchase": true,
 | 
					 | 
				
			||||||
        "Id": {
 | 
					 | 
				
			||||||
          "$oid": "66fd60b20ba592c4c95e994e"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Effects/TatsuSkullEphemera",
 | 
					 | 
				
			||||||
        "ItemPrices": [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "ItemCount": 85,
 | 
					 | 
				
			||||||
            "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence",
 | 
					 | 
				
			||||||
            "ProductCategory": "MiscItems"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "Bin": "BIN_0",
 | 
					 | 
				
			||||||
        "QuantityMultiplier": 1,
 | 
					 | 
				
			||||||
        "Expiry": {
 | 
					 | 
				
			||||||
          "$date": {
 | 
					 | 
				
			||||||
            "$numberLong": "2051240400000"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "AllowMultipurchase": true,
 | 
					 | 
				
			||||||
        "Id": {
 | 
					 | 
				
			||||||
          "$oid": "66fd60b20ba592c4c95e994f"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawShotgunRandomMod",
 | 
					 | 
				
			||||||
        "ItemPrices": [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "ItemCount": 75,
 | 
					 | 
				
			||||||
            "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence",
 | 
					 | 
				
			||||||
            "ProductCategory": "MiscItems"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "Bin": "BIN_0",
 | 
					 | 
				
			||||||
        "QuantityMultiplier": 1,
 | 
					 | 
				
			||||||
        "Expiry": {
 | 
					 | 
				
			||||||
          "$date": {
 | 
					 | 
				
			||||||
            "$numberLong": "2051240400000"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "PurchaseQuantityLimit": 1,
 | 
					 | 
				
			||||||
        "RotatedWeekly": true,
 | 
					 | 
				
			||||||
        "AllowMultipurchase": false,
 | 
					 | 
				
			||||||
        "Id": {
 | 
					 | 
				
			||||||
          "$oid": "66fd60b20ba592c4c95e9950"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "StoreItem": "/Lotus/StoreItems/Types/Recipes/Components/UmbraFormaBlueprint",
 | 
					 | 
				
			||||||
        "ItemPrices": [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "ItemCount": 150,
 | 
					 | 
				
			||||||
            "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence",
 | 
					 | 
				
			||||||
            "ProductCategory": "MiscItems"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "Bin": "BIN_0",
 | 
					 | 
				
			||||||
        "QuantityMultiplier": 1,
 | 
					 | 
				
			||||||
        "Expiry": {
 | 
					 | 
				
			||||||
          "$date": {
 | 
					 | 
				
			||||||
            "$numberLong": "2051240400000"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "PurchaseQuantityLimit": 1,
 | 
					 | 
				
			||||||
        "RotatedWeekly": true,
 | 
					 | 
				
			||||||
        "AllowMultipurchase": false,
 | 
					 | 
				
			||||||
        "Id": {
 | 
					 | 
				
			||||||
          "$oid": "66fd60b20ba592c4c95e9951"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Kuva",
 | 
					 | 
				
			||||||
        "ItemPrices": [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "ItemCount": 55,
 | 
					 | 
				
			||||||
            "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence",
 | 
					 | 
				
			||||||
            "ProductCategory": "MiscItems"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "Bin": "BIN_0",
 | 
					 | 
				
			||||||
        "QuantityMultiplier": 50000,
 | 
					 | 
				
			||||||
        "Expiry": {
 | 
					 | 
				
			||||||
          "$date": {
 | 
					 | 
				
			||||||
            "$numberLong": "2051240400000"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "PurchaseQuantityLimit": 1,
 | 
					 | 
				
			||||||
        "RotatedWeekly": true,
 | 
					 | 
				
			||||||
        "AllowMultipurchase": false,
 | 
					 | 
				
			||||||
        "Id": {
 | 
					 | 
				
			||||||
          "$oid": "66fd60b20ba592c4c95e9952"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawModularPistolRandomMod",
 | 
					 | 
				
			||||||
        "ItemPrices": [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "ItemCount": 75,
 | 
					 | 
				
			||||||
            "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence",
 | 
					 | 
				
			||||||
            "ProductCategory": "MiscItems"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "Bin": "BIN_0",
 | 
					 | 
				
			||||||
        "QuantityMultiplier": 1,
 | 
					 | 
				
			||||||
        "Expiry": {
 | 
					 | 
				
			||||||
          "$date": {
 | 
					 | 
				
			||||||
            "$numberLong": "2051240400000"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "PurchaseQuantityLimit": 1,
 | 
					 | 
				
			||||||
        "RotatedWeekly": true,
 | 
					 | 
				
			||||||
        "AllowMultipurchase": false,
 | 
					 | 
				
			||||||
        "Id": {
 | 
					 | 
				
			||||||
          "$oid": "66fd60b20ba592c4c95e9953"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Forma",
 | 
					 | 
				
			||||||
        "ItemPrices": [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "ItemCount": 75,
 | 
					 | 
				
			||||||
            "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence",
 | 
					 | 
				
			||||||
            "ProductCategory": "MiscItems"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "Bin": "BIN_0",
 | 
					 | 
				
			||||||
        "QuantityMultiplier": 3,
 | 
					 | 
				
			||||||
        "Expiry": {
 | 
					 | 
				
			||||||
          "$date": {
 | 
					 | 
				
			||||||
            "$numberLong": "2051240400000"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "PurchaseQuantityLimit": 1,
 | 
					 | 
				
			||||||
        "RotatedWeekly": true,
 | 
					 | 
				
			||||||
        "AllowMultipurchase": false,
 | 
					 | 
				
			||||||
        "Id": {
 | 
					 | 
				
			||||||
          "$oid": "66fd60b20ba592c4c95e9954"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawModularMeleeRandomMod",
 | 
					 | 
				
			||||||
        "ItemPrices": [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "ItemCount": 75,
 | 
					 | 
				
			||||||
            "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence",
 | 
					 | 
				
			||||||
            "ProductCategory": "MiscItems"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "Bin": "BIN_0",
 | 
					 | 
				
			||||||
        "QuantityMultiplier": 1,
 | 
					 | 
				
			||||||
        "Expiry": {
 | 
					 | 
				
			||||||
          "$date": {
 | 
					 | 
				
			||||||
            "$numberLong": "2051240400000"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "PurchaseQuantityLimit": 1,
 | 
					 | 
				
			||||||
        "RotatedWeekly": true,
 | 
					 | 
				
			||||||
        "AllowMultipurchase": false,
 | 
					 | 
				
			||||||
        "Id": {
 | 
					 | 
				
			||||||
          "$oid": "66fd60b20ba592c4c95e9955"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/FusionBundles/EvergreenLoginRewardFusionBundle",
 | 
					 | 
				
			||||||
        "ItemPrices": [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "ItemCount": 150,
 | 
					 | 
				
			||||||
            "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence",
 | 
					 | 
				
			||||||
            "ProductCategory": "MiscItems"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "Bin": "BIN_0",
 | 
					 | 
				
			||||||
        "QuantityMultiplier": 1,
 | 
					 | 
				
			||||||
        "Expiry": {
 | 
					 | 
				
			||||||
          "$date": {
 | 
					 | 
				
			||||||
            "$numberLong": "2051240400000"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "PurchaseQuantityLimit": 1,
 | 
					 | 
				
			||||||
        "RotatedWeekly": true,
 | 
					 | 
				
			||||||
        "AllowMultipurchase": false,
 | 
					 | 
				
			||||||
        "Id": {
 | 
					 | 
				
			||||||
          "$oid": "66fd60b20ba592c4c95e9956"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawRifleRandomMod",
 | 
					 | 
				
			||||||
        "ItemPrices": [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "ItemCount": 75,
 | 
					 | 
				
			||||||
            "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence",
 | 
					 | 
				
			||||||
            "ProductCategory": "MiscItems"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "Bin": "BIN_0",
 | 
					 | 
				
			||||||
        "QuantityMultiplier": 1,
 | 
					 | 
				
			||||||
        "Expiry": {
 | 
					 | 
				
			||||||
          "$date": {
 | 
					 | 
				
			||||||
            "$numberLong": "2051240400000"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "PurchaseQuantityLimit": 1,
 | 
					 | 
				
			||||||
        "RotatedWeekly": true,
 | 
					 | 
				
			||||||
        "AllowMultipurchase": false,
 | 
					 | 
				
			||||||
        "Id": {
 | 
					 | 
				
			||||||
          "$oid": "66fd60b20ba592c4c95e9957"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/WeaponRecoilReductionMod",
 | 
					 | 
				
			||||||
        "ItemPrices": [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "ItemCount": 35,
 | 
					 | 
				
			||||||
            "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence",
 | 
					 | 
				
			||||||
            "ProductCategory": "MiscItems"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "Bin": "BIN_0",
 | 
					 | 
				
			||||||
        "QuantityMultiplier": 1,
 | 
					 | 
				
			||||||
        "Expiry": {
 | 
					 | 
				
			||||||
          "$date": {
 | 
					 | 
				
			||||||
            "$numberLong": "2051240400000"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "AllowMultipurchase": true,
 | 
					 | 
				
			||||||
        "Id": {
 | 
					 | 
				
			||||||
          "$oid": "66fd60b20ba592c4c95e9958"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/TeshinBobbleHead",
 | 
					 | 
				
			||||||
        "ItemPrices": [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "ItemCount": 35,
 | 
					 | 
				
			||||||
            "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence",
 | 
					 | 
				
			||||||
            "ProductCategory": "MiscItems"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "Bin": "BIN_0",
 | 
					 | 
				
			||||||
        "QuantityMultiplier": 1,
 | 
					 | 
				
			||||||
        "Expiry": {
 | 
					 | 
				
			||||||
          "$date": {
 | 
					 | 
				
			||||||
            "$numberLong": "2051240400000"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "AllowMultipurchase": true,
 | 
					 | 
				
			||||||
        "Id": {
 | 
					 | 
				
			||||||
          "$oid": "66fd60b20ba592c4c95e9959"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "StoreItem": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/ImageGaussVED",
 | 
					 | 
				
			||||||
        "ItemPrices": [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "ItemCount": 15,
 | 
					 | 
				
			||||||
            "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence",
 | 
					 | 
				
			||||||
            "ProductCategory": "MiscItems"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "Bin": "BIN_0",
 | 
					 | 
				
			||||||
        "QuantityMultiplier": 1,
 | 
					 | 
				
			||||||
        "Expiry": {
 | 
					 | 
				
			||||||
          "$date": {
 | 
					 | 
				
			||||||
            "$numberLong": "2051240400000"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "AllowMultipurchase": true,
 | 
					 | 
				
			||||||
        "Id": {
 | 
					 | 
				
			||||||
          "$oid": "66fd60b20ba592c4c95e995a"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "StoreItem": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/ImageGrendelVED",
 | 
					 | 
				
			||||||
        "ItemPrices": [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "ItemCount": 15,
 | 
					 | 
				
			||||||
            "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence",
 | 
					 | 
				
			||||||
            "ProductCategory": "MiscItems"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "Bin": "BIN_0",
 | 
					 | 
				
			||||||
        "QuantityMultiplier": 1,
 | 
					 | 
				
			||||||
        "Expiry": {
 | 
					 | 
				
			||||||
          "$date": {
 | 
					 | 
				
			||||||
            "$numberLong": "2051240400000"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "AllowMultipurchase": true,
 | 
					 | 
				
			||||||
        "Id": {
 | 
					 | 
				
			||||||
          "$oid": "66fd60b20ba592c4c95e995b"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "StoreItem": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageProteaAction",
 | 
					 | 
				
			||||||
        "ItemPrices": [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "ItemCount": 15,
 | 
					 | 
				
			||||||
            "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence",
 | 
					 | 
				
			||||||
            "ProductCategory": "MiscItems"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "Bin": "BIN_0",
 | 
					 | 
				
			||||||
        "QuantityMultiplier": 1,
 | 
					 | 
				
			||||||
        "Expiry": {
 | 
					 | 
				
			||||||
          "$date": {
 | 
					 | 
				
			||||||
            "$numberLong": "2051240400000"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "AllowMultipurchase": true,
 | 
					 | 
				
			||||||
        "Id": {
 | 
					 | 
				
			||||||
          "$oid": "66fd60b20ba592c4c95e995c"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/TeaSet",
 | 
					 | 
				
			||||||
        "ItemPrices": [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "ItemCount": 15,
 | 
					 | 
				
			||||||
            "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence",
 | 
					 | 
				
			||||||
            "ProductCategory": "MiscItems"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "Bin": "BIN_0",
 | 
					 | 
				
			||||||
        "QuantityMultiplier": 1,
 | 
					 | 
				
			||||||
        "Expiry": {
 | 
					 | 
				
			||||||
          "$date": {
 | 
					 | 
				
			||||||
            "$numberLong": "2051240400000"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "AllowMultipurchase": true,
 | 
					 | 
				
			||||||
        "Id": {
 | 
					 | 
				
			||||||
          "$oid": "66fd60b20ba592c4c95e995d"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "StoreItem": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageXakuAction",
 | 
					 | 
				
			||||||
        "ItemPrices": [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "ItemCount": 15,
 | 
					 | 
				
			||||||
            "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence",
 | 
					 | 
				
			||||||
            "ProductCategory": "MiscItems"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "Bin": "BIN_0",
 | 
					 | 
				
			||||||
        "QuantityMultiplier": 1,
 | 
					 | 
				
			||||||
        "Expiry": {
 | 
					 | 
				
			||||||
          "$date": {
 | 
					 | 
				
			||||||
            "$numberLong": "2051240400000"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "AllowMultipurchase": true,
 | 
					 | 
				
			||||||
        "Id": {
 | 
					 | 
				
			||||||
          "$oid": "66fd60b20ba592c4c95e995e"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/RivenIdentifier",
 | 
					 | 
				
			||||||
        "ItemPrices": [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "ItemCount": 20,
 | 
					 | 
				
			||||||
            "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence",
 | 
					 | 
				
			||||||
            "ProductCategory": "MiscItems"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "Bin": "BIN_0",
 | 
					 | 
				
			||||||
        "QuantityMultiplier": 1,
 | 
					 | 
				
			||||||
        "Expiry": {
 | 
					 | 
				
			||||||
          "$date": {
 | 
					 | 
				
			||||||
            "$numberLong": "2051240400000"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "PurchaseQuantityLimit": 1,
 | 
					 | 
				
			||||||
        "RotatedWeekly": true,
 | 
					 | 
				
			||||||
        "AllowMultipurchase": false,
 | 
					 | 
				
			||||||
        "Id": {
 | 
					 | 
				
			||||||
          "$oid": "66fd60b20ba592c4c95e995f"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "StoreItem": "/Lotus/StoreItems/Types/BoosterPacks/RandomSyndicateProjectionPack",
 | 
					 | 
				
			||||||
        "ItemPrices": [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "ItemCount": 15,
 | 
					 | 
				
			||||||
            "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence",
 | 
					 | 
				
			||||||
            "ProductCategory": "MiscItems"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "Bin": "BIN_0",
 | 
					 | 
				
			||||||
        "QuantityMultiplier": 1,
 | 
					 | 
				
			||||||
        "Expiry": {
 | 
					 | 
				
			||||||
          "$date": {
 | 
					 | 
				
			||||||
            "$numberLong": "604800000"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "PurchaseQuantityLimit": 25,
 | 
					 | 
				
			||||||
        "AllowMultipurchase": true,
 | 
					 | 
				
			||||||
        "Id": {
 | 
					 | 
				
			||||||
          "$oid": "66fd60b20ba592c4c95e997c"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Kuva",
 | 
					 | 
				
			||||||
        "ItemPrices": [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            "ItemCount": 15,
 | 
					 | 
				
			||||||
            "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence",
 | 
					 | 
				
			||||||
            "ProductCategory": "MiscItems"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        "Bin": "BIN_0",
 | 
					 | 
				
			||||||
        "QuantityMultiplier": 10000,
 | 
					 | 
				
			||||||
        "Expiry": {
 | 
					 | 
				
			||||||
          "$date": {
 | 
					 | 
				
			||||||
            "$numberLong": "604800000"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "PurchaseQuantityLimit": 25,
 | 
					 | 
				
			||||||
        "AllowMultipurchase": true,
 | 
					 | 
				
			||||||
        "Id": {
 | 
					 | 
				
			||||||
          "$oid": "66fd60b20ba592c4c95e997d"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    "PropertyTextHash": "0A0F20AFA748FBEE490510DBF5A33A0D",
 | 
					 | 
				
			||||||
    "Expiry": {
 | 
					 | 
				
			||||||
      "$date": {
 | 
					 | 
				
			||||||
        "$numberLong": "604800000"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -3,8 +3,8 @@ dict = {
 | 
				
			|||||||
    general_inventoryUpdateNote: `Nota: Los cambios realizados aquí se reflejarán en el juego cuando este sincronice el inventario. Usar la navegación debería ser la forma más sencilla de activar esto.`,
 | 
					    general_inventoryUpdateNote: `Nota: Los cambios realizados aquí se reflejarán en el juego cuando este sincronice el inventario. Usar la navegación debería ser la forma más sencilla de activar esto.`,
 | 
				
			||||||
    general_addButton: `Agregar`,
 | 
					    general_addButton: `Agregar`,
 | 
				
			||||||
    general_bulkActions: `Acciones masivas`,
 | 
					    general_bulkActions: `Acciones masivas`,
 | 
				
			||||||
    code_loginFail: `[UNTRANSLATED] Login failed. Double-check the email and password.`,
 | 
					    code_loginFail: `Error al iniciar sesión. Verifica el correo electrónico y la contraseña.`,
 | 
				
			||||||
    code_regFail: `[UNTRANSLATED] Registration failed. Account already exists?`,
 | 
					    code_regFail: `Error al registrar la cuenta. ¿Ya existe una cuenta con este correo?`,
 | 
				
			||||||
    code_nonValidAuthz: `Tus credenciales no son válidas.`,
 | 
					    code_nonValidAuthz: `Tus credenciales no son válidas.`,
 | 
				
			||||||
    code_changeNameConfirm: `¿Qué nombre te gustaría ponerle a tu cuenta?`,
 | 
					    code_changeNameConfirm: `¿Qué nombre te gustaría ponerle a tu cuenta?`,
 | 
				
			||||||
    code_deleteAccountConfirm: `¿Estás seguro de que deseas eliminar tu cuenta |DISPLAYNAME| (|EMAIL|)? Esta acción es permanente.`,
 | 
					    code_deleteAccountConfirm: `¿Estás seguro de que deseas eliminar tu cuenta |DISPLAYNAME| (|EMAIL|)? Esta acción es permanente.`,
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user