XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESpaceNinjaServer

A simple server for a small space ninja game

公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/XFESpaceNinjaServer

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>

77a3b64f
Sainan <63328889+Sainan@users.noreply.github.com>
提交于

代码差异

4 个文件 +225 -169
Modified src/controllers/api/inventoryController.ts +5 -2
@@ -24,7 +24,7 @@ import {
24 24 import { logger } from "@/src/utils/logger";
25 25 import { catBreadHash } from "@/src/helpers/stringHelpers";
26 26 import { Types } from "mongoose";
27 import { isNemesisCompatibleWithVersion } from "@/src/helpers/nemesisHelpers";
27 import { getNemesisManifest } from "@/src/helpers/nemesisHelpers";
28 28 import { getPersonalRooms } from "@/src/services/personalRoomsService";
29 29 import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
30 30 import { Ship } from "@/src/models/shipModel";
@@ -308,7 +308,10 @@ export const getInventoryResponse = async (
308 308
309 309 if (buildLabel) {
310 310 // Fix nemesis for older versions
311 if (inventoryResponse.Nemesis && !isNemesisCompatibleWithVersion(inventoryResponse.Nemesis, buildLabel)) {
311 if (
312 inventoryResponse.Nemesis &&
313 version_compare(getNemesisManifest(inventoryResponse.Nemesis.manifest).minBuild, buildLabel) < 0
314 ) {
312 315 inventoryResponse.Nemesis = undefined;
313 316 }
314 317
Modified src/controllers/api/nemesisController.ts +26 -16
@@ -1,18 +1,18 @@
1 import { version_compare } from "@/src/helpers/inventoryHelpers";
1 2 import {
2 3 consumeModCharge,
3 4 encodeNemesisGuess,
4 5 getInfNodes,
5 6 getKnifeUpgrade,
7 getNemesisManifest,
6 8 getNemesisPasscode,
7 9 getNemesisPasscodeModTypes,
8 getWeaponsForManifest,
9 IKnifeResponse,
10 nemesisFactionInfos
10 IKnifeResponse
11 11 } from "@/src/helpers/nemesisHelpers";
12 12 import { getJSONfromString } from "@/src/helpers/stringHelpers";
13 13 import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
14 14 import { freeUpSlot, getInventory } from "@/src/services/inventoryService";
15 import { getAccountIdForRequest } from "@/src/services/loginService";
15 import { getAccountForRequest } from "@/src/services/loginService";
16 16 import { SRng } from "@/src/services/rngService";
17 17 import { IMongoDate, IOid } from "@/src/types/commonTypes";
18 18 import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
@@ -31,10 +31,10 @@ import { logger } from "@/src/utils/logger";
31 31 import { RequestHandler } from "express";
32 32
33 33 export const nemesisController: RequestHandler = async (req, res) => {
34 const accountId = await getAccountIdForRequest(req);
34 const account = await getAccountForRequest(req);
35 35 if ((req.query.mode as string) == "f") {
36 36 const body = getJSONfromString<IValenceFusionRequest>(String(req.body));
37 const inventory = await getInventory(accountId, body.Category + " WeaponBin");
37 const inventory = await getInventory(account._id.toString(), body.Category + " WeaponBin");
38 38 const destWeapon = inventory[body.Category].id(body.DestWeapon.$oid)!;
39 39 const sourceWeapon = inventory[body.Category].id(body.SourceWeapon.$oid)!;
40 40 const destFingerprint = JSON.parse(destWeapon.UpgradeFingerprint!) as IInnateDamageFingerprint;
@@ -69,7 +69,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
69 69 }
70 70 });
71 71 } else if ((req.query.mode as string) == "p") {
72 const inventory = await getInventory(accountId, "Nemesis");
72 const inventory = await getInventory(account._id.toString(), "Nemesis");
73 73 const body = getJSONfromString<INemesisPrespawnCheckRequest>(String(req.body));
74 74 const passcode = getNemesisPasscode(inventory.Nemesis!);
75 75 let guessResult = 0;
@@ -90,7 +90,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
90 90 res.json({ GuessResult: guessResult });
91 91 } else if (req.query.mode == "r") {
92 92 const inventory = await getInventory(
93 accountId,
93 account._id.toString(),
94 94 "Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades"
95 95 );
96 96 const body = getJSONfromString<INemesisRequiemRequest>(String(req.body));
@@ -144,7 +144,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
144 144 if (inventory.Nemesis!.HenchmenKilled >= 100) {
145 145 inventory.Nemesis!.HenchmenKilled = 100;
146 146 }
147 inventory.Nemesis!.InfNodes = getInfNodes("FC_INFESTATION", 0);
147 inventory.Nemesis!.InfNodes = getInfNodes(getNemesisManifest(inventory.Nemesis!.manifest), 0);
148 148
149 149 await inventory.save();
150 150 res.json(response);
@@ -154,25 +154,35 @@ export const nemesisController: RequestHandler = async (req, res) => {
154 154 res.end();
155 155 } else {
156 156 inventory.Nemesis!.Rank += 1;
157 inventory.Nemesis!.InfNodes = getInfNodes(inventory.Nemesis!.Faction, inventory.Nemesis!.Rank);
157 inventory.Nemesis!.InfNodes = getInfNodes(
158 getNemesisManifest(inventory.Nemesis!.manifest),
159 inventory.Nemesis!.Rank
160 );
158 161 await inventory.save();
159 162 res.json({ RankIncrease: 1 });
160 163 }
161 164 }
162 165 } else if ((req.query.mode as string) == "rs") {
163 166 // report spawn; POST but no application data in body
164 const inventory = await getInventory(accountId, "Nemesis");
167 const inventory = await getInventory(account._id.toString(), "Nemesis");
165 168 inventory.Nemesis!.LastEnc = inventory.Nemesis!.MissionCount;
166 169 await inventory.save();
167 170 res.json({ LastEnc: inventory.Nemesis!.LastEnc });
168 171 } else if ((req.query.mode as string) == "s") {
169 const inventory = await getInventory(accountId, "Nemesis");
172 const inventory = await getInventory(account._id.toString(), "Nemesis");
170 173 const body = getJSONfromString<INemesisStartRequest>(String(req.body));
171 174 body.target.fp = BigInt(body.target.fp);
172 175
176 const manifest = getNemesisManifest(body.target.manifest);
177 if (account.BuildLabel && version_compare(manifest.minBuild, account.BuildLabel) < 0) {
178 logger.warn(
179 `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.`
180 );
181 }
182
173 183 let weaponIdx = -1;
174 184 if (body.target.Faction != "FC_INFESTATION") {
175 const weapons = getWeaponsForManifest(body.target.manifest);
185 const weapons: readonly string[] = manifest.weapons;
176 186 const initialWeaponIdx = new SRng(body.target.fp).randomInt(0, weapons.length - 1);
177 187 weaponIdx = initialWeaponIdx;
178 188 do {
@@ -198,7 +208,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
198 208 k: false,
199 209 Traded: false,
200 210 d: new Date(),
201 InfNodes: getInfNodes(body.target.Faction, 0),
211 InfNodes: getInfNodes(manifest, 0),
202 212 GuessHistory: [],
203 213 Hints: [],
204 214 HintProgress: 0,
@@ -216,14 +226,14 @@ export const nemesisController: RequestHandler = async (req, res) => {
216 226 });
217 227 } else if ((req.query.mode as string) == "w") {
218 228 const inventory = await getInventory(
219 accountId,
229 account._id.toString(),
220 230 "Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades"
221 231 );
222 232 //const body = getJSONfromString<INemesisWeakenRequest>(String(req.body));
223 233
224 234 inventory.Nemesis!.InfNodes = [
225 235 {
226 Node: nemesisFactionInfos[inventory.Nemesis!.Faction].showdownNode,
236 Node: getNemesisManifest(inventory.Nemesis!.manifest).showdownNode,
227 237 Influence: 1
228 238 }
229 239 ];
Modified src/helpers/nemesisHelpers.ts +182 -137
@@ -7,51 +7,197 @@ import { IOid } from "../types/commonTypes";
7 7 import { Types } from "mongoose";
8 8 import { addMods, generateRewardSeed } from "../services/inventoryService";
9 9 import { isArchwingMission } from "../services/worldStateService";
10 import { version_compare } from "./inventoryHelpers";
11 10
12 export interface INemesisFactionInfo {
13 systemIndexes: number[];
11 type TInnateDamageTag =
12 | "InnateElectricityDamage"
13 | "InnateHeatDamage"
14 | "InnateFreezeDamage"
15 | "InnateToxinDamage"
16 | "InnateMagDamage"
17 | "InnateRadDamage"
18 | "InnateImpactDamage";
19
20 export interface INemesisManifest {
21 weapons: readonly string[];
22 systemIndexes: readonly number[];
14 23 showdownNode: string;
15 24 ephemeraChance: number;
25 ephemeraTypes?: Record<TInnateDamageTag, string>;
16 26 firstKillReward: string;
17 27 firstConvertReward: string;
18 28 messageTitle: string;
19 29 messageBody: string;
30 minBuild: string;
20 31 }
21 32
22 export const nemesisFactionInfos: Record<TNemesisFaction, INemesisFactionInfo> = {
23 FC_GRINEER: {
24 systemIndexes: [2, 3, 9, 11, 18],
25 showdownNode: "CrewBattleNode557",
26 ephemeraChance: 0.05,
27 firstKillReward: "/Lotus/StoreItems/Upgrades/Skins/Clan/LichKillerBadgeItem",
28 firstConvertReward: "/Lotus/StoreItems/Upgrades/Skins/Sigils/KuvaLichSigil",
29 messageTitle: "/Lotus/Language/Inbox/VanquishKuvaMsgTitle",
30 messageBody: "/Lotus/Language/Inbox/VanquishLichMsgBody"
31 },
32 FC_CORPUS: {
33 systemIndexes: [1, 15, 4, 7, 8],
34 showdownNode: "CrewBattleNode558",
35 ephemeraChance: 0.2,
36 firstKillReward: "/Lotus/StoreItems/Upgrades/Skins/Clan/CorpusLichBadgeItem",
37 firstConvertReward: "/Lotus/StoreItems/Upgrades/Skins/Sigils/CorpusLichSigil",
38 messageTitle: "/Lotus/Language/Inbox/VanquishLawyerMsgTitle",
39 messageBody: "/Lotus/Language/Inbox/VanquishLichMsgBody"
40 },
41 FC_INFESTATION: {
42 systemIndexes: [23],
43 showdownNode: "CrewBattleNode559",
44 ephemeraChance: 0,
45 firstKillReward: "/Lotus/StoreItems/Upgrades/Skins/Sigils/InfLichVanquishedSigil",
46 firstConvertReward: "/Lotus/StoreItems/Upgrades/Skins/Sigils/InfLichConvertedSigil",
47 messageTitle: "/Lotus/Language/Inbox/VanquishBandMsgTitle",
48 messageBody: "/Lotus/Language/Inbox/VanquishBandMsgBody"
33 class KuvaLichManifest implements INemesisManifest {
34 weapons = [
35 "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Drakgoon/KuvaDrakgoon",
36 "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Karak/KuvaKarak",
37 "/Lotus/Weapons/Grineer/Melee/GrnKuvaLichScythe/GrnKuvaLichScytheWeapon",
38 "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Kohm/KuvaKohm",
39 "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Ogris/KuvaOgris",
40 "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Quartakk/KuvaQuartakk",
41 "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Tonkor/KuvaTonkor",
42 "/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Brakk/KuvaBrakk",
43 "/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Kraken/KuvaKraken",
44 "/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Seer/KuvaSeer",
45 "/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Stubba/KuvaStubba",
46 "/Lotus/Weapons/Grineer/HeavyWeapons/GrnHeavyGrenadeLauncher",
47 "/Lotus/Weapons/Grineer/LongGuns/GrnKuvaLichRifle/GrnKuvaLichRifleWeapon"
48 ];
49 systemIndexes = [2, 3, 9, 11, 18];
50 showdownNode = "CrewBattleNode557";
51 ephemeraChance = 0.05;
52 ephemeraTypes = {
53 InnateElectricityDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaLightningEphemera",
54 InnateHeatDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaFireEphemera",
55 InnateFreezeDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaIceEphemera",
56 InnateToxinDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaToxinEphemera",
57 InnateMagDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaMagneticEphemera",
58 InnateRadDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaTricksterEphemera",
59 InnateImpactDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaImpactEphemera"
60 };
61 firstKillReward = "/Lotus/StoreItems/Upgrades/Skins/Clan/LichKillerBadgeItem";
62 firstConvertReward = "/Lotus/StoreItems/Upgrades/Skins/Sigils/KuvaLichSigil";
63 messageTitle = "/Lotus/Language/Inbox/VanquishKuvaMsgTitle";
64 messageBody = "/Lotus/Language/Inbox/VanquishLichMsgBody";
65 minBuild = "2019.10.31.22.42"; // 26.0.0
66 }
67
68 class KuvaLichManifestVersionTwo extends KuvaLichManifest {
69 constructor() {
70 super();
71 this.ephemeraChance = 0.1;
72 this.minBuild = "2020.03.05.16.06"; // Unsure about this one, so using the same value as in version three.
73 }
74 }
75
76 class KuvaLichManifestVersionThree extends KuvaLichManifestVersionTwo {
77 constructor() {
78 super();
79 this.weapons.push("/Lotus/Weapons/Grineer/Bows/GrnBow/GrnBowWeapon");
80 this.weapons.push("/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hind/KuvaHind");
81 this.weapons.push("/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Nukor/KuvaNukor");
82 this.ephemeraChance = 0.2;
83 this.minBuild = "2020.03.05.16.06"; // This is 27.2.0, tho 27.1.0 should also recognise this.
84 }
85 }
86
87 class KuvaLichManifestVersionFour extends KuvaLichManifestVersionThree {
88 constructor() {
89 super();
90 this.minBuild = "2021.07.05.17.03"; // Unsure about this one, so using the same value as in version five.
91 }
92 }
93
94 class KuvaLichManifestVersionFive extends KuvaLichManifestVersionFour {
95 constructor() {
96 super();
97 this.weapons.push("/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hek/KuvaHekWeapon");
98 this.weapons.push("/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Zarr/KuvaZarr");
99 this.weapons.push("/Lotus/Weapons/Grineer/KuvaLich/HeavyWeapons/Grattler/KuvaGrattler");
100 this.minBuild = "2021.07.05.17.03"; // 30.5.0
101 }
102 }
103
104 class KuvaLichManifestVersionSix extends KuvaLichManifestVersionFive {
105 constructor() {
106 super();
107 this.weapons.push("/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Sobek/KuvaSobek");
108 this.minBuild = "2024.05.15.11.07"; // 35.6.0
109 }
110 }
111
112 class LawyerManifest implements INemesisManifest {
113 weapons = [
114 "/Lotus/Weapons/Corpus/LongGuns/CrpBriefcaseLauncher/CrpBriefcaseLauncher",
115 "/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEArcaPlasmor/CrpBEArcaPlasmor",
116 "/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEFluxRifle/CrpBEFluxRifle",
117 "/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBETetra/CrpBETetra",
118 "/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBECycron/CrpBECycron",
119 "/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEDetron/CrpBEDetron",
120 "/Lotus/Weapons/Corpus/Pistols/CrpIgniterPistol/CrpIgniterPistol",
121 "/Lotus/Weapons/Corpus/Pistols/CrpBriefcaseAkimbo/CrpBriefcaseAkimboPistol"
122 ];
123 systemIndexes = [1, 15, 4, 7, 8];
124 showdownNode = "CrewBattleNode558";
125 ephemeraChance = 0.2;
126 ephemeraTypes = {
127 InnateElectricityDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraA",
128 InnateHeatDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraB",
129 InnateFreezeDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraC",
130 InnateToxinDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraD",
131 InnateMagDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraE",
132 InnateRadDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraF",
133 InnateImpactDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraG"
134 };
135 firstKillReward = "/Lotus/StoreItems/Upgrades/Skins/Clan/CorpusLichBadgeItem";
136 firstConvertReward = "/Lotus/StoreItems/Upgrades/Skins/Sigils/CorpusLichSigil";
137 messageTitle = "/Lotus/Language/Inbox/VanquishLawyerMsgTitle";
138 messageBody = "/Lotus/Language/Inbox/VanquishLichMsgBody";
139 minBuild = "2021.07.05.17.03"; // 30.5.0
140 }
141
142 class LawyerManifestVersionTwo extends LawyerManifest {
143 constructor() {
144 super();
145 this.weapons.push("/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEPlinx/CrpBEPlinxWeapon");
146 this.minBuild = "2022.11.30.08.13"; // 32.2.0
147 }
148 }
149
150 class LawyerManifestVersionThree extends LawyerManifestVersionTwo {
151 constructor() {
152 super();
153 this.weapons.push("/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEGlaxion/CrpBEGlaxion");
154 this.minBuild = "2024.05.15.11.07"; // 35.6.0
155 }
156 }
157
158 class LawyerManifestVersionFour extends LawyerManifestVersionThree {
159 constructor() {
160 super();
161 this.minBuild = "2024.10.01.11.03"; // 37.0.0
49 162 }
163 }
164
165 class InfestedLichManfest implements INemesisManifest {
166 weapons = [];
167 systemIndexes = [23];
168 showdownNode = "CrewBattleNode559";
169 ephemeraChance = 0;
170 firstKillReward = "/Lotus/StoreItems/Upgrades/Skins/Sigils/InfLichVanquishedSigil";
171 firstConvertReward = "/Lotus/StoreItems/Upgrades/Skins/Sigils/InfLichConvertedSigil";
172 messageTitle = "/Lotus/Language/Inbox/VanquishBandMsgTitle";
173 messageBody = "/Lotus/Language/Inbox/VanquishBandMsgBody";
174 minBuild = "2025.03.18.09.51"; // 38.5.0
175 }
176
177 const nemesisManifests: Record<string, INemesisManifest> = {
178 "/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifest": new KuvaLichManifest(),
179 "/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionTwo": new KuvaLichManifestVersionTwo(),
180 "/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionThree": new KuvaLichManifestVersionThree(),
181 "/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionFour": new KuvaLichManifestVersionFour(),
182 "/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionFive": new KuvaLichManifestVersionFive(),
183 "/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionSix": new KuvaLichManifestVersionSix(),
184 "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifest": new LawyerManifest(),
185 "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionTwo": new LawyerManifestVersionTwo(),
186 "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionThree": new LawyerManifestVersionThree(),
187 "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionFour": new LawyerManifestVersionFour(),
188 "/Lotus/Types/Enemies/InfestedLich/InfestedLichManifest": new InfestedLichManfest()
50 189 };
51 190
52 export const getInfNodes = (faction: TNemesisFaction, rank: number): IInfNode[] => {
191 export const getNemesisManifest = (manifest: string): INemesisManifest => {
192 if (manifest in nemesisManifests) {
193 return nemesisManifests[manifest];
194 }
195 throw new Error(`unknown nemesis manifest: ${manifest}`);
196 };
197
198 export const getInfNodes = (manifest: INemesisManifest, rank: number): IInfNode[] => {
53 199 const infNodes = [];
54 const systemIndex = nemesisFactionInfos[faction].systemIndexes[rank];
200 const systemIndex = manifest.systemIndexes[rank];
55 201 for (const [key, value] of Object.entries(ExportRegions)) {
56 202 if (
57 203 value.systemIndex === systemIndex &&
@@ -73,36 +219,6 @@ export const getInfNodes = (faction: TNemesisFaction, rank: number): IInfNode[]
73 219 return infNodes;
74 220 };
75 221
76 type TInnateDamageTag =
77 | "InnateElectricityDamage"
78 | "InnateHeatDamage"
79 | "InnateFreezeDamage"
80 | "InnateToxinDamage"
81 | "InnateMagDamage"
82 | "InnateRadDamage"
83 | "InnateImpactDamage";
84
85 const ephmeraTypes: Record<"FC_GRINEER" | "FC_CORPUS", Record<TInnateDamageTag, string>> = {
86 FC_GRINEER: {
87 InnateElectricityDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaLightningEphemera",
88 InnateHeatDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaFireEphemera",
89 InnateFreezeDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaIceEphemera",
90 InnateToxinDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaToxinEphemera",
91 InnateMagDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaMagneticEphemera",
92 InnateRadDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaTricksterEphemera",
93 InnateImpactDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaImpactEphemera"
94 },
95 FC_CORPUS: {
96 InnateElectricityDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraA",
97 InnateHeatDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraB",
98 InnateFreezeDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraC",
99 InnateToxinDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraD",
100 InnateMagDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraE",
101 InnateRadDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraF",
102 InnateImpactDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraG"
103 }
104 };
105
106 222 // Get a parazon 'passcode' based on the nemesis fingerprint so it's always the same for the same nemesis.
107 223 export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: TNemesisFaction }): number[] => {
108 224 const rng = new SRng(nemesis.fp);
@@ -256,77 +372,6 @@ export const consumeModCharge = (
256 372 }
257 373 };
258 374
259 const kuvaLichVersionSixWeapons = [
260 "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Drakgoon/KuvaDrakgoon",
261 "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Karak/KuvaKarak",
262 "/Lotus/Weapons/Grineer/Melee/GrnKuvaLichScythe/GrnKuvaLichScytheWeapon",
263 "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Kohm/KuvaKohm",
264 "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Ogris/KuvaOgris",
265 "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Quartakk/KuvaQuartakk",
266 "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Tonkor/KuvaTonkor",
267 "/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Brakk/KuvaBrakk",
268 "/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Kraken/KuvaKraken",
269 "/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Seer/KuvaSeer",
270 "/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Stubba/KuvaStubba",
271 "/Lotus/Weapons/Grineer/HeavyWeapons/GrnHeavyGrenadeLauncher",
272 "/Lotus/Weapons/Grineer/LongGuns/GrnKuvaLichRifle/GrnKuvaLichRifleWeapon",
273 "/Lotus/Weapons/Grineer/Bows/GrnBow/GrnBowWeapon",
274 "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hind/KuvaHind",
275 "/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Nukor/KuvaNukor",
276 "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hek/KuvaHekWeapon",
277 "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Zarr/KuvaZarr",
278 "/Lotus/Weapons/Grineer/KuvaLich/HeavyWeapons/Grattler/KuvaGrattler",
279 "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Sobek/KuvaSobek"
280 ];
281
282 const corpusVersionThreeWeapons = [
283 "/Lotus/Weapons/Corpus/LongGuns/CrpBriefcaseLauncher/CrpBriefcaseLauncher",
284 "/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEArcaPlasmor/CrpBEArcaPlasmor",
285 "/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEFluxRifle/CrpBEFluxRifle",
286 "/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBETetra/CrpBETetra",
287 "/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBECycron/CrpBECycron",
288 "/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEDetron/CrpBEDetron",
289 "/Lotus/Weapons/Corpus/Pistols/CrpIgniterPistol/CrpIgniterPistol",
290 "/Lotus/Weapons/Corpus/Pistols/CrpBriefcaseAkimbo/CrpBriefcaseAkimboPistol",
291 "/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEPlinx/CrpBEPlinxWeapon",
292 "/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEGlaxion/CrpBEGlaxion"
293 ];
294
295 export const getWeaponsForManifest = (manifest: string): readonly string[] => {
296 switch (manifest) {
297 case "/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionSix": // >= 35.6.0
298 return kuvaLichVersionSixWeapons;
299 case "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionThree": // >= 35.6.0
300 case "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionFour": // >= 37.0.0
301 return corpusVersionThreeWeapons;
302 }
303 throw new Error(`unknown nemesis manifest: ${manifest}`);
304 };
305
306 export const isNemesisCompatibleWithVersion = (
307 nemesis: { manifest: string; Faction: TNemesisFaction },
308 buildLabel: string
309 ): boolean => {
310 // Anything below 35.6.0 is not going to be okay given our set of supported manifests.
311 if (version_compare(buildLabel, "2024.05.15.11.07") < 0) {
312 return false;
313 }
314
315 if (nemesis.Faction == "FC_INFESTATION") {
316 // Anything below 38.5.0 isn't gonna like an infested lich.
317 if (version_compare(buildLabel, "2025.03.18.16.07") < 0) {
318 return false;
319 }
320 } else if (nemesis.manifest == "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionFour") {
321 // 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.
322 if (version_compare(buildLabel, "2024.10.01.11.03") < 0) {
323 nemesis.manifest = "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionThree";
324 }
325 }
326
327 return true;
328 };
329
330 375 export const getInnateDamageTag = (KillingSuit: string): TInnateDamageTag => {
331 376 return ExportWarframes[KillingSuit].nemesisUpgradeTag!;
332 377 };
@@ -349,7 +394,7 @@ export interface INemesisProfile {
349 394
350 395 export const generateNemesisProfile = (
351 396 fp: bigint = generateRewardSeed(),
352 Faction: TNemesisFaction = "FC_CORPUS",
397 manifest: INemesisManifest = new LawyerManifest(),
353 398 killingSuit: string = "/Lotus/Powersuits/Ember/Ember"
354 399 ): INemesisProfile => {
355 400 const rng = new SRng(fp);
@@ -363,11 +408,11 @@ export const generateNemesisProfile = (
363 408 innateDamageTag: getInnateDamageTag(killingSuit),
364 409 innateDamageValue: Math.trunc(value * 0x40000000) // TODO: For -1399275245665749231n, the value should be 75306944, but we're off by 59 with 75307003.
365 410 };
366 if (rng.randomFloat() <= nemesisFactionInfos[Faction].ephemeraChance && Faction != "FC_INFESTATION") {
367 profile.ephemera = ephmeraTypes[Faction][profile.innateDamageTag];
411 if (rng.randomFloat() <= manifest.ephemeraChance && manifest.ephemeraTypes) {
412 profile.ephemera = manifest.ephemeraTypes[profile.innateDamageTag];
368 413 }
369 414 rng.randomFloat(); // something related to sentinel agent maybe
370 if (Faction == "FC_CORPUS") {
415 if (manifest instanceof LawyerManifest) {
371 416 profile.petHead = rng.randomElement(petHeads)!;
372 417 profile.petBody = rng.randomElement([
373 418 "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyA",
Modified src/services/missionInventoryUpdateService.ts +12 -14
@@ -61,9 +61,8 @@ import {
61 61 getInfestedLichItemRewards,
62 62 getInfNodes,
63 63 getKillTokenRewardCount,
64 getNemesisPasscode,
65 getWeaponsForManifest,
66 nemesisFactionInfos
64 getNemesisManifest,
65 getNemesisPasscode
67 66 } from "@/src/helpers/nemesisHelpers";
68 67 import { Loadout } from "../models/inventoryModels/loadoutModel";
69 68 import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes";
@@ -662,12 +661,12 @@ export const addMissionInventoryUpdates = async (
662 661 k: value.killed
663 662 });
664 663
664 const manifest = getNemesisManifest(inventory.Nemesis.manifest);
665 665 const profile = generateNemesisProfile(
666 666 inventory.Nemesis.fp,
667 inventory.Nemesis.Faction,
667 manifest,
668 668 inventory.Nemesis.KillingSuit
669 669 );
670 const nemesisFactionInfo = nemesisFactionInfos[inventory.Nemesis.Faction];
671 670 const att: string[] = [];
672 671 let countedAtt: ITypeCount[] | undefined;
673 672
@@ -676,9 +675,7 @@ export const addMissionInventoryUpdates = async (
676 675 value.weaponLoc &&
677 676 inventory.Nemesis.Faction != "FC_INFESTATION" // weaponLoc is "/Lotus/Language/Weapons/DerelictCernosName" for these for some reason
678 677 ) {
679 const weaponType = getWeaponsForManifest(inventory.Nemesis.manifest)[
680 inventory.Nemesis.WeaponIdx
681 ];
678 const weaponType = manifest.weapons[inventory.Nemesis.WeaponIdx];
682 679 giveNemesisWeaponRecipe(inventory, weaponType, value.nemesisName, value.weaponLoc, profile);
683 680 att.push(weaponType);
684 681 }
@@ -704,9 +701,7 @@ export const addMissionInventoryUpdates = async (
704 701 att.push(profile.ephemera);
705 702 }
706 703
707 const skinRewardStoreItem = value.killed
708 ? nemesisFactionInfo.firstKillReward
709 : nemesisFactionInfo.firstConvertReward;
704 const skinRewardStoreItem = value.killed ? manifest.firstKillReward : manifest.firstConvertReward;
710 705 if (Object.keys(addSkin(inventory, fromStoreItem(skinRewardStoreItem))).length != 0) {
711 706 att.push(skinRewardStoreItem);
712 707 }
@@ -737,7 +732,7 @@ export const addMissionInventoryUpdates = async (
737 732 await createMessage(inventory.accountOwnerId, [
738 733 {
739 734 sndr: "/Lotus/Language/Bosses/Ordis",
740 msg: nemesisFactionInfo.messageBody,
735 msg: manifest.messageBody,
741 736 arg: [
742 737 {
743 738 Key: "LICH_NAME",
@@ -747,7 +742,7 @@ export const addMissionInventoryUpdates = async (
747 742 att: att,
748 743 countedAtt: countedAtt,
749 744 attVisualOnly: true,
750 sub: nemesisFactionInfo.messageTitle,
745 sub: manifest.messageTitle,
751 746 icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
752 747 highPriority: true
753 748 }
@@ -1187,7 +1182,10 @@ export const addMissionRewards = async (
1187 1182 inventory.Nemesis.Rank = Math.min(inventory.Nemesis.Rank + 1, 4);
1188 1183 inventoryChanges.Nemesis.Rank = inventory.Nemesis.Rank;
1189 1184 }
1190 inventory.Nemesis.InfNodes = getInfNodes(inventory.Nemesis.Faction, inventory.Nemesis.Rank);
1185 inventory.Nemesis.InfNodes = getInfNodes(
1186 getNemesisManifest(inventory.Nemesis.manifest),
1187 inventory.Nemesis.Rank
1188 );
1191 1189 }
1192 1190
1193 1191 if (inventory.Nemesis.Faction == "FC_INFESTATION") {