feat: support all nemesis manifests down to 26.0.0
All checks were successful
Build / build (push) Successful in 1m49s
Build / build (pull_request) Successful in 1m46s

Also fixes the ephemera chance for kuva lich manifest v3 and up
This commit is contained in:
Sainan 2025-05-17 09:00:44 +02:00
parent 870ff2dd2c
commit 32c9682e73
4 changed files with 211 additions and 161 deletions

View File

@ -24,7 +24,7 @@ import {
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { catBreadHash } from "@/src/helpers/stringHelpers"; import { catBreadHash } from "@/src/helpers/stringHelpers";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { isNemesisCompatibleWithVersion } from "@/src/helpers/nemesisHelpers"; import { getNemesisManifest } from "@/src/helpers/nemesisHelpers";
import { getPersonalRooms } from "@/src/services/personalRoomsService"; import { getPersonalRooms } from "@/src/services/personalRoomsService";
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes"; import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
import { Ship } from "@/src/models/shipModel"; import { Ship } from "@/src/models/shipModel";
@ -308,7 +308,10 @@ export const getInventoryResponse = async (
if (buildLabel) { if (buildLabel) {
// Fix nemesis for older versions // 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; inventoryResponse.Nemesis = undefined;
} }

View File

@ -3,11 +3,10 @@ import {
encodeNemesisGuess, encodeNemesisGuess,
getInfNodes, getInfNodes,
getKnifeUpgrade, getKnifeUpgrade,
getNemesisManifest,
getNemesisPasscode, getNemesisPasscode,
getNemesisPasscodeModTypes, getNemesisPasscodeModTypes,
getWeaponsForManifest, IKnifeResponse
IKnifeResponse,
nemesisFactionInfos
} from "@/src/helpers/nemesisHelpers"; } from "@/src/helpers/nemesisHelpers";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Loadout } from "@/src/models/inventoryModels/loadoutModel"; import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
@ -144,7 +143,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
if (inventory.Nemesis!.HenchmenKilled >= 100) { if (inventory.Nemesis!.HenchmenKilled >= 100) {
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(); await inventory.save();
res.json(response); res.json(response);
@ -154,7 +153,10 @@ export const nemesisController: RequestHandler = async (req, res) => {
res.end(); res.end();
} else { } else {
inventory.Nemesis!.Rank += 1; 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(); await inventory.save();
res.json({ RankIncrease: 1 }); res.json({ RankIncrease: 1 });
} }
@ -170,9 +172,11 @@ export const nemesisController: RequestHandler = async (req, res) => {
const body = getJSONfromString<INemesisStartRequest>(String(req.body)); const body = getJSONfromString<INemesisStartRequest>(String(req.body));
body.target.fp = BigInt(body.target.fp); body.target.fp = BigInt(body.target.fp);
const manifest = getNemesisManifest(body.target.manifest);
let weaponIdx = -1; let weaponIdx = -1;
if (body.target.Faction != "FC_INFESTATION") { 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); const initialWeaponIdx = new SRng(body.target.fp).randomInt(0, weapons.length - 1);
weaponIdx = initialWeaponIdx; weaponIdx = initialWeaponIdx;
do { do {
@ -198,7 +202,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
k: false, k: false,
Traded: false, Traded: false,
d: new Date(), d: new Date(),
InfNodes: getInfNodes(body.target.Faction, 0), InfNodes: getInfNodes(manifest, 0),
GuessHistory: [], GuessHistory: [],
Hints: [], Hints: [],
HintProgress: 0, HintProgress: 0,
@ -223,7 +227,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
inventory.Nemesis!.InfNodes = [ inventory.Nemesis!.InfNodes = [
{ {
Node: nemesisFactionInfos[inventory.Nemesis!.Faction].showdownNode, Node: getNemesisManifest(inventory.Nemesis!.manifest).showdownNode,
Influence: 1 Influence: 1
} }
]; ];

View File

@ -7,51 +7,197 @@ import { IOid } from "../types/commonTypes";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { addMods, generateRewardSeed } from "../services/inventoryService"; import { addMods, generateRewardSeed } from "../services/inventoryService";
import { isArchwingMission } from "../services/worldStateService"; import { isArchwingMission } from "../services/worldStateService";
import { version_compare } from "./inventoryHelpers";
export interface INemesisFactionInfo { type TInnateDamageTag =
systemIndexes: number[]; | "InnateElectricityDamage"
| "InnateHeatDamage"
| "InnateFreezeDamage"
| "InnateToxinDamage"
| "InnateMagDamage"
| "InnateRadDamage"
| "InnateImpactDamage";
export interface INemesisManifest {
weapons: readonly string[];
systemIndexes: readonly number[];
showdownNode: string; showdownNode: string;
ephemeraChance: number; ephemeraChance: number;
ephemeraTypes?: Record<TInnateDamageTag, string>;
firstKillReward: string; firstKillReward: string;
firstConvertReward: string; firstConvertReward: string;
messageTitle: string; messageTitle: string;
messageBody: string; messageBody: string;
minBuild: string;
} }
export const nemesisFactionInfos: Record<TNemesisFaction, INemesisFactionInfo> = { class KuvaLichManifest implements INemesisManifest {
FC_GRINEER: { weapons = [
systemIndexes: [2, 3, 9, 11, 18], "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Drakgoon/KuvaDrakgoon",
showdownNode: "CrewBattleNode557", "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Karak/KuvaKarak",
ephemeraChance: 0.05, "/Lotus/Weapons/Grineer/Melee/GrnKuvaLichScythe/GrnKuvaLichScytheWeapon",
firstKillReward: "/Lotus/StoreItems/Upgrades/Skins/Clan/LichKillerBadgeItem", "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Kohm/KuvaKohm",
firstConvertReward: "/Lotus/StoreItems/Upgrades/Skins/Sigils/KuvaLichSigil", "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Ogris/KuvaOgris",
messageTitle: "/Lotus/Language/Inbox/VanquishKuvaMsgTitle", "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Quartakk/KuvaQuartakk",
messageBody: "/Lotus/Language/Inbox/VanquishLichMsgBody" "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Tonkor/KuvaTonkor",
}, "/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Brakk/KuvaBrakk",
FC_CORPUS: { "/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Kraken/KuvaKraken",
systemIndexes: [1, 15, 4, 7, 8], "/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Seer/KuvaSeer",
showdownNode: "CrewBattleNode558", "/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Stubba/KuvaStubba",
ephemeraChance: 0.2, "/Lotus/Weapons/Grineer/HeavyWeapons/GrnHeavyGrenadeLauncher",
firstKillReward: "/Lotus/StoreItems/Upgrades/Skins/Clan/CorpusLichBadgeItem", "/Lotus/Weapons/Grineer/LongGuns/GrnKuvaLichRifle/GrnKuvaLichRifleWeapon"
firstConvertReward: "/Lotus/StoreItems/Upgrades/Skins/Sigils/CorpusLichSigil", ];
messageTitle: "/Lotus/Language/Inbox/VanquishLawyerMsgTitle", systemIndexes = [2, 3, 9, 11, 18];
messageBody: "/Lotus/Language/Inbox/VanquishLichMsgBody" showdownNode = "CrewBattleNode557";
}, ephemeraChance = 0.05;
FC_INFESTATION: { ephemeraTypes = {
systemIndexes: [23], InnateElectricityDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaLightningEphemera",
showdownNode: "CrewBattleNode559", InnateHeatDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaFireEphemera",
ephemeraChance: 0, InnateFreezeDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaIceEphemera",
firstKillReward: "/Lotus/StoreItems/Upgrades/Skins/Sigils/InfLichVanquishedSigil", InnateToxinDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaToxinEphemera",
firstConvertReward: "/Lotus/StoreItems/Upgrades/Skins/Sigils/InfLichConvertedSigil", InnateMagDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaMagneticEphemera",
messageTitle: "/Lotus/Language/Inbox/VanquishBandMsgTitle", InnateRadDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaTricksterEphemera",
messageBody: "/Lotus/Language/Inbox/VanquishBandMsgBody" 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<string, INemesisManifest> = {
"/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 infNodes = [];
const systemIndex = nemesisFactionInfos[faction].systemIndexes[rank]; const systemIndex = manifest.systemIndexes[rank];
for (const [key, value] of Object.entries(ExportRegions)) { for (const [key, value] of Object.entries(ExportRegions)) {
if ( if (
value.systemIndex === systemIndex && value.systemIndex === systemIndex &&
@ -73,36 +219,6 @@ export const getInfNodes = (faction: TNemesisFaction, rank: number): IInfNode[]
return infNodes; return infNodes;
}; };
type TInnateDamageTag =
| "InnateElectricityDamage"
| "InnateHeatDamage"
| "InnateFreezeDamage"
| "InnateToxinDamage"
| "InnateMagDamage"
| "InnateRadDamage"
| "InnateImpactDamage";
const ephmeraTypes: Record<"FC_GRINEER" | "FC_CORPUS", Record<TInnateDamageTag, string>> = {
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. // 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[] => { export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: TNemesisFaction }): number[] => {
const rng = new SRng(nemesis.fp); 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 => { export const getInnateDamageTag = (KillingSuit: string): TInnateDamageTag => {
return ExportWarframes[KillingSuit].nemesisUpgradeTag!; return ExportWarframes[KillingSuit].nemesisUpgradeTag!;
}; };
@ -349,7 +394,7 @@ export interface INemesisProfile {
export const generateNemesisProfile = ( export const generateNemesisProfile = (
fp: bigint = generateRewardSeed(), fp: bigint = generateRewardSeed(),
Faction: TNemesisFaction = "FC_CORPUS", manifest: INemesisManifest = new LawyerManifest(),
killingSuit: string = "/Lotus/Powersuits/Ember/Ember" killingSuit: string = "/Lotus/Powersuits/Ember/Ember"
): INemesisProfile => { ): INemesisProfile => {
const rng = new SRng(fp); const rng = new SRng(fp);
@ -363,11 +408,11 @@ export const generateNemesisProfile = (
innateDamageTag: getInnateDamageTag(killingSuit), innateDamageTag: getInnateDamageTag(killingSuit),
innateDamageValue: Math.trunc(value * 0x40000000) // TODO: For -1399275245665749231n, the value should be 75306944, but we're off by 59 with 75307003. 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") { if (rng.randomFloat() <= manifest.ephemeraChance && manifest.ephemeraTypes) {
profile.ephemera = ephmeraTypes[Faction][profile.innateDamageTag]; profile.ephemera = manifest.ephemeraTypes[profile.innateDamageTag];
} }
rng.randomFloat(); // something related to sentinel agent maybe rng.randomFloat(); // something related to sentinel agent maybe
if (Faction == "FC_CORPUS") { if (manifest instanceof LawyerManifest) {
profile.petHead = rng.randomElement(petHeads)!; profile.petHead = rng.randomElement(petHeads)!;
profile.petBody = rng.randomElement([ profile.petBody = rng.randomElement([
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyA", "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyA",

View File

@ -61,9 +61,8 @@ import {
getInfestedLichItemRewards, getInfestedLichItemRewards,
getInfNodes, getInfNodes,
getKillTokenRewardCount, getKillTokenRewardCount,
getNemesisPasscode, getNemesisManifest,
getWeaponsForManifest, getNemesisPasscode
nemesisFactionInfos
} from "@/src/helpers/nemesisHelpers"; } from "@/src/helpers/nemesisHelpers";
import { Loadout } from "../models/inventoryModels/loadoutModel"; import { Loadout } from "../models/inventoryModels/loadoutModel";
import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes"; import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes";
@ -662,12 +661,12 @@ export const addMissionInventoryUpdates = async (
k: value.killed k: value.killed
}); });
const manifest = getNemesisManifest(inventory.Nemesis.manifest);
const profile = generateNemesisProfile( const profile = generateNemesisProfile(
inventory.Nemesis.fp, inventory.Nemesis.fp,
inventory.Nemesis.Faction, manifest,
inventory.Nemesis.KillingSuit inventory.Nemesis.KillingSuit
); );
const nemesisFactionInfo = nemesisFactionInfos[inventory.Nemesis.Faction];
const att: string[] = []; const att: string[] = [];
let countedAtt: ITypeCount[] | undefined; let countedAtt: ITypeCount[] | undefined;
@ -676,9 +675,7 @@ export const addMissionInventoryUpdates = async (
value.weaponLoc && value.weaponLoc &&
inventory.Nemesis.Faction != "FC_INFESTATION" // weaponLoc is "/Lotus/Language/Weapons/DerelictCernosName" for these for some reason inventory.Nemesis.Faction != "FC_INFESTATION" // weaponLoc is "/Lotus/Language/Weapons/DerelictCernosName" for these for some reason
) { ) {
const weaponType = getWeaponsForManifest(inventory.Nemesis.manifest)[ const weaponType = manifest.weapons[inventory.Nemesis.WeaponIdx];
inventory.Nemesis.WeaponIdx
];
giveNemesisWeaponRecipe(inventory, weaponType, value.nemesisName, value.weaponLoc, profile); giveNemesisWeaponRecipe(inventory, weaponType, value.nemesisName, value.weaponLoc, profile);
att.push(weaponType); att.push(weaponType);
} }
@ -704,9 +701,7 @@ export const addMissionInventoryUpdates = async (
att.push(profile.ephemera); att.push(profile.ephemera);
} }
const skinRewardStoreItem = value.killed const skinRewardStoreItem = value.killed ? manifest.firstKillReward : manifest.firstConvertReward;
? nemesisFactionInfo.firstKillReward
: nemesisFactionInfo.firstConvertReward;
if (Object.keys(addSkin(inventory, fromStoreItem(skinRewardStoreItem))).length != 0) { if (Object.keys(addSkin(inventory, fromStoreItem(skinRewardStoreItem))).length != 0) {
att.push(skinRewardStoreItem); att.push(skinRewardStoreItem);
} }
@ -737,7 +732,7 @@ export const addMissionInventoryUpdates = async (
await createMessage(inventory.accountOwnerId, [ await createMessage(inventory.accountOwnerId, [
{ {
sndr: "/Lotus/Language/Bosses/Ordis", sndr: "/Lotus/Language/Bosses/Ordis",
msg: nemesisFactionInfo.messageBody, msg: manifest.messageBody,
arg: [ arg: [
{ {
Key: "LICH_NAME", Key: "LICH_NAME",
@ -747,7 +742,7 @@ export const addMissionInventoryUpdates = async (
att: att, att: att,
countedAtt: countedAtt, countedAtt: countedAtt,
attVisualOnly: true, attVisualOnly: true,
sub: nemesisFactionInfo.messageTitle, sub: manifest.messageTitle,
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png", icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
highPriority: true highPriority: true
} }
@ -1187,7 +1182,10 @@ export const addMissionRewards = async (
inventory.Nemesis.Rank = Math.min(inventory.Nemesis.Rank + 1, 4); inventory.Nemesis.Rank = Math.min(inventory.Nemesis.Rank + 1, 4);
inventoryChanges.Nemesis.Rank = inventory.Nemesis.Rank; 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") { if (inventory.Nemesis.Faction == "FC_INFESTATION") {