import { IMissionRewardResponse, IInventoryFieldType, inventoryFields } from "@/src/types/missionTypes"; import { ExportRegions, ExportRewards, ExportUpgrades, ExportGear, ExportRecipes, ExportRelics, IReward } from "warframe-public-export-plus"; import { IMissionInventoryUpdateRequest } from "../types/requestTypes"; import { logger } from "@/src/utils/logger"; import { IRngResult, getRandomReward } from "@/src/services/rngService"; // need reverse engineer rewardSeed, otherwise ingame displayed rotation reward will be different than added to db or displayed on mission end const getRewards = ({ RewardInfo }: IMissionInventoryUpdateRequest): { InventoryChanges: IMissionInventoryUpdateRequest; MissionRewards: IMissionRewardResponse[]; } => { if (!RewardInfo) { return { InventoryChanges: {}, MissionRewards: [] }; } const drops: IRngResult[] = []; if (RewardInfo.node in ExportRegions) { const region = ExportRegions[RewardInfo.node]; const rewardManifests = region.rewardManifests ?? []; let rotations: number[] = []; if (RewardInfo.VaultsCracked) { // For Spy missions, e.g. 3 vaults cracked = A, B, C for (let i = 0; i != RewardInfo.VaultsCracked; ++i) { rotations.push(i); } } else { const rotationCount = RewardInfo.rewardQualifications?.length || 0; rotations = getRotations(rotationCount); } rewardManifests .map(name => ExportRewards[name]) .forEach(table => { for (const rotation of rotations) { const rotationRewards = table[rotation]; const drop = getRandomRewardByChance(rotationRewards); if (drop) { drops.push(drop); } } }); if (region.cacheRewardManifest && RewardInfo.EnemyCachesFound) { const deck = ExportRewards[region.cacheRewardManifest]; for (let rotation = 0; rotation != RewardInfo.EnemyCachesFound; ++rotation) { const drop = getRandomRewardByChance(deck[rotation]); if (drop) { drops.push(drop); } } } } logger.debug("Mission rewards:", drops); return formatRewardsToInventoryType(drops); }; const combineRewardAndLootInventory = ( rewardInventory: IMissionInventoryUpdateRequest, lootInventory: IMissionInventoryUpdateRequest ) => { const missionCredits = lootInventory.RegularCredits || 0; const creditsBonus = rewardInventory.RegularCredits || 0; const totalCredits = missionCredits + creditsBonus; let FusionPoints = rewardInventory.FusionPoints || 0; // Discharge Endo picked up during the mission if (lootInventory.FusionBundles) { for (const fusionBundle of lootInventory.FusionBundles) { if (fusionBundle.ItemType in fusionBundles) { FusionPoints += fusionBundles[fusionBundle.ItemType] * fusionBundle.ItemCount; } else { logger.error(`unknown fusion bundle: ${fusionBundle.ItemType}`); } } lootInventory.FusionBundles = undefined; } lootInventory.RegularCredits = totalCredits; lootInventory.FusionPoints = FusionPoints; inventoryFields.forEach((field: IInventoryFieldType) => { if (rewardInventory[field] && !lootInventory[field]) { lootInventory[field] = []; } rewardInventory[field]?.forEach(item => lootInventory[field]!.push(item)); }); return { combinedInventoryChanges: lootInventory, TotalCredits: [totalCredits, totalCredits], CreditsBonus: [creditsBonus, creditsBonus], MissionCredits: [missionCredits, missionCredits], FusionPoints: FusionPoints }; }; const getRotations = (rotationCount: number): number[] => { if (rotationCount === 0) return [0]; const rotationPattern = [0, 0, 1, 2]; // A, A, B, C const rotatedValues = []; for (let i = 0; i < rotationCount; i++) { rotatedValues.push(rotationPattern[i % rotationPattern.length]); } return rotatedValues; }; const getRandomRewardByChance = (pool: IReward[]): IRngResult | undefined => { return getRandomReward(pool as IRngResult[]); }; const creditBundles: Record = { "/Lotus/StoreItems/Types/PickUps/Credits/1500Credits": 1500, "/Lotus/StoreItems/Types/PickUps/Credits/2000Credits": 2000, "/Lotus/StoreItems/Types/PickUps/Credits/2500Credits": 2500, "/Lotus/StoreItems/Types/PickUps/Credits/3000Credits": 3000, "/Lotus/StoreItems/Types/PickUps/Credits/4000Credits": 4000, "/Lotus/StoreItems/Types/PickUps/Credits/5000Credits": 5000, "/Lotus/StoreItems/Types/PickUps/Credits/7500Credits": 7500, "/Lotus/StoreItems/Types/PickUps/Credits/10000Credits": 10000, "/Lotus/StoreItems/Types/StoreItems/CreditBundles/Zariman/TableACreditsCommon": 15000, "/Lotus/StoreItems/Types/StoreItems/CreditBundles/Zariman/TableACreditsUncommon": 30000, "/Lotus/StoreItems/Types/PickUps/Credits/CorpusArenaCreditRewards/CorpusArenaRewardOneHard": 105000, "/Lotus/StoreItems/Types/PickUps/Credits/CorpusArenaCreditRewards/CorpusArenaRewardTwoHard": 175000, "/Lotus/StoreItems/Types/PickUps/Credits/CorpusArenaCreditRewards/CorpusArenaRewardThreeHard": 25000 }; const fusionBundles: Record = { "/Lotus/Upgrades/Mods/FusionBundles/CommonFusionBundle": 15, "/Lotus/Upgrades/Mods/FusionBundles/UncommonFusionBundle": 50, "/Lotus/Upgrades/Mods/FusionBundles/RareFusionBundle": 80 }; const formatRewardsToInventoryType = ( rewards: IRngResult[] ): { InventoryChanges: IMissionInventoryUpdateRequest; MissionRewards: IMissionRewardResponse[] } => { const InventoryChanges: IMissionInventoryUpdateRequest = {}; const MissionRewards: IMissionRewardResponse[] = []; for (const reward of rewards) { if (reward.type in creditBundles) { InventoryChanges.RegularCredits ??= 0; InventoryChanges.RegularCredits += creditBundles[reward.type] * reward.itemCount; } else { const type = reward.type.replace("/Lotus/StoreItems/", "/Lotus/"); if (type in fusionBundles) { InventoryChanges.FusionPoints ??= 0; InventoryChanges.FusionPoints += fusionBundles[type] * reward.itemCount; } else if (type in ExportUpgrades) { addRewardResponse(InventoryChanges, MissionRewards, type, reward.itemCount, "RawUpgrades"); } else if (type in ExportGear) { addRewardResponse(InventoryChanges, MissionRewards, type, reward.itemCount, "Consumables"); } else if (type in ExportRecipes) { addRewardResponse(InventoryChanges, MissionRewards, type, reward.itemCount, "Recipes"); } else if (type in ExportRelics) { addRewardResponse(InventoryChanges, MissionRewards, type, reward.itemCount, "MiscItems"); } else { logger.error(`rolled reward ${reward.itemCount}X ${reward.type} but unsure how to give it`); } } } return { InventoryChanges, MissionRewards }; }; const addRewardResponse = ( InventoryChanges: IMissionInventoryUpdateRequest, MissionRewards: IMissionRewardResponse[], ItemType: string, ItemCount: number, InventoryCategory: IInventoryFieldType ) => { if (!ItemType) return; if (!InventoryChanges[InventoryCategory]) { InventoryChanges[InventoryCategory] = []; } const existReward = InventoryChanges[InventoryCategory].find(item => item.ItemType === ItemType); if (existReward) { existReward.ItemCount += ItemCount; const missionReward = MissionRewards.find(missionReward => missionReward.TypeName === ItemType); if (missionReward) { missionReward.ItemCount += ItemCount; } } else { InventoryChanges[InventoryCategory].push({ ItemType, ItemCount }); MissionRewards.push({ ItemCount, TweetText: ItemType, // ensure if/how this even still used, or if it's needed at all ProductCategory: InventoryCategory, StoreItem: ItemType.replace("/Lotus/", "/Lotus/StoreItems/"), TypeName: ItemType }); } }; export { getRewards, combineRewardAndLootInventory };