forked from OpenWF/SpaceNinjaServer
		
	Compare commits
	
		
			13 Commits
		
	
	
		
			045d933458
			...
			a27f1c5e01
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| a27f1c5e01 | |||
| 93afc2645c | |||
| b5b088249c | |||
| e6ec144f1f | |||
| 3d82fee99e | |||
| 39f0f7de9a | |||
| f672f05db9 | |||
| 4d9e6a35ab | |||
| c29bf6aab5 | |||
| bc07978846 | |||
| 2efe0df2f2 | |||
| 38b255d41a | |||
| 421164986a | 
@ -23,7 +23,7 @@
 | 
			
		||||
        "@typescript-eslint/no-explicit-any": "warn",
 | 
			
		||||
        "@typescript-eslint/no-loss-of-precision": "warn",
 | 
			
		||||
        "@typescript-eslint/no-unnecessary-condition": "warn",
 | 
			
		||||
        "no-case-declarations": "warn",
 | 
			
		||||
        "no-case-declarations": "error",
 | 
			
		||||
        "prettier/prettier": "error",
 | 
			
		||||
        "@typescript-eslint/semi": "error",
 | 
			
		||||
        "no-mixed-spaces-and-tabs": "error",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -12,7 +12,7 @@
 | 
			
		||||
        "copyfiles": "^2.4.1",
 | 
			
		||||
        "express": "^5",
 | 
			
		||||
        "mongoose": "^8.9.4",
 | 
			
		||||
        "warframe-public-export-plus": "^0.5.35",
 | 
			
		||||
        "warframe-public-export-plus": "^0.5.36",
 | 
			
		||||
        "warframe-riven-info": "^0.1.2",
 | 
			
		||||
        "winston": "^3.17.0",
 | 
			
		||||
        "winston-daily-rotate-file": "^5.0.0"
 | 
			
		||||
@ -4093,9 +4093,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/warframe-public-export-plus": {
 | 
			
		||||
      "version": "0.5.35",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.35.tgz",
 | 
			
		||||
      "integrity": "sha512-YLQP1n5sOV+PS5hfC4Kuoapa9gsqOy5Qy/E4EYfRV/xJBruFl3tPhbdbgFn3HhL2OBrgRJ8yzT5bjIvaHKhOCw=="
 | 
			
		||||
      "version": "0.5.36",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.36.tgz",
 | 
			
		||||
      "integrity": "sha512-FYZECqBSnynl6lQvcQyEqpnGW9l84wzusekhtwKjvg3280CYdn7g5x0Q9tOMhj1jpc/1tuY+akHtHa94sPtqKw=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/warframe-riven-info": {
 | 
			
		||||
      "version": "0.1.2",
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,7 @@
 | 
			
		||||
    "copyfiles": "^2.4.1",
 | 
			
		||||
    "express": "^5",
 | 
			
		||||
    "mongoose": "^8.9.4",
 | 
			
		||||
    "warframe-public-export-plus": "^0.5.35",
 | 
			
		||||
    "warframe-public-export-plus": "^0.5.36",
 | 
			
		||||
    "warframe-riven-info": "^0.1.2",
 | 
			
		||||
    "winston": "^3.17.0",
 | 
			
		||||
    "winston-daily-rotate-file": "^5.0.0"
 | 
			
		||||
 | 
			
		||||
@ -1,77 +1,31 @@
 | 
			
		||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
			
		||||
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
 | 
			
		||||
import { crackRelic } from "@/src/helpers/relicHelper";
 | 
			
		||||
import { getInventory } from "@/src/services/inventoryService";
 | 
			
		||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
			
		||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
 | 
			
		||||
import { getRandomWeightedReward2 } from "@/src/services/rngService";
 | 
			
		||||
import { ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
			
		||||
import { logger } from "@/src/utils/logger";
 | 
			
		||||
import { IVoidTearParticipantInfo } from "@/src/types/requestTypes";
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
import { ExportRelics, ExportRewards, TRarity } from "warframe-public-export-plus";
 | 
			
		||||
 | 
			
		||||
export const getVoidProjectionRewardsController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const data = getJSONfromString<IVoidProjectionRewardRequest>(String(req.body));
 | 
			
		||||
 | 
			
		||||
    if (data.ParticipantInfo.QualifiesForReward && !data.ParticipantInfo.HaveRewardResponse) {
 | 
			
		||||
        const inventory = await getInventory(accountId);
 | 
			
		||||
        await crackRelic(inventory, data.ParticipantInfo);
 | 
			
		||||
        await inventory.save();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const response: IVoidProjectionRewardResponse = {
 | 
			
		||||
        CurrentWave: data.CurrentWave,
 | 
			
		||||
        ParticipantInfo: data.ParticipantInfo,
 | 
			
		||||
        DifficultyTier: data.DifficultyTier
 | 
			
		||||
    };
 | 
			
		||||
    if (data.ParticipantInfo.QualifiesForReward) {
 | 
			
		||||
        const relic = ExportRelics[data.ParticipantInfo.VoidProjection];
 | 
			
		||||
        const weights = refinementToWeights[relic.quality];
 | 
			
		||||
        logger.debug(`opening a relic of quality ${relic.quality}; rarity weights are`, weights);
 | 
			
		||||
        const reward = getRandomWeightedReward2(
 | 
			
		||||
            ExportRewards[relic.rewardManifest][0] as { type: string; itemCount: number; rarity: TRarity }[], // rarity is nullable in PE+ typings, but always present for relics
 | 
			
		||||
            weights
 | 
			
		||||
        )!;
 | 
			
		||||
        logger.debug(`relic rolled`, reward);
 | 
			
		||||
        response.ParticipantInfo.Reward = reward.type;
 | 
			
		||||
 | 
			
		||||
        const inventory = await getInventory(accountId);
 | 
			
		||||
        // Remove relic
 | 
			
		||||
        addMiscItems(inventory, [
 | 
			
		||||
            {
 | 
			
		||||
                ItemType: data.ParticipantInfo.VoidProjection,
 | 
			
		||||
                ItemCount: -1
 | 
			
		||||
            }
 | 
			
		||||
        ]);
 | 
			
		||||
        // Give reward
 | 
			
		||||
        await handleStoreItemAcquisition(reward.type, inventory, reward.itemCount);
 | 
			
		||||
        await inventory.save();
 | 
			
		||||
    }
 | 
			
		||||
    res.json(response);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const refinementToWeights = {
 | 
			
		||||
    VPQ_BRONZE: {
 | 
			
		||||
        COMMON: 0.76,
 | 
			
		||||
        UNCOMMON: 0.22,
 | 
			
		||||
        RARE: 0.02,
 | 
			
		||||
        LEGENDARY: 0
 | 
			
		||||
    },
 | 
			
		||||
    VPQ_SILVER: {
 | 
			
		||||
        COMMON: 0.7,
 | 
			
		||||
        UNCOMMON: 0.26,
 | 
			
		||||
        RARE: 0.04,
 | 
			
		||||
        LEGENDARY: 0
 | 
			
		||||
    },
 | 
			
		||||
    VPQ_GOLD: {
 | 
			
		||||
        COMMON: 0.6,
 | 
			
		||||
        UNCOMMON: 0.34,
 | 
			
		||||
        RARE: 0.06,
 | 
			
		||||
        LEGENDARY: 0
 | 
			
		||||
    },
 | 
			
		||||
    VPQ_PLATINUM: {
 | 
			
		||||
        COMMON: 0.5,
 | 
			
		||||
        UNCOMMON: 0.4,
 | 
			
		||||
        RARE: 0.1,
 | 
			
		||||
        LEGENDARY: 0
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface IVoidProjectionRewardRequest {
 | 
			
		||||
    CurrentWave: number;
 | 
			
		||||
    ParticipantInfo: IParticipantInfo;
 | 
			
		||||
    ParticipantInfo: IVoidTearParticipantInfo;
 | 
			
		||||
    VoidTier: string;
 | 
			
		||||
    DifficultyTier: number;
 | 
			
		||||
    VoidProjectionRemovalHash: string;
 | 
			
		||||
@ -79,20 +33,6 @@ interface IVoidProjectionRewardRequest {
 | 
			
		||||
 | 
			
		||||
interface IVoidProjectionRewardResponse {
 | 
			
		||||
    CurrentWave: number;
 | 
			
		||||
    ParticipantInfo: IParticipantInfo;
 | 
			
		||||
    ParticipantInfo: IVoidTearParticipantInfo;
 | 
			
		||||
    DifficultyTier: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface IParticipantInfo {
 | 
			
		||||
    AccountId: string;
 | 
			
		||||
    Name: string;
 | 
			
		||||
    ChosenRewardOwner: string;
 | 
			
		||||
    MissionHash: string;
 | 
			
		||||
    VoidProjection: string;
 | 
			
		||||
    Reward: string;
 | 
			
		||||
    QualifiesForReward: boolean;
 | 
			
		||||
    HaveRewardResponse: boolean;
 | 
			
		||||
    RewardsMultiplier: number;
 | 
			
		||||
    RewardProjection: string;
 | 
			
		||||
    HardModeReward: ITypeCount;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -39,7 +39,7 @@ const awakeningRewards = [
 | 
			
		||||
export const addStartingGear = async (
 | 
			
		||||
    inventory: HydratedDocument<IInventoryDatabase, InventoryDocumentProps>,
 | 
			
		||||
    startingGear: TPartialStartingGear | undefined = undefined
 | 
			
		||||
) => {
 | 
			
		||||
): Promise<IInventoryChanges> => {
 | 
			
		||||
    const { LongGuns, Pistols, Suits, Melee } = startingGear || {
 | 
			
		||||
        LongGuns: [{ ItemType: "/Lotus/Weapons/Tenno/Rifle/Rifle" }],
 | 
			
		||||
        Pistols: [{ ItemType: "/Lotus/Weapons/Tenno/Pistol/Pistol" }],
 | 
			
		||||
 | 
			
		||||
@ -249,7 +249,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
 | 
			
		||||
            const suit = inventory.Suits.id(request.SuitId.$oid)!;
 | 
			
		||||
            inventory.Suits.pull(suit);
 | 
			
		||||
            const consumedSuit: IConsumedSuit = { s: suit.ItemType };
 | 
			
		||||
            if (suit.Configs && suit.Configs[0] && suit.Configs[0].pricol) {
 | 
			
		||||
            if (suit.Configs[0] && suit.Configs[0].pricol) {
 | 
			
		||||
                consumedSuit.c = suit.Configs[0].pricol;
 | 
			
		||||
            }
 | 
			
		||||
            if ((inventory.InfestedFoundry!.XP ?? 0) < 73125_00) {
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,14 @@ import { RequestHandler } from "express";
 | 
			
		||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
			
		||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
			
		||||
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
			
		||||
import { getInventory, updateCurrency, addEquipment, addMiscItems } from "@/src/services/inventoryService";
 | 
			
		||||
import {
 | 
			
		||||
    getInventory,
 | 
			
		||||
    updateCurrency,
 | 
			
		||||
    addEquipment,
 | 
			
		||||
    addMiscItems,
 | 
			
		||||
    applyDefaultUpgrades
 | 
			
		||||
} from "@/src/services/inventoryService";
 | 
			
		||||
import { ExportWeapons } from "warframe-public-export-plus";
 | 
			
		||||
 | 
			
		||||
const modularWeaponTypes: Record<string, TEquipmentKey> = {
 | 
			
		||||
    "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary": "LongGuns",
 | 
			
		||||
@ -36,8 +43,11 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
 | 
			
		||||
    const category = modularWeaponTypes[data.WeaponType];
 | 
			
		||||
    const inventory = await getInventory(accountId);
 | 
			
		||||
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
			
		||||
    const configs = applyDefaultUpgrades(inventory, ExportWeapons[data.Parts[0]]?.defaultUpgrades);
 | 
			
		||||
 | 
			
		||||
    // Give weapon
 | 
			
		||||
    const weapon = addEquipment(inventory, category, data.WeaponType, data.Parts);
 | 
			
		||||
    const inventoryChanges = addEquipment(inventory, category, data.WeaponType, data.Parts, {}, { Configs: configs });
 | 
			
		||||
 | 
			
		||||
    // Remove credits & parts
 | 
			
		||||
    const miscItemChanges = [];
 | 
			
		||||
@ -58,8 +68,8 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
 | 
			
		||||
    // Tell client what we did
 | 
			
		||||
    res.json({
 | 
			
		||||
        InventoryChanges: {
 | 
			
		||||
            ...inventoryChanges,
 | 
			
		||||
            ...currencyChanges,
 | 
			
		||||
            [category]: [weapon],
 | 
			
		||||
            MiscItems: miscItemChanges
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@ -50,6 +50,7 @@ const qualityKeywordToNumber: Record<VoidProjectionQuality, number> = {
 | 
			
		||||
// e.g. "/Lotus/Types/Game/Projections/T2VoidProjectionProteaPrimeDBronze" -> ["Lith", "W5", "VPQ_BRONZE"]
 | 
			
		||||
const parseProjection = (typeName: string): [string, string, VoidProjectionQuality] => {
 | 
			
		||||
    const relic: IRelic | undefined = ExportRelics[typeName];
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
			
		||||
    if (!relic) {
 | 
			
		||||
        throw new Error(`Unknown projection ${typeName}`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -39,17 +39,11 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
			
		||||
    res.miscitems = [];
 | 
			
		||||
    res.Syndicates = [];
 | 
			
		||||
    for (const [uniqueName, item] of Object.entries(ExportWarframes)) {
 | 
			
		||||
        if (
 | 
			
		||||
            item.productCategory == "Suits" ||
 | 
			
		||||
            item.productCategory == "SpaceSuits" ||
 | 
			
		||||
            item.productCategory == "MechSuits"
 | 
			
		||||
        ) {
 | 
			
		||||
            res[item.productCategory].push({
 | 
			
		||||
                uniqueName,
 | 
			
		||||
                name: getString(item.name, lang),
 | 
			
		||||
                exalted: item.exalted
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        res[item.productCategory].push({
 | 
			
		||||
            uniqueName,
 | 
			
		||||
            name: getString(item.name, lang),
 | 
			
		||||
            exalted: item.exalted
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    for (const [uniqueName, item] of Object.entries(ExportSentinels)) {
 | 
			
		||||
        if (item.productCategory == "Sentinels") {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										60
									
								
								src/helpers/relicHelper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/helpers/relicHelper.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,60 @@
 | 
			
		||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
 | 
			
		||||
import { IVoidTearParticipantInfo } from "@/src/types/requestTypes";
 | 
			
		||||
import { ExportRelics, ExportRewards, TRarity } from "warframe-public-export-plus";
 | 
			
		||||
import { getRandomWeightedReward2 } from "@/src/services/rngService";
 | 
			
		||||
import { logger } from "@/src/utils/logger";
 | 
			
		||||
import { addMiscItems } from "@/src/services/inventoryService";
 | 
			
		||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
 | 
			
		||||
 | 
			
		||||
export const crackRelic = async (
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
    participant: IVoidTearParticipantInfo
 | 
			
		||||
): Promise<void> => {
 | 
			
		||||
    const relic = ExportRelics[participant.VoidProjection];
 | 
			
		||||
    const weights = refinementToWeights[relic.quality];
 | 
			
		||||
    logger.debug(`opening a relic of quality ${relic.quality}; rarity weights are`, weights);
 | 
			
		||||
    const reward = getRandomWeightedReward2(
 | 
			
		||||
        ExportRewards[relic.rewardManifest][0] as { type: string; itemCount: number; rarity: TRarity }[], // rarity is nullable in PE+ typings, but always present for relics
 | 
			
		||||
        weights
 | 
			
		||||
    )!;
 | 
			
		||||
    logger.debug(`relic rolled`, reward);
 | 
			
		||||
    participant.Reward = reward.type;
 | 
			
		||||
 | 
			
		||||
    // Remove relic
 | 
			
		||||
    addMiscItems(inventory, [
 | 
			
		||||
        {
 | 
			
		||||
            ItemType: participant.VoidProjection,
 | 
			
		||||
            ItemCount: -1
 | 
			
		||||
        }
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    // Give reward
 | 
			
		||||
    await handleStoreItemAcquisition(reward.type, inventory, reward.itemCount);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const refinementToWeights = {
 | 
			
		||||
    VPQ_BRONZE: {
 | 
			
		||||
        COMMON: 0.76,
 | 
			
		||||
        UNCOMMON: 0.22,
 | 
			
		||||
        RARE: 0.02,
 | 
			
		||||
        LEGENDARY: 0
 | 
			
		||||
    },
 | 
			
		||||
    VPQ_SILVER: {
 | 
			
		||||
        COMMON: 0.7,
 | 
			
		||||
        UNCOMMON: 0.26,
 | 
			
		||||
        RARE: 0.04,
 | 
			
		||||
        LEGENDARY: 0
 | 
			
		||||
    },
 | 
			
		||||
    VPQ_GOLD: {
 | 
			
		||||
        COMMON: 0.6,
 | 
			
		||||
        UNCOMMON: 0.34,
 | 
			
		||||
        RARE: 0.06,
 | 
			
		||||
        LEGENDARY: 0
 | 
			
		||||
    },
 | 
			
		||||
    VPQ_PLATINUM: {
 | 
			
		||||
        COMMON: 0.5,
 | 
			
		||||
        UNCOMMON: 0.4,
 | 
			
		||||
        RARE: 0.1,
 | 
			
		||||
        LEGENDARY: 0
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
@ -25,8 +25,10 @@ mongoose
 | 
			
		||||
            cert: fs.readFileSync("static/certs/cert.pem")
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // eslint-disable-next-line @typescript-eslint/no-misused-promises
 | 
			
		||||
        http.createServer(app).listen(httpPort, () => {
 | 
			
		||||
            logger.info("HTTP server started on port " + httpPort);
 | 
			
		||||
            // eslint-disable-next-line @typescript-eslint/no-misused-promises
 | 
			
		||||
            https.createServer(options, app).listen(httpsPort, () => {
 | 
			
		||||
                logger.info("HTTPS server started on port " + httpsPort);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -808,7 +808,7 @@ detailsSchema.set("toJSON", {
 | 
			
		||||
const EquipmentSchema = new Schema<IEquipmentDatabase>(
 | 
			
		||||
    {
 | 
			
		||||
        ItemType: String,
 | 
			
		||||
        Configs: [ItemConfigSchema],
 | 
			
		||||
        Configs: { type: [ItemConfigSchema], default: [] },
 | 
			
		||||
        UpgradeVer: { type: Number, default: 101 },
 | 
			
		||||
        XP: { type: Number, default: 0 },
 | 
			
		||||
        Features: Number,
 | 
			
		||||
@ -1303,7 +1303,7 @@ inventorySchema.set("toJSON", {
 | 
			
		||||
        if (inventoryDatabase.GuildId) {
 | 
			
		||||
            inventoryResponse.GuildId = toOid(inventoryDatabase.GuildId);
 | 
			
		||||
        }
 | 
			
		||||
        if (inventoryResponse.BlessingCooldown) {
 | 
			
		||||
        if (inventoryDatabase.BlessingCooldown) {
 | 
			
		||||
            inventoryResponse.BlessingCooldown = toMongoDate(inventoryDatabase.BlessingCooldown);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -50,6 +50,7 @@ const convertEquipment = (client: IEquipmentClient): IEquipmentDatabase => {
 | 
			
		||||
        UpgradesExpiry: convertOptionalDate(client.UpgradesExpiry),
 | 
			
		||||
        CrewMembers: client.CrewMembers ? convertCrewShipMembers(client.CrewMembers) : undefined,
 | 
			
		||||
        Details: client.Details ? convertKubrowDetails(client.Details) : undefined,
 | 
			
		||||
        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
			
		||||
        Configs: client.Configs
 | 
			
		||||
            ? client.Configs.map(obj =>
 | 
			
		||||
                  Object.fromEntries(
 | 
			
		||||
 | 
			
		||||
@ -27,7 +27,7 @@ export const deleteAllMessagesRead = async (accountId: string): Promise<void> =>
 | 
			
		||||
    await Inbox.deleteMany({ ownerId: accountId, r: true });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const createNewEventMessages = async (req: Request) => {
 | 
			
		||||
export const createNewEventMessages = async (req: Request): Promise<void> => {
 | 
			
		||||
    const account = await getAccountForRequest(req);
 | 
			
		||||
    const latestEventMessageDate = account.LatestEventMessageDate;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -38,27 +38,30 @@ import { getExalted, getKeyChainItems } from "@/src/services/itemDataService";
 | 
			
		||||
import { IEquipmentClient, IEquipmentDatabase, IItemConfig } from "../types/inventoryTypes/commonInventoryTypes";
 | 
			
		||||
import {
 | 
			
		||||
    ExportArcanes,
 | 
			
		||||
    ExportBundles,
 | 
			
		||||
    ExportCustoms,
 | 
			
		||||
    ExportDrones,
 | 
			
		||||
    ExportFlavour,
 | 
			
		||||
    ExportFusionBundles,
 | 
			
		||||
    ExportGear,
 | 
			
		||||
    ExportKeys,
 | 
			
		||||
    ExportMisc,
 | 
			
		||||
    ExportRecipes,
 | 
			
		||||
    ExportResources,
 | 
			
		||||
    ExportSentinels,
 | 
			
		||||
    ExportSyndicates,
 | 
			
		||||
    ExportUpgrades,
 | 
			
		||||
    ExportWeapons,
 | 
			
		||||
    IDefaultUpgrade,
 | 
			
		||||
    TStandingLimitBin
 | 
			
		||||
} from "warframe-public-export-plus";
 | 
			
		||||
import { createShip } from "./shipService";
 | 
			
		||||
import { creditBundles } from "@/src/services/missionInventoryUpdateService";
 | 
			
		||||
import { IKeyChainRequest } from "@/src/controllers/api/giveKeyChainTriggeredItemsController";
 | 
			
		||||
import { toOid } from "../helpers/inventoryHelpers";
 | 
			
		||||
import { generateRewardSeed } from "@/src/controllers/api/getNewRewardSeedController";
 | 
			
		||||
import { addStartingGear } from "@/src/controllers/api/giveStartingGearController";
 | 
			
		||||
import { addQuestKey, completeQuest } from "@/src/services/questService";
 | 
			
		||||
import { handleBundleAcqusition } from "./purchaseService";
 | 
			
		||||
 | 
			
		||||
export const createInventory = async (
 | 
			
		||||
    accountOwnerId: Types.ObjectId,
 | 
			
		||||
@ -157,6 +160,11 @@ export const addItem = async (
 | 
			
		||||
    typeName: string,
 | 
			
		||||
    quantity: number = 1
 | 
			
		||||
): Promise<{ InventoryChanges: IInventoryChanges }> => {
 | 
			
		||||
    // Bundles are technically StoreItems but a) they don't have a normal counterpart, and b) they are used in non-StoreItem contexts, e.g. email attachments.
 | 
			
		||||
    if (typeName in ExportBundles) {
 | 
			
		||||
        return { InventoryChanges: await handleBundleAcqusition(typeName, inventory, quantity) };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Strict typing
 | 
			
		||||
    if (typeName in ExportRecipes) {
 | 
			
		||||
        const recipeChanges = [
 | 
			
		||||
@ -203,6 +211,7 @@ export const addItem = async (
 | 
			
		||||
            const inventoryChanges = {
 | 
			
		||||
                ...addCrewShip(inventory, typeName),
 | 
			
		||||
                // fix to unlock railjack modding, item bellow supposed to be obtained from archwing quest
 | 
			
		||||
                // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
			
		||||
                ...(!inventory.CrewShipHarnesses?.length
 | 
			
		||||
                    ? addCrewShipHarness(inventory, "/Lotus/Types/Game/CrewShip/RailJack/DefaultHarness")
 | 
			
		||||
                    : {})
 | 
			
		||||
@ -284,6 +293,11 @@ export const addItem = async (
 | 
			
		||||
        const weapon = ExportWeapons[typeName];
 | 
			
		||||
        if (weapon.totalDamage != 0) {
 | 
			
		||||
            const inventoryChanges = addEquipment(inventory, weapon.productCategory, typeName);
 | 
			
		||||
            if (weapon.additionalItems) {
 | 
			
		||||
                for (const item of weapon.additionalItems) {
 | 
			
		||||
                    combineInventoryChanges(inventoryChanges, await addItem(inventory, item, 1));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            updateSlots(inventory, InventorySlot.WEAPONS, 0, 1);
 | 
			
		||||
            return {
 | 
			
		||||
                InventoryChanges: {
 | 
			
		||||
@ -307,8 +321,8 @@ export const addItem = async (
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (typeName in creditBundles) {
 | 
			
		||||
        const creditsTotal = creditBundles[typeName] * quantity;
 | 
			
		||||
    if (typeName in ExportMisc.creditBundles) {
 | 
			
		||||
        const creditsTotal = ExportMisc.creditBundles[typeName] * quantity;
 | 
			
		||||
        inventory.RegularCredits += creditsTotal;
 | 
			
		||||
        return {
 | 
			
		||||
            InventoryChanges: {
 | 
			
		||||
@ -331,9 +345,8 @@ export const addItem = async (
 | 
			
		||||
 | 
			
		||||
        if (key.chainStages) {
 | 
			
		||||
            const key = addQuestKey(inventory, { ItemType: typeName });
 | 
			
		||||
            if (key) {
 | 
			
		||||
                return { InventoryChanges: { QuestKeys: [key] } };
 | 
			
		||||
            }
 | 
			
		||||
            if (!key) return { InventoryChanges: {} };
 | 
			
		||||
            return { InventoryChanges: { QuestKeys: [key] } };
 | 
			
		||||
        } else {
 | 
			
		||||
            const key = { ItemType: typeName, ItemCount: quantity };
 | 
			
		||||
 | 
			
		||||
@ -405,18 +418,21 @@ export const addItem = async (
 | 
			
		||||
            switch (typeName.substr(1).split("/")[2]) {
 | 
			
		||||
                case "Mods": // Legendary Core
 | 
			
		||||
                case "CosmeticEnhancers": // Traumatic Peculiar
 | 
			
		||||
                    const changes = [
 | 
			
		||||
                        {
 | 
			
		||||
                            ItemType: typeName,
 | 
			
		||||
                            ItemCount: quantity
 | 
			
		||||
                        }
 | 
			
		||||
                    ];
 | 
			
		||||
                    addMods(inventory, changes);
 | 
			
		||||
                    return {
 | 
			
		||||
                        InventoryChanges: {
 | 
			
		||||
                            RawUpgrades: changes
 | 
			
		||||
                        }
 | 
			
		||||
                    };
 | 
			
		||||
                    {
 | 
			
		||||
                        const changes = [
 | 
			
		||||
                            {
 | 
			
		||||
                                ItemType: typeName,
 | 
			
		||||
                                ItemCount: quantity
 | 
			
		||||
                            }
 | 
			
		||||
                        ];
 | 
			
		||||
                        addMods(inventory, changes);
 | 
			
		||||
                        return {
 | 
			
		||||
                            InventoryChanges: {
 | 
			
		||||
                                RawUpgrades: changes
 | 
			
		||||
                            }
 | 
			
		||||
                        };
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
@ -517,21 +533,15 @@ export const addItems = async (
 | 
			
		||||
    return inventoryChanges;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//TODO: maybe genericMethod for all the add methods, they share a lot of logic
 | 
			
		||||
export const addSentinel = (
 | 
			
		||||
export const applyDefaultUpgrades = (
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
    sentinelName: string,
 | 
			
		||||
    inventoryChanges: IInventoryChanges = {}
 | 
			
		||||
): IInventoryChanges => {
 | 
			
		||||
    if (ExportSentinels[sentinelName]?.defaultWeapon) {
 | 
			
		||||
        addSentinelWeapon(inventory, ExportSentinels[sentinelName].defaultWeapon, inventoryChanges);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    defaultUpgrades: IDefaultUpgrade[] | undefined
 | 
			
		||||
): IItemConfig[] => {
 | 
			
		||||
    const modsToGive: IRawUpgrade[] = [];
 | 
			
		||||
    const configs: IItemConfig[] = [];
 | 
			
		||||
    if (ExportSentinels[sentinelName]?.defaultUpgrades) {
 | 
			
		||||
    if (defaultUpgrades) {
 | 
			
		||||
        const upgrades = [];
 | 
			
		||||
        for (const defaultUpgrade of ExportSentinels[sentinelName].defaultUpgrades) {
 | 
			
		||||
        for (const defaultUpgrade of defaultUpgrades) {
 | 
			
		||||
            modsToGive.push({ ItemType: defaultUpgrade.ItemType, ItemCount: 1 });
 | 
			
		||||
            if (defaultUpgrade.Slot != -1) {
 | 
			
		||||
                upgrades[defaultUpgrade.Slot] = defaultUpgrade.ItemType;
 | 
			
		||||
@ -541,8 +551,24 @@ export const addSentinel = (
 | 
			
		||||
            configs.push({ Upgrades: upgrades });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    addMods(inventory, modsToGive);
 | 
			
		||||
    return configs;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//TODO: maybe genericMethod for all the add methods, they share a lot of logic
 | 
			
		||||
export const addSentinel = (
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
    sentinelName: string,
 | 
			
		||||
    inventoryChanges: IInventoryChanges = {}
 | 
			
		||||
): IInventoryChanges => {
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
			
		||||
    if (ExportSentinels[sentinelName]?.defaultWeapon) {
 | 
			
		||||
        addSentinelWeapon(inventory, ExportSentinels[sentinelName].defaultWeapon, inventoryChanges);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
			
		||||
    const configs: IItemConfig[] = applyDefaultUpgrades(inventory, ExportSentinels[sentinelName]?.defaultUpgrades);
 | 
			
		||||
 | 
			
		||||
    const sentinelIndex = inventory.Sentinels.push({ ItemType: sentinelName, Configs: configs, XP: 0 }) - 1;
 | 
			
		||||
    inventoryChanges.Sentinels ??= [];
 | 
			
		||||
    inventoryChanges.Sentinels.push(inventory.Sentinels[sentinelIndex].toJSON<IEquipmentClient>());
 | 
			
		||||
 | 
			
		||||
@ -33,6 +33,7 @@ import { getEntriesUnsafe } from "@/src/utils/ts-utils";
 | 
			
		||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
			
		||||
import { handleStoreItemAcquisition } from "./purchaseService";
 | 
			
		||||
import { IMissionReward } from "../types/missionTypes";
 | 
			
		||||
import { crackRelic } from "@/src/helpers/relicHelper";
 | 
			
		||||
 | 
			
		||||
const getRotations = (rotationCount: number): number[] => {
 | 
			
		||||
    if (rotationCount === 0) return [0];
 | 
			
		||||
@ -51,27 +52,6 @@ const getRandomRewardByChance = (pool: IReward[]): IRngResult | undefined => {
 | 
			
		||||
    return getRandomReward(pool as IRngResult[]);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const creditBundles: Record<string, number> = {
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/1500Credits": 1500,
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/2000Credits": 2000,
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/2500Credits": 2500,
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/3000Credits": 3000,
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/4000Credits": 4000,
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/5000Credits": 5000,
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/7500Credits": 7500,
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/10000Credits": 10000,
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/5000Hollars": 5000,
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/7500Hollars": 7500,
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/10000Hollars": 10000,
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/CorpusArenaCreditRewards/CorpusArenaRewardOneHard": 105000,
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/CorpusArenaCreditRewards/CorpusArenaRewardTwoHard": 175000,
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/CorpusArenaCreditRewards/CorpusArenaRewardThreeHard": 250000,
 | 
			
		||||
    "/Lotus/Types/StoreItems/CreditBundles/Zariman/TableACreditsCommon": 15000,
 | 
			
		||||
    "/Lotus/Types/StoreItems/CreditBundles/Zariman/TableACreditsUncommon": 30000,
 | 
			
		||||
    "/Lotus/Types/StoreItems/CreditBundles/CreditBundleA": 50000,
 | 
			
		||||
    "/Lotus/Types/StoreItems/CreditBundles/CreditBundleC": 175000
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//type TMissionInventoryUpdateKeys = keyof IMissionInventoryUpdateRequest;
 | 
			
		||||
//const ignoredInventoryUpdateKeys = ["FpsAvg", "FpsMax", "FpsMin", "FpsSamples"] satisfies TMissionInventoryUpdateKeys[]; // for keys with no meaning for this server
 | 
			
		||||
//type TignoredInventoryUpdateKeys = (typeof ignoredInventoryUpdateKeys)[number];
 | 
			
		||||
@ -80,7 +60,7 @@ export const creditBundles: Record<string, number> = {
 | 
			
		||||
export const addMissionInventoryUpdates = (
 | 
			
		||||
    inventory: HydratedDocument<IInventoryDatabase, InventoryDocumentProps>,
 | 
			
		||||
    inventoryUpdates: IMissionInventoryUpdateRequest
 | 
			
		||||
) => {
 | 
			
		||||
): Partial<IInventoryDatabase> | undefined => {
 | 
			
		||||
    //TODO: type this properly
 | 
			
		||||
    const inventoryChanges: Partial<IInventoryDatabase> = {};
 | 
			
		||||
    if (inventoryUpdates.MissionFailed === true) {
 | 
			
		||||
@ -208,14 +188,14 @@ export const addMissionInventoryUpdates = (
 | 
			
		||||
                inventory.CompletedSorties.push(value);
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            case "SeasonChallengeCompletions":
 | 
			
		||||
            case "SeasonChallengeCompletions": {
 | 
			
		||||
                const processedCompletions = value.map(({ challenge, id }) => ({
 | 
			
		||||
                    challenge: challenge.substring(challenge.lastIndexOf("/") + 1),
 | 
			
		||||
                    id
 | 
			
		||||
                }));
 | 
			
		||||
 | 
			
		||||
                inventory.SeasonChallengeHistory.push(...processedCompletions);
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            default:
 | 
			
		||||
                // Equipment XP updates
 | 
			
		||||
                if (equipmentKeys.includes(key as TEquipmentKey)) {
 | 
			
		||||
@ -242,7 +222,8 @@ export const addMissionRewards = async (
 | 
			
		||||
        RewardInfo: rewardInfo,
 | 
			
		||||
        LevelKeyName: levelKeyName,
 | 
			
		||||
        Missions: missions,
 | 
			
		||||
        RegularCredits: creditDrops
 | 
			
		||||
        RegularCredits: creditDrops,
 | 
			
		||||
        VoidTearParticipantsCurrWave: voidTearWave
 | 
			
		||||
    }: IMissionInventoryUpdateRequest
 | 
			
		||||
) => {
 | 
			
		||||
    if (!rewardInfo) {
 | 
			
		||||
@ -252,9 +233,7 @@ export const addMissionRewards = async (
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //TODO: check double reward merging
 | 
			
		||||
    const MissionRewards = getRandomMissionDrops(rewardInfo).map(drop => {
 | 
			
		||||
        return { StoreItem: drop.type, ItemCount: drop.itemCount };
 | 
			
		||||
    });
 | 
			
		||||
    const MissionRewards: IMissionReward[] = getRandomMissionDrops(rewardInfo);
 | 
			
		||||
    logger.debug("random mission drops:", MissionRewards);
 | 
			
		||||
    const inventoryChanges: IInventoryChanges = {};
 | 
			
		||||
 | 
			
		||||
@ -282,12 +261,15 @@ export const addMissionRewards = async (
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (missions) {
 | 
			
		||||
    if (
 | 
			
		||||
        missions &&
 | 
			
		||||
        missions.Tag != "" // #1013
 | 
			
		||||
    ) {
 | 
			
		||||
        const node = getNode(missions.Tag);
 | 
			
		||||
 | 
			
		||||
        //node based credit rewards for mission completion
 | 
			
		||||
        if (node.missionIndex !== 28) {
 | 
			
		||||
            const levelCreditReward = getLevelCreditRewards(missions?.Tag);
 | 
			
		||||
            const levelCreditReward = getLevelCreditRewards(missions.Tag);
 | 
			
		||||
            missionCompletionCredits += levelCreditReward;
 | 
			
		||||
            inventory.RegularCredits += levelCreditReward;
 | 
			
		||||
            logger.debug(`levelCreditReward ${levelCreditReward}`);
 | 
			
		||||
@ -312,6 +294,15 @@ export const addMissionRewards = async (
 | 
			
		||||
        rngRewardCredits: inventoryChanges.RegularCredits ?? 0
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
        voidTearWave &&
 | 
			
		||||
        voidTearWave.Participants[0].QualifiesForReward &&
 | 
			
		||||
        !voidTearWave.Participants[0].HaveRewardResponse
 | 
			
		||||
    ) {
 | 
			
		||||
        await crackRelic(inventory, voidTearWave.Participants[0]);
 | 
			
		||||
        MissionRewards.push({ StoreItem: voidTearWave.Participants[0].Reward, ItemCount: 1 });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return { inventoryChanges, MissionRewards, credits };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -360,7 +351,7 @@ export const addFixedLevelRewards = (
 | 
			
		||||
    if (rewards.items) {
 | 
			
		||||
        for (const item of rewards.items) {
 | 
			
		||||
            MissionRewards.push({
 | 
			
		||||
                StoreItem: `/Lotus/StoreItems${item.substring("Lotus/".length)}`,
 | 
			
		||||
                StoreItem: item.includes(`/StoreItems/`) ? item : `/Lotus/StoreItems${item.substring("Lotus/".length)}`,
 | 
			
		||||
                ItemCount: 1
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
@ -368,7 +359,9 @@ export const addFixedLevelRewards = (
 | 
			
		||||
    if (rewards.countedItems) {
 | 
			
		||||
        for (const item of rewards.countedItems) {
 | 
			
		||||
            MissionRewards.push({
 | 
			
		||||
                StoreItem: `/Lotus/StoreItems${item.ItemType.substring("Lotus/".length)}`,
 | 
			
		||||
                StoreItem: item.ItemType.includes(`/StoreItems/`)
 | 
			
		||||
                    ? item.ItemType
 | 
			
		||||
                    : `/Lotus/StoreItems${item.ItemType.substring("Lotus/".length)}`,
 | 
			
		||||
                ItemCount: item.ItemCount
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
@ -389,11 +382,14 @@ function getLevelCreditRewards(nodeName: string): number {
 | 
			
		||||
    //TODO: get dark sektor fixed credit rewards and railjack bonus
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getRandomMissionDrops(RewardInfo: IRewardInfo): IRngResult[] {
 | 
			
		||||
    const drops: IRngResult[] = [];
 | 
			
		||||
function getRandomMissionDrops(RewardInfo: IRewardInfo): IMissionReward[] {
 | 
			
		||||
    const drops: IMissionReward[] = [];
 | 
			
		||||
    if (RewardInfo.node in ExportRegions) {
 | 
			
		||||
        const region = ExportRegions[RewardInfo.node];
 | 
			
		||||
        const rewardManifests = region.rewardManifests ?? [];
 | 
			
		||||
        const rewardManifests: string[] =
 | 
			
		||||
            RewardInfo.periodicMissionTag == "EliteAlert" || RewardInfo.periodicMissionTag == "EliteAlertB"
 | 
			
		||||
                ? ["/Lotus/Types/Game/MissionDecks/EliteAlertMissionRewards/EliteAlertMissionRewards"]
 | 
			
		||||
                : region.rewardManifests;
 | 
			
		||||
 | 
			
		||||
        let rotations: number[] = [];
 | 
			
		||||
        if (RewardInfo.VaultsCracked) {
 | 
			
		||||
@ -412,7 +408,7 @@ function getRandomMissionDrops(RewardInfo: IRewardInfo): IRngResult[] {
 | 
			
		||||
                    const rotationRewards = table[rotation];
 | 
			
		||||
                    const drop = getRandomRewardByChance(rotationRewards);
 | 
			
		||||
                    if (drop) {
 | 
			
		||||
                        drops.push(drop);
 | 
			
		||||
                        drops.push({ StoreItem: drop.type, ItemCount: drop.itemCount });
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
@ -422,7 +418,7 @@ function getRandomMissionDrops(RewardInfo: IRewardInfo): IRngResult[] {
 | 
			
		||||
            for (let rotation = 0; rotation != RewardInfo.EnemyCachesFound; ++rotation) {
 | 
			
		||||
                const drop = getRandomRewardByChance(deck[rotation]);
 | 
			
		||||
                if (drop) {
 | 
			
		||||
                    drops.push(drop);
 | 
			
		||||
                    drops.push({ StoreItem: drop.type, ItemCount: drop.itemCount, FromEnemyCache: true });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@ -441,7 +437,7 @@ function getRandomMissionDrops(RewardInfo: IRewardInfo): IRngResult[] {
 | 
			
		||||
 | 
			
		||||
            const drop = getRandomRewardByChance(deck[rotation]);
 | 
			
		||||
            if (drop) {
 | 
			
		||||
                drops.push(drop);
 | 
			
		||||
                drops.push({ StoreItem: drop.type, ItemCount: drop.itemCount });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -16,8 +16,10 @@ import { logger } from "@/src/utils/logger";
 | 
			
		||||
import worldState from "@/static/fixed_responses/worldState/worldState.json";
 | 
			
		||||
import {
 | 
			
		||||
    ExportBoosterPacks,
 | 
			
		||||
    ExportBoosters,
 | 
			
		||||
    ExportBundles,
 | 
			
		||||
    ExportGear,
 | 
			
		||||
    ExportMisc,
 | 
			
		||||
    ExportResources,
 | 
			
		||||
    ExportSyndicates,
 | 
			
		||||
    ExportVendors,
 | 
			
		||||
@ -25,7 +27,6 @@ import {
 | 
			
		||||
} from "warframe-public-export-plus";
 | 
			
		||||
import { config } from "./configService";
 | 
			
		||||
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
 | 
			
		||||
import { creditBundles } from "./missionInventoryUpdateService";
 | 
			
		||||
 | 
			
		||||
export const getStoreItemCategory = (storeItem: string): string => {
 | 
			
		||||
    const storeItemString = getSubstringFromKeyword(storeItem, "StoreItems/");
 | 
			
		||||
@ -199,6 +200,31 @@ const handleItemPrices = (
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const handleBundleAcqusition = async (
 | 
			
		||||
    storeItemName: string,
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
    quantity: number = 1,
 | 
			
		||||
    inventoryChanges: IInventoryChanges = {}
 | 
			
		||||
): Promise<IInventoryChanges> => {
 | 
			
		||||
    const bundle = ExportBundles[storeItemName];
 | 
			
		||||
    logger.debug("acquiring bundle", bundle);
 | 
			
		||||
    for (const component of bundle.components) {
 | 
			
		||||
        combineInventoryChanges(
 | 
			
		||||
            inventoryChanges,
 | 
			
		||||
            (
 | 
			
		||||
                await handleStoreItemAcquisition(
 | 
			
		||||
                    component.typeName,
 | 
			
		||||
                    inventory,
 | 
			
		||||
                    component.purchaseQuantity * quantity,
 | 
			
		||||
                    component.durability,
 | 
			
		||||
                    true
 | 
			
		||||
                )
 | 
			
		||||
            ).InventoryChanges
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    return inventoryChanges;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const handleStoreItemAcquisition = async (
 | 
			
		||||
    storeItemName: string,
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
@ -211,22 +237,7 @@ export const handleStoreItemAcquisition = async (
 | 
			
		||||
    };
 | 
			
		||||
    logger.debug(`handling acquision of ${storeItemName}`);
 | 
			
		||||
    if (storeItemName in ExportBundles) {
 | 
			
		||||
        const bundle = ExportBundles[storeItemName];
 | 
			
		||||
        logger.debug("acquiring bundle", bundle);
 | 
			
		||||
        for (const component of bundle.components) {
 | 
			
		||||
            combineInventoryChanges(
 | 
			
		||||
                purchaseResponse.InventoryChanges,
 | 
			
		||||
                (
 | 
			
		||||
                    await handleStoreItemAcquisition(
 | 
			
		||||
                        component.typeName,
 | 
			
		||||
                        inventory,
 | 
			
		||||
                        component.purchaseQuantity * quantity,
 | 
			
		||||
                        component.durability,
 | 
			
		||||
                        true
 | 
			
		||||
                    )
 | 
			
		||||
                ).InventoryChanges
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        await handleBundleAcqusition(storeItemName, inventory, quantity, purchaseResponse.InventoryChanges);
 | 
			
		||||
    } else {
 | 
			
		||||
        const storeCategory = getStoreItemCategory(storeItemName);
 | 
			
		||||
        const internalName = storeItemName.replace("/StoreItems", "");
 | 
			
		||||
@ -247,7 +258,7 @@ export const handleStoreItemAcquisition = async (
 | 
			
		||||
                purchaseResponse = await handleTypesPurchase(internalName, inventory, quantity);
 | 
			
		||||
                break;
 | 
			
		||||
            case "Boosters":
 | 
			
		||||
                purchaseResponse = handleBoostersPurchase(internalName, inventory, durability);
 | 
			
		||||
                purchaseResponse = handleBoostersPurchase(storeItemName, inventory, durability);
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -335,8 +346,8 @@ const handleCreditBundlePurchase = async (
 | 
			
		||||
    typeName: string,
 | 
			
		||||
    inventory: TInventoryDatabaseDocument
 | 
			
		||||
): Promise<IPurchaseResponse> => {
 | 
			
		||||
    if (typeName && typeName in creditBundles) {
 | 
			
		||||
        const creditsAmount = creditBundles[typeName];
 | 
			
		||||
    if (typeName && typeName in ExportMisc.creditBundles) {
 | 
			
		||||
        const creditsAmount = ExportMisc.creditBundles[typeName];
 | 
			
		||||
 | 
			
		||||
        inventory.RegularCredits += creditsAmount;
 | 
			
		||||
        await inventory.save();
 | 
			
		||||
@ -367,32 +378,18 @@ const handleTypesPurchase = async (
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const boosterCollection = [
 | 
			
		||||
    "/Lotus/Types/Boosters/ResourceAmountBooster",
 | 
			
		||||
    "/Lotus/Types/Boosters/AffinityBooster",
 | 
			
		||||
    "/Lotus/Types/Boosters/ResourceDropChanceBooster",
 | 
			
		||||
    "/Lotus/Types/Boosters/CreditBooster"
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const boosterDuration: Record<TRarity, number> = {
 | 
			
		||||
    COMMON: 3 * 86400,
 | 
			
		||||
    UNCOMMON: 7 * 86400,
 | 
			
		||||
    RARE: 30 * 86400,
 | 
			
		||||
    LEGENDARY: 90 * 86400
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handleBoostersPurchase = (
 | 
			
		||||
    boosterStoreName: string,
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
    durability: TRarity
 | 
			
		||||
): { InventoryChanges: IInventoryChanges } => {
 | 
			
		||||
    const ItemType = boosterStoreName.replace("StoreItem", "");
 | 
			
		||||
    if (!boosterCollection.find(x => x == ItemType)) {
 | 
			
		||||
        logger.error(`unknown booster type: ${ItemType}`);
 | 
			
		||||
    if (!(boosterStoreName in ExportBoosters)) {
 | 
			
		||||
        logger.error(`unknown booster type: ${boosterStoreName}`);
 | 
			
		||||
        return { InventoryChanges: {} };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const ExpiryDate = boosterDuration[durability];
 | 
			
		||||
    const ItemType = ExportBoosters[boosterStoreName].typeName;
 | 
			
		||||
    const ExpiryDate = ExportMisc.boosterDurations[durability];
 | 
			
		||||
 | 
			
		||||
    addBooster(ItemType, ExpiryDate, inventory);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -15,6 +15,7 @@ import { logger } from "@/src/utils/logger";
 | 
			
		||||
import { HydratedDocument } from "mongoose";
 | 
			
		||||
import { ExportKeys } from "warframe-public-export-plus";
 | 
			
		||||
import { addFixedLevelRewards } from "./missionInventoryUpdateService";
 | 
			
		||||
import { IInventoryChanges } from "../types/purchaseTypes";
 | 
			
		||||
 | 
			
		||||
export interface IUpdateQuestRequest {
 | 
			
		||||
    QuestKeys: Omit<IQuestKeyDatabase, "CompletionDate">[];
 | 
			
		||||
@ -64,6 +65,7 @@ export const updateQuestStage = (
 | 
			
		||||
 | 
			
		||||
    const questStage = quest.Progress[ChainStage];
 | 
			
		||||
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
			
		||||
    if (!questStage) {
 | 
			
		||||
        const questStageIndex = quest.Progress.push(questStageUpdate) - 1;
 | 
			
		||||
        if (questStageIndex !== ChainStage) {
 | 
			
		||||
@ -77,7 +79,7 @@ export const updateQuestStage = (
 | 
			
		||||
 | 
			
		||||
export const addQuestKey = (inventory: TInventoryDatabaseDocument, questKey: IQuestKeyDatabase) => {
 | 
			
		||||
    if (inventory.QuestKeys.some(q => q.ItemType === questKey.ItemType)) {
 | 
			
		||||
        logger.error(`quest key ${questKey.ItemType} already exists`);
 | 
			
		||||
        logger.warn(`Quest key ${questKey.ItemType} already exists. It will not be added`);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    const index = inventory.QuestKeys.push(questKey);
 | 
			
		||||
@ -86,6 +88,7 @@ export const addQuestKey = (inventory: TInventoryDatabaseDocument, questKey: IQu
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const completeQuest = async (inventory: TInventoryDatabaseDocument, questKey: string) => {
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
			
		||||
    const chainStages = ExportKeys[questKey]?.chainStages;
 | 
			
		||||
 | 
			
		||||
    if (!chainStages) {
 | 
			
		||||
@ -164,7 +167,10 @@ export const completeQuest = async (inventory: TInventoryDatabaseDocument, quest
 | 
			
		||||
    //TODO: handle quest completion items
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const giveKeyChainItem = async (inventory: TInventoryDatabaseDocument, keyChainInfo: IKeyChainRequest) => {
 | 
			
		||||
export const giveKeyChainItem = async (
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
    keyChainInfo: IKeyChainRequest
 | 
			
		||||
): Promise<IInventoryChanges> => {
 | 
			
		||||
    const inventoryChanges = await addKeyChainItems(inventory, keyChainInfo);
 | 
			
		||||
 | 
			
		||||
    if (isEmptyObject(inventoryChanges)) {
 | 
			
		||||
@ -189,7 +195,7 @@ export const giveKeyChainMessage = async (
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
    accountId: string,
 | 
			
		||||
    keyChainInfo: IKeyChainRequest
 | 
			
		||||
) => {
 | 
			
		||||
): Promise<void> => {
 | 
			
		||||
    const keyChainMessage = getKeyChainMessage(keyChainInfo);
 | 
			
		||||
 | 
			
		||||
    const message = {
 | 
			
		||||
 | 
			
		||||
@ -148,7 +148,7 @@ export const handleInventoryItemConfigChange = async (
 | 
			
		||||
 | 
			
		||||
                    const itemEntries = equipment as IItemEntry;
 | 
			
		||||
                    for (const [itemId, itemConfigEntries] of Object.entries(itemEntries)) {
 | 
			
		||||
                        const inventoryItem = inventory[equipmentName].find(item => item._id?.toString() === itemId);
 | 
			
		||||
                        const inventoryItem = inventory[equipmentName].id(itemId);
 | 
			
		||||
 | 
			
		||||
                        if (!inventoryItem) {
 | 
			
		||||
                            throw new Error(`inventory item ${equipmentName} not found with id ${itemId}`);
 | 
			
		||||
 | 
			
		||||
@ -58,6 +58,7 @@ export const updateStats = async (playerStats: TStatsDatabaseDocument, payload:
 | 
			
		||||
                                        break;
 | 
			
		||||
                                    default:
 | 
			
		||||
                                        if (!ignoredCategories.includes(category)) {
 | 
			
		||||
                                            // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
			
		||||
                                            if (!unknownCategories[action]) {
 | 
			
		||||
                                                unknownCategories[action] = [];
 | 
			
		||||
                                            }
 | 
			
		||||
@ -105,7 +106,7 @@ export const updateStats = async (playerStats: TStatsDatabaseDocument, payload:
 | 
			
		||||
                        case "FIRE_WEAPON":
 | 
			
		||||
                        case "HIT_ENTITY_ITEM":
 | 
			
		||||
                        case "HEADSHOT_ITEM":
 | 
			
		||||
                        case "KILL_ENEMY_ITEM":
 | 
			
		||||
                        case "KILL_ENEMY_ITEM": {
 | 
			
		||||
                            playerStats.Weapons ??= [];
 | 
			
		||||
                            const statKey = {
 | 
			
		||||
                                FIRE_WEAPON: "fired",
 | 
			
		||||
@ -126,10 +127,11 @@ export const updateStats = async (playerStats: TStatsDatabaseDocument, payload:
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        case "KILL_ENEMY":
 | 
			
		||||
                        case "EXECUTE_ENEMY":
 | 
			
		||||
                        case "HEADSHOT":
 | 
			
		||||
                        case "HEADSHOT": {
 | 
			
		||||
                            playerStats.Enemies ??= [];
 | 
			
		||||
                            const enemyStatKey = {
 | 
			
		||||
                                KILL_ENEMY: "kills",
 | 
			
		||||
@ -149,6 +151,7 @@ export const updateStats = async (playerStats: TStatsDatabaseDocument, payload:
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        case "DIE":
 | 
			
		||||
                            playerStats.Enemies ??= [];
 | 
			
		||||
@ -229,6 +232,7 @@ export const updateStats = async (playerStats: TStatsDatabaseDocument, payload:
 | 
			
		||||
 | 
			
		||||
                        default:
 | 
			
		||||
                            if (!ignoredCategories.includes(category)) {
 | 
			
		||||
                                // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
			
		||||
                                if (!unknownCategories[action]) {
 | 
			
		||||
                                    unknownCategories[action] = [];
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
@ -51,7 +51,7 @@ export interface IInventoryDatabase
 | 
			
		||||
    GuildId?: Types.ObjectId;
 | 
			
		||||
    PendingRecipes: IPendingRecipe[];
 | 
			
		||||
    QuestKeys: IQuestKeyDatabase[];
 | 
			
		||||
    BlessingCooldown: Date;
 | 
			
		||||
    BlessingCooldown?: Date;
 | 
			
		||||
    Ships: Types.ObjectId[];
 | 
			
		||||
    WeaponSkins: IWeaponSkinDatabase[];
 | 
			
		||||
    Upgrades: IUpgradeDatabase[];
 | 
			
		||||
@ -300,7 +300,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
 | 
			
		||||
    PlayedParkourTutorial: boolean;
 | 
			
		||||
    SubscribedToEmailsPersonalized: number;
 | 
			
		||||
    InfestedFoundry?: IInfestedFoundryClient;
 | 
			
		||||
    BlessingCooldown: IMongoDate;
 | 
			
		||||
    BlessingCooldown?: IMongoDate;
 | 
			
		||||
    CrewShipRawSalvage: IConsumable[];
 | 
			
		||||
    CrewMembers: ICrewMember[];
 | 
			
		||||
    LotusCustomization: ILotusCustomization;
 | 
			
		||||
@ -309,7 +309,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
 | 
			
		||||
    LastInventorySync: IOid;
 | 
			
		||||
    NextRefill: IMongoDate; // Next time argon crystals will have a decay tick
 | 
			
		||||
    FoundToday?: IMiscItem[]; // for Argon Crystals
 | 
			
		||||
    CustomMarkers: ICustomMarkers[];
 | 
			
		||||
    CustomMarkers?: ICustomMarkers[];
 | 
			
		||||
    ActiveLandscapeTraps: any[];
 | 
			
		||||
    EvolutionProgress?: IEvolutionProgress[];
 | 
			
		||||
    RepVotes: any[];
 | 
			
		||||
 | 
			
		||||
@ -8,4 +8,6 @@ export interface IMissionReward {
 | 
			
		||||
    ItemCount: number;
 | 
			
		||||
    TweetText?: string;
 | 
			
		||||
    ProductCategory?: string;
 | 
			
		||||
    FromEnemyCache?: boolean;
 | 
			
		||||
    IsStrippedItem?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -87,6 +87,11 @@ export type IMissionInventoryUpdateRequest = {
 | 
			
		||||
    PlayerSkillGains: IPlayerSkills;
 | 
			
		||||
    CustomMarkers?: ICustomMarkers[];
 | 
			
		||||
    LoreFragmentScans?: ILoreFragmentScan[];
 | 
			
		||||
    VoidTearParticipantsCurrWave?: {
 | 
			
		||||
        Wave: number;
 | 
			
		||||
        IsFinalWave: boolean;
 | 
			
		||||
        Participants: IVoidTearParticipantInfo[];
 | 
			
		||||
    };
 | 
			
		||||
} & {
 | 
			
		||||
    [K in TEquipmentKey]?: IEquipmentClient[];
 | 
			
		||||
};
 | 
			
		||||
@ -136,3 +141,17 @@ export interface IUnlockShipFeatureRequest {
 | 
			
		||||
    KeyChain: string;
 | 
			
		||||
    ChainStage: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IVoidTearParticipantInfo {
 | 
			
		||||
    AccountId: string;
 | 
			
		||||
    Name: string;
 | 
			
		||||
    ChosenRewardOwner: string;
 | 
			
		||||
    MissionHash: string;
 | 
			
		||||
    VoidProjection: string;
 | 
			
		||||
    Reward: string;
 | 
			
		||||
    QualifiesForReward: boolean;
 | 
			
		||||
    HaveRewardResponse: boolean;
 | 
			
		||||
    RewardsMultiplier: number;
 | 
			
		||||
    RewardProjection: string;
 | 
			
		||||
    HardModeReward: ITypeCount;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -33,9 +33,9 @@ const consolelogFormat = format.printf(info => {
 | 
			
		||||
            colors: true
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return `${info.timestamp} [${info.version}] ${info.level}: ${info.message} ${metadataString}`;
 | 
			
		||||
        return `${info.timestamp as string} [${info.version as string}] ${info.level}: ${info.message as string} ${metadataString}`;
 | 
			
		||||
    }
 | 
			
		||||
    return `${info.timestamp} [${info.version}] ${info.level}: ${info.message}`;
 | 
			
		||||
    return `${info.timestamp as string} [${info.version as string}] ${info.level}: ${info.message as string}`;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const fileFormat = format.combine(
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user