From 77a3b64f4938f5fd39463243068a161b82e0abcd Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sat, 17 May 2025 23:29:23 -0700 Subject: [PATCH] feat: support all nemesis manifests down to 26.0.0 (#2086) Also fixes the ephemera chance for kuva lich manifest v2 and up Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2086 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/inventoryController.ts | 7 +- src/controllers/api/nemesisController.ts | 42 ++- src/helpers/nemesisHelpers.ts | 319 ++++++++++-------- src/services/missionInventoryUpdateService.ts | 26 +- 4 files changed, 225 insertions(+), 169 deletions(-) diff --git a/src/controllers/api/inventoryController.ts b/src/controllers/api/inventoryController.ts index 97cc050b..97da2c65 100644 --- a/src/controllers/api/inventoryController.ts +++ b/src/controllers/api/inventoryController.ts @@ -24,7 +24,7 @@ import { import { logger } from "@/src/utils/logger"; import { catBreadHash } from "@/src/helpers/stringHelpers"; import { Types } from "mongoose"; -import { isNemesisCompatibleWithVersion } from "@/src/helpers/nemesisHelpers"; +import { getNemesisManifest } from "@/src/helpers/nemesisHelpers"; import { getPersonalRooms } from "@/src/services/personalRoomsService"; import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes"; import { Ship } from "@/src/models/shipModel"; @@ -308,7 +308,10 @@ export const getInventoryResponse = async ( if (buildLabel) { // Fix nemesis for older versions - if (inventoryResponse.Nemesis && !isNemesisCompatibleWithVersion(inventoryResponse.Nemesis, buildLabel)) { + if ( + inventoryResponse.Nemesis && + version_compare(getNemesisManifest(inventoryResponse.Nemesis.manifest).minBuild, buildLabel) < 0 + ) { inventoryResponse.Nemesis = undefined; } diff --git a/src/controllers/api/nemesisController.ts b/src/controllers/api/nemesisController.ts index df8e1652..9c305103 100644 --- a/src/controllers/api/nemesisController.ts +++ b/src/controllers/api/nemesisController.ts @@ -1,18 +1,18 @@ +import { version_compare } from "@/src/helpers/inventoryHelpers"; import { consumeModCharge, encodeNemesisGuess, getInfNodes, getKnifeUpgrade, + getNemesisManifest, getNemesisPasscode, getNemesisPasscodeModTypes, - getWeaponsForManifest, - IKnifeResponse, - nemesisFactionInfos + IKnifeResponse } from "@/src/helpers/nemesisHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { Loadout } from "@/src/models/inventoryModels/loadoutModel"; import { freeUpSlot, getInventory } from "@/src/services/inventoryService"; -import { getAccountIdForRequest } from "@/src/services/loginService"; +import { getAccountForRequest } from "@/src/services/loginService"; import { SRng } from "@/src/services/rngService"; import { IMongoDate, IOid } from "@/src/types/commonTypes"; import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; @@ -31,10 +31,10 @@ import { logger } from "@/src/utils/logger"; import { RequestHandler } from "express"; export const nemesisController: RequestHandler = async (req, res) => { - const accountId = await getAccountIdForRequest(req); + const account = await getAccountForRequest(req); if ((req.query.mode as string) == "f") { const body = getJSONfromString(String(req.body)); - const inventory = await getInventory(accountId, body.Category + " WeaponBin"); + const inventory = await getInventory(account._id.toString(), body.Category + " WeaponBin"); const destWeapon = inventory[body.Category].id(body.DestWeapon.$oid)!; const sourceWeapon = inventory[body.Category].id(body.SourceWeapon.$oid)!; const destFingerprint = JSON.parse(destWeapon.UpgradeFingerprint!) as IInnateDamageFingerprint; @@ -69,7 +69,7 @@ export const nemesisController: RequestHandler = async (req, res) => { } }); } else if ((req.query.mode as string) == "p") { - const inventory = await getInventory(accountId, "Nemesis"); + const inventory = await getInventory(account._id.toString(), "Nemesis"); const body = getJSONfromString(String(req.body)); const passcode = getNemesisPasscode(inventory.Nemesis!); let guessResult = 0; @@ -90,7 +90,7 @@ export const nemesisController: RequestHandler = async (req, res) => { res.json({ GuessResult: guessResult }); } else if (req.query.mode == "r") { const inventory = await getInventory( - accountId, + account._id.toString(), "Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades" ); const body = getJSONfromString(String(req.body)); @@ -144,7 +144,7 @@ export const nemesisController: RequestHandler = async (req, res) => { if (inventory.Nemesis!.HenchmenKilled >= 100) { inventory.Nemesis!.HenchmenKilled = 100; } - inventory.Nemesis!.InfNodes = getInfNodes("FC_INFESTATION", 0); + inventory.Nemesis!.InfNodes = getInfNodes(getNemesisManifest(inventory.Nemesis!.manifest), 0); await inventory.save(); res.json(response); @@ -154,25 +154,35 @@ export const nemesisController: RequestHandler = async (req, res) => { res.end(); } else { inventory.Nemesis!.Rank += 1; - inventory.Nemesis!.InfNodes = getInfNodes(inventory.Nemesis!.Faction, inventory.Nemesis!.Rank); + inventory.Nemesis!.InfNodes = getInfNodes( + getNemesisManifest(inventory.Nemesis!.manifest), + inventory.Nemesis!.Rank + ); await inventory.save(); res.json({ RankIncrease: 1 }); } } } else if ((req.query.mode as string) == "rs") { // report spawn; POST but no application data in body - const inventory = await getInventory(accountId, "Nemesis"); + const inventory = await getInventory(account._id.toString(), "Nemesis"); inventory.Nemesis!.LastEnc = inventory.Nemesis!.MissionCount; await inventory.save(); res.json({ LastEnc: inventory.Nemesis!.LastEnc }); } else if ((req.query.mode as string) == "s") { - const inventory = await getInventory(accountId, "Nemesis"); + const inventory = await getInventory(account._id.toString(), "Nemesis"); const body = getJSONfromString(String(req.body)); body.target.fp = BigInt(body.target.fp); + const manifest = getNemesisManifest(body.target.manifest); + if (account.BuildLabel && version_compare(manifest.minBuild, account.BuildLabel) < 0) { + logger.warn( + `client on version ${account.BuildLabel} provided nemesis manifest ${body.target.manifest} which was expected to require ${manifest.minBuild} or above. please file a bug report.` + ); + } + let weaponIdx = -1; if (body.target.Faction != "FC_INFESTATION") { - const weapons = getWeaponsForManifest(body.target.manifest); + const weapons: readonly string[] = manifest.weapons; const initialWeaponIdx = new SRng(body.target.fp).randomInt(0, weapons.length - 1); weaponIdx = initialWeaponIdx; do { @@ -198,7 +208,7 @@ export const nemesisController: RequestHandler = async (req, res) => { k: false, Traded: false, d: new Date(), - InfNodes: getInfNodes(body.target.Faction, 0), + InfNodes: getInfNodes(manifest, 0), GuessHistory: [], Hints: [], HintProgress: 0, @@ -216,14 +226,14 @@ export const nemesisController: RequestHandler = async (req, res) => { }); } else if ((req.query.mode as string) == "w") { const inventory = await getInventory( - accountId, + account._id.toString(), "Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades" ); //const body = getJSONfromString(String(req.body)); inventory.Nemesis!.InfNodes = [ { - Node: nemesisFactionInfos[inventory.Nemesis!.Faction].showdownNode, + Node: getNemesisManifest(inventory.Nemesis!.manifest).showdownNode, Influence: 1 } ]; diff --git a/src/helpers/nemesisHelpers.ts b/src/helpers/nemesisHelpers.ts index 015aec25..9d6a7a2b 100644 --- a/src/helpers/nemesisHelpers.ts +++ b/src/helpers/nemesisHelpers.ts @@ -7,51 +7,197 @@ import { IOid } from "../types/commonTypes"; import { Types } from "mongoose"; import { addMods, generateRewardSeed } from "../services/inventoryService"; import { isArchwingMission } from "../services/worldStateService"; -import { version_compare } from "./inventoryHelpers"; -export interface INemesisFactionInfo { - systemIndexes: number[]; +type TInnateDamageTag = + | "InnateElectricityDamage" + | "InnateHeatDamage" + | "InnateFreezeDamage" + | "InnateToxinDamage" + | "InnateMagDamage" + | "InnateRadDamage" + | "InnateImpactDamage"; + +export interface INemesisManifest { + weapons: readonly string[]; + systemIndexes: readonly number[]; showdownNode: string; ephemeraChance: number; + ephemeraTypes?: Record; firstKillReward: string; firstConvertReward: string; messageTitle: string; messageBody: string; + minBuild: 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" +class KuvaLichManifest implements INemesisManifest { + weapons = [ + "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Drakgoon/KuvaDrakgoon", + "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Karak/KuvaKarak", + "/Lotus/Weapons/Grineer/Melee/GrnKuvaLichScythe/GrnKuvaLichScytheWeapon", + "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Kohm/KuvaKohm", + "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Ogris/KuvaOgris", + "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Quartakk/KuvaQuartakk", + "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Tonkor/KuvaTonkor", + "/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Brakk/KuvaBrakk", + "/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Kraken/KuvaKraken", + "/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Seer/KuvaSeer", + "/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Stubba/KuvaStubba", + "/Lotus/Weapons/Grineer/HeavyWeapons/GrnHeavyGrenadeLauncher", + "/Lotus/Weapons/Grineer/LongGuns/GrnKuvaLichRifle/GrnKuvaLichRifleWeapon" + ]; + systemIndexes = [2, 3, 9, 11, 18]; + showdownNode = "CrewBattleNode557"; + ephemeraChance = 0.05; + ephemeraTypes = { + InnateElectricityDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaLightningEphemera", + InnateHeatDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaFireEphemera", + InnateFreezeDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaIceEphemera", + InnateToxinDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaToxinEphemera", + InnateMagDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaMagneticEphemera", + InnateRadDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaTricksterEphemera", + InnateImpactDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaImpactEphemera" + }; + firstKillReward = "/Lotus/StoreItems/Upgrades/Skins/Clan/LichKillerBadgeItem"; + firstConvertReward = "/Lotus/StoreItems/Upgrades/Skins/Sigils/KuvaLichSigil"; + messageTitle = "/Lotus/Language/Inbox/VanquishKuvaMsgTitle"; + messageBody = "/Lotus/Language/Inbox/VanquishLichMsgBody"; + minBuild = "2019.10.31.22.42"; // 26.0.0 +} + +class KuvaLichManifestVersionTwo extends KuvaLichManifest { + constructor() { + super(); + this.ephemeraChance = 0.1; + this.minBuild = "2020.03.05.16.06"; // Unsure about this one, so using the same value as in version three. } +} + +class KuvaLichManifestVersionThree extends KuvaLichManifestVersionTwo { + constructor() { + super(); + this.weapons.push("/Lotus/Weapons/Grineer/Bows/GrnBow/GrnBowWeapon"); + this.weapons.push("/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hind/KuvaHind"); + this.weapons.push("/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Nukor/KuvaNukor"); + this.ephemeraChance = 0.2; + this.minBuild = "2020.03.05.16.06"; // This is 27.2.0, tho 27.1.0 should also recognise this. + } +} + +class KuvaLichManifestVersionFour extends KuvaLichManifestVersionThree { + constructor() { + super(); + this.minBuild = "2021.07.05.17.03"; // Unsure about this one, so using the same value as in version five. + } +} + +class KuvaLichManifestVersionFive extends KuvaLichManifestVersionFour { + constructor() { + super(); + this.weapons.push("/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hek/KuvaHekWeapon"); + this.weapons.push("/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Zarr/KuvaZarr"); + this.weapons.push("/Lotus/Weapons/Grineer/KuvaLich/HeavyWeapons/Grattler/KuvaGrattler"); + this.minBuild = "2021.07.05.17.03"; // 30.5.0 + } +} + +class KuvaLichManifestVersionSix extends KuvaLichManifestVersionFive { + constructor() { + super(); + this.weapons.push("/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Sobek/KuvaSobek"); + this.minBuild = "2024.05.15.11.07"; // 35.6.0 + } +} + +class LawyerManifest implements INemesisManifest { + weapons = [ + "/Lotus/Weapons/Corpus/LongGuns/CrpBriefcaseLauncher/CrpBriefcaseLauncher", + "/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEArcaPlasmor/CrpBEArcaPlasmor", + "/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEFluxRifle/CrpBEFluxRifle", + "/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBETetra/CrpBETetra", + "/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBECycron/CrpBECycron", + "/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEDetron/CrpBEDetron", + "/Lotus/Weapons/Corpus/Pistols/CrpIgniterPistol/CrpIgniterPistol", + "/Lotus/Weapons/Corpus/Pistols/CrpBriefcaseAkimbo/CrpBriefcaseAkimboPistol" + ]; + systemIndexes = [1, 15, 4, 7, 8]; + showdownNode = "CrewBattleNode558"; + ephemeraChance = 0.2; + ephemeraTypes = { + InnateElectricityDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraA", + InnateHeatDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraB", + InnateFreezeDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraC", + InnateToxinDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraD", + InnateMagDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraE", + InnateRadDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraF", + InnateImpactDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraG" + }; + firstKillReward = "/Lotus/StoreItems/Upgrades/Skins/Clan/CorpusLichBadgeItem"; + firstConvertReward = "/Lotus/StoreItems/Upgrades/Skins/Sigils/CorpusLichSigil"; + messageTitle = "/Lotus/Language/Inbox/VanquishLawyerMsgTitle"; + messageBody = "/Lotus/Language/Inbox/VanquishLichMsgBody"; + minBuild = "2021.07.05.17.03"; // 30.5.0 +} + +class LawyerManifestVersionTwo extends LawyerManifest { + constructor() { + super(); + this.weapons.push("/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEPlinx/CrpBEPlinxWeapon"); + this.minBuild = "2022.11.30.08.13"; // 32.2.0 + } +} + +class LawyerManifestVersionThree extends LawyerManifestVersionTwo { + constructor() { + super(); + this.weapons.push("/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEGlaxion/CrpBEGlaxion"); + this.minBuild = "2024.05.15.11.07"; // 35.6.0 + } +} + +class LawyerManifestVersionFour extends LawyerManifestVersionThree { + constructor() { + super(); + this.minBuild = "2024.10.01.11.03"; // 37.0.0 + } +} + +class InfestedLichManfest implements INemesisManifest { + weapons = []; + 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"; + minBuild = "2025.03.18.09.51"; // 38.5.0 +} + +const nemesisManifests: Record = { + "/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifest": new KuvaLichManifest(), + "/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionTwo": new KuvaLichManifestVersionTwo(), + "/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionThree": new KuvaLichManifestVersionThree(), + "/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionFour": new KuvaLichManifestVersionFour(), + "/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionFive": new KuvaLichManifestVersionFive(), + "/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionSix": new KuvaLichManifestVersionSix(), + "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifest": new LawyerManifest(), + "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionTwo": new LawyerManifestVersionTwo(), + "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionThree": new LawyerManifestVersionThree(), + "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionFour": new LawyerManifestVersionFour(), + "/Lotus/Types/Enemies/InfestedLich/InfestedLichManifest": new InfestedLichManfest() }; -export const getInfNodes = (faction: TNemesisFaction, rank: number): IInfNode[] => { +export const getNemesisManifest = (manifest: string): INemesisManifest => { + if (manifest in nemesisManifests) { + return nemesisManifests[manifest]; + } + throw new Error(`unknown nemesis manifest: ${manifest}`); +}; + +export const getInfNodes = (manifest: INemesisManifest, rank: number): IInfNode[] => { const infNodes = []; - const systemIndex = nemesisFactionInfos[faction].systemIndexes[rank]; + const systemIndex = manifest.systemIndexes[rank]; for (const [key, value] of Object.entries(ExportRegions)) { if ( value.systemIndex === systemIndex && @@ -73,36 +219,6 @@ export const getInfNodes = (faction: TNemesisFaction, rank: number): IInfNode[] return infNodes; }; -type TInnateDamageTag = - | "InnateElectricityDamage" - | "InnateHeatDamage" - | "InnateFreezeDamage" - | "InnateToxinDamage" - | "InnateMagDamage" - | "InnateRadDamage" - | "InnateImpactDamage"; - -const ephmeraTypes: Record<"FC_GRINEER" | "FC_CORPUS", Record> = { - FC_GRINEER: { - InnateElectricityDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaLightningEphemera", - InnateHeatDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaFireEphemera", - InnateFreezeDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaIceEphemera", - InnateToxinDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaToxinEphemera", - InnateMagDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaMagneticEphemera", - InnateRadDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaTricksterEphemera", - InnateImpactDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaImpactEphemera" - }, - FC_CORPUS: { - InnateElectricityDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraA", - InnateHeatDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraB", - InnateFreezeDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraC", - InnateToxinDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraD", - InnateMagDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraE", - InnateRadDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraF", - InnateImpactDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraG" - } -}; - // Get a parazon 'passcode' based on the nemesis fingerprint so it's always the same for the same nemesis. export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: TNemesisFaction }): number[] => { const rng = new SRng(nemesis.fp); @@ -256,77 +372,6 @@ export const consumeModCharge = ( } }; -const kuvaLichVersionSixWeapons = [ - "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Drakgoon/KuvaDrakgoon", - "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Karak/KuvaKarak", - "/Lotus/Weapons/Grineer/Melee/GrnKuvaLichScythe/GrnKuvaLichScytheWeapon", - "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Kohm/KuvaKohm", - "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Ogris/KuvaOgris", - "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Quartakk/KuvaQuartakk", - "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Tonkor/KuvaTonkor", - "/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Brakk/KuvaBrakk", - "/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Kraken/KuvaKraken", - "/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Seer/KuvaSeer", - "/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Stubba/KuvaStubba", - "/Lotus/Weapons/Grineer/HeavyWeapons/GrnHeavyGrenadeLauncher", - "/Lotus/Weapons/Grineer/LongGuns/GrnKuvaLichRifle/GrnKuvaLichRifleWeapon", - "/Lotus/Weapons/Grineer/Bows/GrnBow/GrnBowWeapon", - "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hind/KuvaHind", - "/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Nukor/KuvaNukor", - "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hek/KuvaHekWeapon", - "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Zarr/KuvaZarr", - "/Lotus/Weapons/Grineer/KuvaLich/HeavyWeapons/Grattler/KuvaGrattler", - "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Sobek/KuvaSobek" -]; - -const corpusVersionThreeWeapons = [ - "/Lotus/Weapons/Corpus/LongGuns/CrpBriefcaseLauncher/CrpBriefcaseLauncher", - "/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEArcaPlasmor/CrpBEArcaPlasmor", - "/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEFluxRifle/CrpBEFluxRifle", - "/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBETetra/CrpBETetra", - "/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBECycron/CrpBECycron", - "/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEDetron/CrpBEDetron", - "/Lotus/Weapons/Corpus/Pistols/CrpIgniterPistol/CrpIgniterPistol", - "/Lotus/Weapons/Corpus/Pistols/CrpBriefcaseAkimbo/CrpBriefcaseAkimboPistol", - "/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEPlinx/CrpBEPlinxWeapon", - "/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEGlaxion/CrpBEGlaxion" -]; - -export const getWeaponsForManifest = (manifest: string): readonly string[] => { - switch (manifest) { - case "/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionSix": // >= 35.6.0 - return kuvaLichVersionSixWeapons; - case "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionThree": // >= 35.6.0 - case "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionFour": // >= 37.0.0 - return corpusVersionThreeWeapons; - } - throw new Error(`unknown nemesis manifest: ${manifest}`); -}; - -export const isNemesisCompatibleWithVersion = ( - nemesis: { manifest: string; Faction: TNemesisFaction }, - buildLabel: string -): boolean => { - // Anything below 35.6.0 is not going to be okay given our set of supported manifests. - if (version_compare(buildLabel, "2024.05.15.11.07") < 0) { - return false; - } - - if (nemesis.Faction == "FC_INFESTATION") { - // Anything below 38.5.0 isn't gonna like an infested lich. - if (version_compare(buildLabel, "2025.03.18.16.07") < 0) { - return false; - } - } else if (nemesis.manifest == "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionFour") { - // Anything below 37.0.0 isn't gonna know version 4, but version 3 is identical in terms of weapon choices, so we can spoof it to that. - if (version_compare(buildLabel, "2024.10.01.11.03") < 0) { - nemesis.manifest = "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionThree"; - } - } - - return true; -}; - export const getInnateDamageTag = (KillingSuit: string): TInnateDamageTag => { return ExportWarframes[KillingSuit].nemesisUpgradeTag!; }; @@ -349,7 +394,7 @@ export interface INemesisProfile { export const generateNemesisProfile = ( fp: bigint = generateRewardSeed(), - Faction: TNemesisFaction = "FC_CORPUS", + manifest: INemesisManifest = new LawyerManifest(), killingSuit: string = "/Lotus/Powersuits/Ember/Ember" ): INemesisProfile => { const rng = new SRng(fp); @@ -363,11 +408,11 @@ 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() <= nemesisFactionInfos[Faction].ephemeraChance && Faction != "FC_INFESTATION") { - profile.ephemera = ephmeraTypes[Faction][profile.innateDamageTag]; + if (rng.randomFloat() <= manifest.ephemeraChance && manifest.ephemeraTypes) { + profile.ephemera = manifest.ephemeraTypes[profile.innateDamageTag]; } rng.randomFloat(); // something related to sentinel agent maybe - if (Faction == "FC_CORPUS") { + if (manifest instanceof LawyerManifest) { profile.petHead = rng.randomElement(petHeads)!; profile.petBody = rng.randomElement([ "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyA", diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index e577a486..12aa3f53 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -61,9 +61,8 @@ import { getInfestedLichItemRewards, getInfNodes, getKillTokenRewardCount, - getNemesisPasscode, - getWeaponsForManifest, - nemesisFactionInfos + getNemesisManifest, + getNemesisPasscode } from "@/src/helpers/nemesisHelpers"; import { Loadout } from "../models/inventoryModels/loadoutModel"; import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes"; @@ -662,12 +661,12 @@ export const addMissionInventoryUpdates = async ( k: value.killed }); + const manifest = getNemesisManifest(inventory.Nemesis.manifest); const profile = generateNemesisProfile( inventory.Nemesis.fp, - inventory.Nemesis.Faction, + manifest, inventory.Nemesis.KillingSuit ); - const nemesisFactionInfo = nemesisFactionInfos[inventory.Nemesis.Faction]; const att: string[] = []; let countedAtt: ITypeCount[] | undefined; @@ -676,9 +675,7 @@ export const addMissionInventoryUpdates = async ( 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 - ]; + const weaponType = manifest.weapons[inventory.Nemesis.WeaponIdx]; giveNemesisWeaponRecipe(inventory, weaponType, value.nemesisName, value.weaponLoc, profile); att.push(weaponType); } @@ -704,9 +701,7 @@ export const addMissionInventoryUpdates = async ( att.push(profile.ephemera); } - const skinRewardStoreItem = value.killed - ? nemesisFactionInfo.firstKillReward - : nemesisFactionInfo.firstConvertReward; + const skinRewardStoreItem = value.killed ? manifest.firstKillReward : manifest.firstConvertReward; if (Object.keys(addSkin(inventory, fromStoreItem(skinRewardStoreItem))).length != 0) { att.push(skinRewardStoreItem); } @@ -737,7 +732,7 @@ export const addMissionInventoryUpdates = async ( await createMessage(inventory.accountOwnerId, [ { sndr: "/Lotus/Language/Bosses/Ordis", - msg: nemesisFactionInfo.messageBody, + msg: manifest.messageBody, arg: [ { Key: "LICH_NAME", @@ -747,7 +742,7 @@ export const addMissionInventoryUpdates = async ( att: att, countedAtt: countedAtt, attVisualOnly: true, - sub: nemesisFactionInfo.messageTitle, + sub: manifest.messageTitle, icon: "/Lotus/Interface/Icons/Npcs/Ordis.png", highPriority: true } @@ -1187,7 +1182,10 @@ export const addMissionRewards = async ( inventory.Nemesis.Rank = Math.min(inventory.Nemesis.Rank + 1, 4); inventoryChanges.Nemesis.Rank = inventory.Nemesis.Rank; } - inventory.Nemesis.InfNodes = getInfNodes(inventory.Nemesis.Faction, inventory.Nemesis.Rank); + inventory.Nemesis.InfNodes = getInfNodes( + getNemesisManifest(inventory.Nemesis.manifest), + inventory.Nemesis.Rank + ); } if (inventory.Nemesis.Faction == "FC_INFESTATION") {