返回提交历史
Modified
src/controllers/api/inventoryController.ts
+5
-2
Modified
src/controllers/api/nemesisController.ts
+26
-16
Modified
src/helpers/nemesisHelpers.ts
+182
-137
Modified
src/services/missionInventoryUpdateService.ts
+12
-14
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
代码差异
4 个文件
+225
-169
@@ -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
@@ -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
];
@@ -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",
@@ -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") {