From 52c8802d5769e5b188efa383c3b5c5d8e7324254 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 14 May 2025 21:14:04 -0700 Subject: [PATCH] feat: classic lich vanquish inbox mesage (#2074) Closes #1897 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2074 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/nemesisController.ts | 4 +- src/helpers/nemesisHelpers.ts | 129 +++++++----------- src/services/missionInventoryUpdateService.ts | 98 +++++++++---- 3 files changed, 121 insertions(+), 110 deletions(-) diff --git a/src/controllers/api/nemesisController.ts b/src/controllers/api/nemesisController.ts index 8b359e3e..861e01ab 100644 --- a/src/controllers/api/nemesisController.ts +++ b/src/controllers/api/nemesisController.ts @@ -7,7 +7,7 @@ import { getNemesisPasscodeModTypes, getWeaponsForManifest, IKnifeResponse, - showdownNodes + nemesisFactionInfos } from "@/src/helpers/nemesisHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { Loadout } from "@/src/models/inventoryModels/loadoutModel"; @@ -223,7 +223,7 @@ export const nemesisController: RequestHandler = async (req, res) => { inventory.Nemesis!.InfNodes = [ { - Node: showdownNodes[inventory.Nemesis!.Faction], + Node: nemesisFactionInfos[inventory.Nemesis!.Faction].showdownNode, Influence: 1 } ]; diff --git a/src/helpers/nemesisHelpers.ts b/src/helpers/nemesisHelpers.ts index ebfd1067..015aec25 100644 --- a/src/helpers/nemesisHelpers.ts +++ b/src/helpers/nemesisHelpers.ts @@ -1,5 +1,5 @@ import { ExportRegions, ExportWarframes } from "warframe-public-export-plus"; -import { IInfNode, ITypeCount, TNemesisFaction } from "@/src/types/inventoryTypes/inventoryTypes"; +import { IInfNode, TNemesisFaction } from "@/src/types/inventoryTypes/inventoryTypes"; import { getRewardAtPercentage, SRng } from "@/src/services/rngService"; import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel"; import { logger } from "../utils/logger"; @@ -7,13 +7,51 @@ import { IOid } from "../types/commonTypes"; import { Types } from "mongoose"; import { addMods, generateRewardSeed } from "../services/inventoryService"; import { isArchwingMission } from "../services/worldStateService"; -import { fromStoreItem, toStoreItem } from "../services/itemDataService"; -import { createMessage } from "../services/inboxService"; import { version_compare } from "./inventoryHelpers"; +export interface INemesisFactionInfo { + systemIndexes: number[]; + showdownNode: string; + ephemeraChance: number; + firstKillReward: string; + firstConvertReward: string; + messageTitle: string; + messageBody: string; +} + +export const nemesisFactionInfos: Record = { + FC_GRINEER: { + systemIndexes: [2, 3, 9, 11, 18], + showdownNode: "CrewBattleNode557", + ephemeraChance: 0.05, + firstKillReward: "/Lotus/StoreItems/Upgrades/Skins/Clan/LichKillerBadgeItem", + firstConvertReward: "/Lotus/StoreItems/Upgrades/Skins/Sigils/KuvaLichSigil", + messageTitle: "/Lotus/Language/Inbox/VanquishKuvaMsgTitle", + messageBody: "/Lotus/Language/Inbox/VanquishLichMsgBody" + }, + FC_CORPUS: { + systemIndexes: [1, 15, 4, 7, 8], + showdownNode: "CrewBattleNode558", + ephemeraChance: 0.2, + firstKillReward: "/Lotus/StoreItems/Upgrades/Skins/Clan/CorpusLichBadgeItem", + firstConvertReward: "/Lotus/StoreItems/Upgrades/Skins/Sigils/CorpusLichSigil", + messageTitle: "/Lotus/Language/Inbox/VanquishLawyerMsgTitle", + messageBody: "/Lotus/Language/Inbox/VanquishLichMsgBody" + }, + FC_INFESTATION: { + systemIndexes: [23], + showdownNode: "CrewBattleNode559", + ephemeraChance: 0, + firstKillReward: "/Lotus/StoreItems/Upgrades/Skins/Sigils/InfLichVanquishedSigil", + firstConvertReward: "/Lotus/StoreItems/Upgrades/Skins/Sigils/InfLichConvertedSigil", + messageTitle: "/Lotus/Language/Inbox/VanquishBandMsgTitle", + messageBody: "/Lotus/Language/Inbox/VanquishBandMsgBody" + } +}; + export const getInfNodes = (faction: TNemesisFaction, rank: number): IInfNode[] => { const infNodes = []; - const systemIndex = systemIndexes[faction][rank]; + const systemIndex = nemesisFactionInfos[faction].systemIndexes[rank]; for (const [key, value] of Object.entries(ExportRegions)) { if ( value.systemIndex === systemIndex && @@ -35,24 +73,6 @@ export const getInfNodes = (faction: TNemesisFaction, rank: number): IInfNode[] return infNodes; }; -const systemIndexes: Record = { - FC_GRINEER: [2, 3, 9, 11, 18], - FC_CORPUS: [1, 15, 4, 7, 8], - FC_INFESTATION: [23] -}; - -export const showdownNodes: Record = { - FC_GRINEER: "CrewBattleNode557", - FC_CORPUS: "CrewBattleNode558", - FC_INFESTATION: "CrewBattleNode559" -}; - -const ephemeraProbabilities: Record = { - FC_GRINEER: 0.05, - FC_CORPUS: 0.2, - FC_INFESTATION: 0 -}; - type TInnateDamageTag = | "InnateElectricityDamage" | "InnateHeatDamage" @@ -311,11 +331,17 @@ export const getInnateDamageTag = (KillingSuit: string): TInnateDamageTag => { return ExportWarframes[KillingSuit].nemesisUpgradeTag!; }; +const petHeads = [ + "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA", + "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB", + "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC" +] as const; + export interface INemesisProfile { innateDamageTag: TInnateDamageTag; innateDamageValue: number; ephemera?: string; - petHead?: string; + petHead?: (typeof petHeads)[number]; petBody?: string; petLegs?: string; petTail?: string; @@ -337,16 +363,12 @@ export const generateNemesisProfile = ( innateDamageTag: getInnateDamageTag(killingSuit), innateDamageValue: Math.trunc(value * 0x40000000) // TODO: For -1399275245665749231n, the value should be 75306944, but we're off by 59 with 75307003. }; - if (rng.randomFloat() <= ephemeraProbabilities[Faction] && Faction != "FC_INFESTATION") { + if (rng.randomFloat() <= nemesisFactionInfos[Faction].ephemeraChance && Faction != "FC_INFESTATION") { profile.ephemera = ephmeraTypes[Faction][profile.innateDamageTag]; } rng.randomFloat(); // something related to sentinel agent maybe if (Faction == "FC_CORPUS") { - profile.petHead = rng.randomElement([ - "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA", - "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB", - "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC" - ])!; + profile.petHead = rng.randomElement(petHeads)!; profile.petBody = rng.randomElement([ "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyA", "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyB", @@ -422,52 +444,3 @@ export const getInfestedLichItemRewards = (fp: bigint): string[] => { 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 1caf5767..4a5150fe 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -10,7 +10,7 @@ import { import { IMissionInventoryUpdateRequest, IRewardInfo } from "../types/requestTypes"; import { logger } from "@/src/utils/logger"; import { IRngResult, SRng, getRandomElement, getRandomReward } from "@/src/services/rngService"; -import { equipmentKeys, IMission, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes"; +import { equipmentKeys, IMission, ITypeCount, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes"; import { addBooster, addChallenges, @@ -44,7 +44,7 @@ import { import { updateQuestKey } from "@/src/services/questService"; import { Types } from "mongoose"; import { IAffiliationMods, IInventoryChanges } from "@/src/types/purchaseTypes"; -import { getLevelKeyRewards, toStoreItem } from "@/src/services/itemDataService"; +import { fromStoreItem, getLevelKeyRewards, toStoreItem } from "@/src/services/itemDataService"; import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { getEntriesUnsafe } from "@/src/utils/ts-utils"; import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; @@ -58,10 +58,12 @@ import kuriaMessage100 from "@/static/fixed_responses/kuriaMessages/oneHundredPe import conservationAnimals from "@/static/fixed_responses/conservationAnimals.json"; import { generateNemesisProfile, + getInfestedLichItemRewards, getInfNodes, + getKillTokenRewardCount, getNemesisPasscode, getWeaponsForManifest, - sendCodaFinishedMessage + nemesisFactionInfos } from "@/src/helpers/nemesisHelpers"; import { Loadout } from "../models/inventoryModels/loadoutModel"; import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes"; @@ -665,6 +667,9 @@ export const addMissionInventoryUpdates = async ( inventory.Nemesis.Faction, inventory.Nemesis.KillingSuit ); + const nemesisFactionInfo = nemesisFactionInfos[inventory.Nemesis.Faction]; + const att: string[] = []; + let countedAtt: ITypeCount[] | undefined; if (value.killed) { if ( @@ -675,45 +680,78 @@ export const addMissionInventoryUpdates = async ( inventory.Nemesis.WeaponIdx ]; giveNemesisWeaponRecipe(inventory, weaponType, value.nemesisName, value.weaponLoc, profile); + att.push(weaponType); } - if (value.petLoc) { + //if (value.petLoc) { + if (profile.petHead) { giveNemesisPetRecipe(inventory, value.nemesisName, profile); + att.push( + { + "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA": + "/Lotus/Types/Recipes/ZanukaPet/ZanukaPetCompleteHeadABlueprint", + "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB": + "/Lotus/Types/Recipes/ZanukaPet/ZanukaPetCompleteHeadBBlueprint", + "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC": + "/Lotus/Types/Recipes/ZanukaPet/ZanukaPetCompleteHeadCBlueprint" + }[profile.petHead] + ); } } // "Players will receive a Lich's Ephemera regardless of whether they Vanquish or Convert them." if (profile.ephemera) { addSkin(inventory, profile.ephemera); + att.push(profile.ephemera); } - switch (inventory.Nemesis.Faction) { - case "FC_GRINEER": - addSkin( - inventory, - value.killed - ? "/Lotus/Upgrades/Skins/Clan/LichKillerBadgeItem" - : "/Lotus/Upgrades/Skins/Sigils/KuvaLichSigil" - ); - break; + const skinRewardStoreItem = value.killed + ? nemesisFactionInfo.firstKillReward + : nemesisFactionInfo.firstConvertReward; + if (Object.keys(addSkin(inventory, fromStoreItem(skinRewardStoreItem))).length != 0) { + att.push(skinRewardStoreItem); + } - case "FC_CORPUS": - addSkin( - inventory, - value.killed - ? "/Lotus/Upgrades/Skins/Clan/CorpusLichBadgeItem" - : "/Lotus/Upgrades/Skins/Sigils/CorpusLichSigil" - ); - break; + if (inventory.Nemesis.Faction == "FC_INFESTATION") { + const [rotARewardStoreItem, rotBRewardStoreItem] = getInfestedLichItemRewards( + inventory.Nemesis.fp + ); + const rotAReward = fromStoreItem(rotARewardStoreItem); + const rotBReward = fromStoreItem(rotBRewardStoreItem); + await addItem(inventory, rotAReward); + await addItem(inventory, rotBReward); + att.push(rotAReward); + att.push(rotBReward); - case "FC_INFESTATION": - // TOVERIFY: Is the inbox message also sent when converting a lich? If not, how are the rewards given? - await sendCodaFinishedMessage( - inventory, - inventory.Nemesis.fp, - value.nemesisName, - value.killed - ); - break; + if (value.killed) { + countedAtt = [ + { + ItemType: "/Lotus/Types/Items/MiscItems/CodaWeaponBucks", + ItemCount: getKillTokenRewardCount(inventory.Nemesis.fp) + } + ]; + addMiscItems(inventory, countedAtt); + } + } + + if (value.killed) { + await createMessage(inventory.accountOwnerId, [ + { + sndr: "/Lotus/Language/Bosses/Ordis", + msg: nemesisFactionInfo.messageBody, + arg: [ + { + Key: "LICH_NAME", + Tag: value.nemesisName + } + ], + att: att, + countedAtt: countedAtt, + attVisualOnly: true, + sub: nemesisFactionInfo.messageTitle, + icon: "/Lotus/Interface/Icons/Npcs/Ordis.png", + highPriority: true + } + ]); } inventory.Nemesis = undefined;