diff --git a/src/controllers/api/inboxController.ts b/src/controllers/api/inboxController.ts index 4c3b8830..7adc2d3d 100644 --- a/src/controllers/api/inboxController.ts +++ b/src/controllers/api/inboxController.ts @@ -13,6 +13,7 @@ import { addItems, combineInventoryChanges, getInventory } from "@/src/services/ import { logger } from "@/src/utils/logger"; import { ExportFlavour, ExportGear } from "warframe-public-export-plus"; import { handleStoreItemAcquisition } from "@/src/services/purchaseService"; +import { fromStoreItem, isStoreItem } from "@/src/services/itemDataService"; export const inboxController: RequestHandler = async (req, res) => { const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query; @@ -48,7 +49,7 @@ export const inboxController: RequestHandler = async (req, res) => { await addItems( inventory, attachmentItems.map(attItem => ({ - ItemType: attItem, + ItemType: isStoreItem(attItem) ? fromStoreItem(attItem) : attItem, ItemCount: attItem in ExportGear ? (ExportGear[attItem].purchaseQuantity ?? 1) : 1 })), inventoryChanges diff --git a/src/helpers/nemesisHelpers.ts b/src/helpers/nemesisHelpers.ts index 6543813f..2c3347ac 100644 --- a/src/helpers/nemesisHelpers.ts +++ b/src/helpers/nemesisHelpers.ts @@ -1,12 +1,14 @@ import { ExportRegions, ExportWarframes } from "warframe-public-export-plus"; -import { IInfNode } from "@/src/types/inventoryTypes/inventoryTypes"; -import { SRng } from "@/src/services/rngService"; +import { IInfNode, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes"; +import { getRewardAtPercentage, SRng } from "@/src/services/rngService"; import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel"; import { logger } from "../utils/logger"; import { IOid } from "../types/commonTypes"; import { Types } from "mongoose"; -import { addMods } from "../services/inventoryService"; +import { addMods, generateRewardSeed } from "../services/inventoryService"; import { isArchwingMission } from "../services/worldStateService"; +import { fromStoreItem, toStoreItem } from "../services/itemDataService"; +import { createMessage } from "../services/inboxService"; export const getInfNodes = (faction: string, rank: number): IInfNode[] => { const infNodes = []; @@ -273,3 +275,109 @@ export const getInnateDamageValue = (fp: bigint): number => { } return Math.trunc(value * 0x40000000); }; + +export const getKillTokenRewardCount = (fp: bigint): number => { + const rng = new SRng(fp); + return rng.randomInt(10, 15); +}; + +// /Lotus/Types/Enemies/InfestedLich/InfestedLichRewardManifest +const infestedLichRotA = [ + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyDJRomHuman", probability: 0.046 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyDJRomInfested", probability: 0.045 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyDrillbitHuman", probability: 0.046 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyDrillbitInfested", probability: 0.045 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyHarddriveHuman", probability: 0.046 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyHarddriveInfested", probability: 0.045 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyPacketHuman", probability: 0.046 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyPacketInfested", probability: 0.045 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyZekeHuman", probability: 0.046 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyZekeInfested", probability: 0.045 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandBillboardPosterA", probability: 0.045 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandBillboardPosterB", probability: 0.046 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandDespairPoster", probability: 0.045 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandGridPoster", probability: 0.046 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandHuddlePoster", probability: 0.045 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandJumpPoster", probability: 0.046 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandLimoPoster", probability: 0.045 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandLookingDownPosterDay", probability: 0.046 }, + { + type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandLookingDownPosterNight", + probability: 0.045 + }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandSillyPoster", probability: 0.046 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandWhiteBluePoster", probability: 0.045 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandWhitePinkPoster", probability: 0.045 } +]; +const infestedLichRotB = [ + { type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraA", probability: 0.072 }, + { type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraB", probability: 0.071 }, + { type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraC", probability: 0.072 }, + { type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraD", probability: 0.071 }, + { type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraE", probability: 0.072 }, + { type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraF", probability: 0.071 }, + { type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraG", probability: 0.071 }, + { type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraH", probability: 0.072 }, + { type: "/Lotus/StoreItems/Types/Items/Emotes/DanceDJRomHype", probability: 0.071 }, + { type: "/Lotus/StoreItems/Types/Items/Emotes/DancePacketWindmillShuffle", probability: 0.072 }, + { type: "/Lotus/StoreItems/Types/Items/Emotes/DanceHarddrivePony", probability: 0.071 }, + { type: "/Lotus/StoreItems/Types/Items/Emotes/DanceDrillbitCrisscross", probability: 0.072 }, + { type: "/Lotus/StoreItems/Types/Items/Emotes/DanceZekeCanthavethis", probability: 0.071 }, + { type: "/Lotus/StoreItems/Types/Items/PhotoBooth/PhotoboothTileRJLasXStadiumBossArena", probability: 0.071 } +]; +export const getInfestedLichItemRewards = (fp: bigint): string[] => { + const rng = new SRng(fp); + const rotAReward = getRewardAtPercentage(infestedLichRotA, rng.randomFloat())!.type; + rng.randomFloat(); // unused afaict + const rotBReward = getRewardAtPercentage(infestedLichRotB, rng.randomFloat())!.type; + return [rotAReward, rotBReward]; +}; + +export const sendCodaFinishedMessage = async ( + inventory: TInventoryDatabaseDocument, + fp: bigint = generateRewardSeed(), + name: string = "ZEKE_BEATWOMAN_TM.1999", + killed: boolean = true +): Promise => { + const att: string[] = []; + + // First vanquish/convert gives a sigil + const sigil = killed + ? "/Lotus/Upgrades/Skins/Sigils/InfLichVanquishedSigil" + : "/Lotus/Upgrades/Skins/Sigils/InfLichConvertedSigil"; + if (!inventory.WeaponSkins.find(x => x.ItemType == sigil)) { + att.push(toStoreItem(sigil)); + } + + const [rotAReward, rotBReward] = getInfestedLichItemRewards(fp); + att.push(fromStoreItem(rotAReward)); + att.push(fromStoreItem(rotBReward)); + + let countedAtt: ITypeCount[] | undefined; + if (killed) { + countedAtt = [ + { + ItemType: "/Lotus/Types/Items/MiscItems/CodaWeaponBucks", + ItemCount: getKillTokenRewardCount(fp) + } + ]; + } + + await createMessage(inventory.accountOwnerId, [ + { + sndr: "/Lotus/Language/Bosses/Ordis", + msg: "/Lotus/Language/Inbox/VanquishBandMsgBody", + arg: [ + { + Key: "LICH_NAME", + Tag: name + } + ], + att: att, + countedAtt: countedAtt, + sub: "/Lotus/Language/Inbox/VanquishBandMsgTitle", + icon: "/Lotus/Interface/Icons/Npcs/Ordis.png", + highPriority: true + } + ]); +}; diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index 3ab2d680..89b55cea 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -55,7 +55,7 @@ import kuriaMessage50 from "@/static/fixed_responses/kuriaMessages/fiftyPercent. import kuriaMessage75 from "@/static/fixed_responses/kuriaMessages/seventyFivePercent.json"; import kuriaMessage100 from "@/static/fixed_responses/kuriaMessages/oneHundredPercent.json"; import conservationAnimals from "@/static/fixed_responses/conservationAnimals.json"; -import { getInfNodes, getWeaponsForManifest } from "@/src/helpers/nemesisHelpers"; +import { getInfNodes, getWeaponsForManifest, sendCodaFinishedMessage } from "@/src/helpers/nemesisHelpers"; import { Loadout } from "../models/inventoryModels/loadoutModel"; import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes"; import { getLiteSortie, getWorldState, idToWeek } from "./worldStateService"; @@ -639,7 +639,10 @@ export const addMissionInventoryUpdates = async ( }); if (value.killed) { - if (value.weaponLoc) { + if ( + value.weaponLoc && + inventory.Nemesis.Faction != "FC_INFESTATION" // weaponLoc is "/Lotus/Language/Weapons/DerelictCernosName" for these for some reason + ) { const weaponType = getWeaponsForManifest(inventory.Nemesis.manifest)[ inventory.Nemesis.WeaponIdx ]; @@ -657,6 +660,11 @@ export const addMissionInventoryUpdates = async ( } } + // TOVERIFY: Is the inbox message also sent when converting a lich? If not, how are the rewards given? + if (inventory.Nemesis.Faction == "FC_INFESTATION") { + await sendCodaFinishedMessage(inventory, inventory.Nemesis.fp, value.nemesisName, value.killed); + } + inventory.Nemesis = undefined; } break; diff --git a/src/services/rngService.ts b/src/services/rngService.ts index bb9028b2..84a4ed8d 100644 --- a/src/services/rngService.ts +++ b/src/services/rngService.ts @@ -18,7 +18,10 @@ export const getRandomInt = (min: number, max: number): number => { return Math.floor(Math.random() * (max - min + 1)) + min; }; -const getRewardAtPercentage = (pool: T[], percentage: number): T | undefined => { +export const getRewardAtPercentage = ( + pool: T[], + percentage: number +): T | undefined => { if (pool.length == 0) return; const totalChance = pool.reduce((accum, item) => accum + item.probability, 0);