feat: weaken nemesis (#1893)
Some checks failed
Build / build (push) Has been cancelled
Build Docker image / docker (push) Has been cancelled

Closes #1885

Reviewed-on: #1893
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
This commit is contained in:
Sainan 2025-04-28 13:59:54 -07:00 committed by Sainan
parent 1e8f2fc766
commit 88d00eaaa1
2 changed files with 156 additions and 50 deletions

View File

@ -2,9 +2,12 @@ import {
consumeModCharge,
encodeNemesisGuess,
getInfNodes,
getKnifeUpgrade,
getNemesisPasscode,
getNemesisPasscodeModTypes,
getWeaponsForManifest,
IKnifeResponse
IKnifeResponse,
showdownNodes
} from "@/src/helpers/nemesisHelpers";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
@ -15,6 +18,8 @@ import { IMongoDate, IOid } from "@/src/types/commonTypes";
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import {
IInnateDamageFingerprint,
IInventoryClient,
INemesisClient,
InventorySlot,
IUpgradeClient,
IWeaponSkinClient,
@ -100,13 +105,14 @@ export const nemesisController: RequestHandler = async (req, res) => {
encodeNemesisGuess(guess[0], result1, guess[1], result2, guess[2], result3)
);
// Increase antivirus
// Increase antivirus if correct antivirus mod is installed
const response: IKnifeResponse = {};
if (result1 == 0 || result2 == 0 || result3 == 0) {
let antivirusGain = 5;
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!;
const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid);
const dataknifeConfigIndex = dataknifeLoadout?.s?.mod ?? 0;
const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex].Upgrades!;
const response: IKnifeResponse = {};
for (const upgrade of body.knife!.AttachedUpgrades) {
switch (upgrade.ItemType) {
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndSpeedOnUseMod":
@ -132,18 +138,12 @@ export const nemesisController: RequestHandler = async (req, res) => {
}
}
inventory.Nemesis!.HenchmenKilled += antivirusGain;
}
if (inventory.Nemesis!.HenchmenKilled >= 100) {
inventory.Nemesis!.HenchmenKilled = 100;
inventory.Nemesis!.InfNodes = [
{
Node: "CrewBattleNode559",
Influence: 1
}
];
inventory.Nemesis!.Weakened = true;
} else {
inventory.Nemesis!.InfNodes = getInfNodes("FC_INFESTATION", 0);
}
await inventory.save();
res.json(response);
@ -213,6 +213,38 @@ export const nemesisController: RequestHandler = async (req, res) => {
res.json({
target: inventory.toJSON().Nemesis
});
} else if ((req.query.mode as string) == "w") {
const inventory = await getInventory(
accountId,
"Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades"
);
//const body = getJSONfromString<INemesisWeakenRequest>(String(req.body));
inventory.Nemesis!.InfNodes = [
{
Node: showdownNodes[inventory.Nemesis!.Faction],
Influence: 1
}
];
inventory.Nemesis!.Weakened = true;
const response: IKnifeResponse & { target: INemesisClient } = {
target: inventory.toJSON<IInventoryClient>().Nemesis!
};
// Consume charge of the correct requiem mod(s)
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!;
const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid);
const dataknifeConfigIndex = dataknifeLoadout?.s?.mod ?? 0;
const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex].Upgrades!;
const modTypes = getNemesisPasscodeModTypes(inventory.Nemesis!);
for (const modType of modTypes) {
const upgrade = getKnifeUpgrade(inventory, dataknifeUpgrades, modType);
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
}
await inventory.save();
res.json(response);
} else {
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
throw new Error(`unknown nemesis mode: ${String(req.query.mode)}`);
@ -264,12 +296,19 @@ interface INemesisRequiemRequest {
guess: number; // grn/crp: 4 bits | coda: 3x 4 bits
position: number; // grn/crp: 0-2 | coda: 0
// knife field provided for coda only
knife?: {
knife?: IKnife;
}
// interface INemesisWeakenRequest {
// target: INemesisClient;
// knife: IKnife;
// }
interface IKnife {
Item: IEquipmentClient;
Skins: IWeaponSkinClient[];
ModSlot: number;
CustSlot: number;
AttachedUpgrades: IUpgradeClient[];
HiddenWhenHolstered: boolean;
};
}

View File

@ -38,17 +38,59 @@ const systemIndexes: Record<string, number[]> = {
FC_INFESTATION: [23]
};
export const showdownNodes: Record<string, string> = {
FC_GRINEER: "CrewBattleNode557",
FC_CORPUS: "CrewBattleNode558",
FC_INFESTATION: "CrewBattleNode559"
};
// 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: string }): number[] => {
const rng = new SRng(nemesis.fp);
const passcode = [rng.randomInt(0, 7)];
const choices = [0, 1, 2, 3, 5, 6, 7];
let choiceIndex = rng.randomInt(0, choices.length - 1);
const passcode = [choices[choiceIndex]];
if (nemesis.Faction != "FC_INFESTATION") {
passcode.push(rng.randomInt(0, 7));
passcode.push(rng.randomInt(0, 7));
choices.splice(choiceIndex, 1);
choiceIndex = rng.randomInt(0, choices.length - 1);
passcode.push(choices[choiceIndex]);
choices.splice(choiceIndex, 1);
choiceIndex = rng.randomInt(0, choices.length - 1);
passcode.push(choices[choiceIndex]);
}
return passcode;
};
const reqiuemMods: readonly string[] = [
"/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalTwoMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalThreeMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalFourMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalFiveMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalSixMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalSevenMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalEightMod"
];
const antivirusMods: readonly string[] = [
"/Lotus/Upgrades/Mods/Immortal/AntivirusOneMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusTwoMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusThreeMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusFourMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusFiveMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusSixMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusSevenMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusEightMod"
];
export const getNemesisPasscodeModTypes = (nemesis: { fp: bigint; Faction: string }): string[] => {
const passcode = getNemesisPasscode(nemesis);
return nemesis.Faction == "FC_INFESTATION"
? passcode.map(i => antivirusMods[i])
: passcode.map(i => reqiuemMods[i]);
};
export const encodeNemesisGuess = (
symbol1: number,
result1: number,
@ -79,6 +121,31 @@ export interface IKnifeResponse {
HasKnife?: boolean;
}
export const getKnifeUpgrade = (
inventory: TInventoryDatabaseDocument,
dataknifeUpgrades: string[],
type: string
): { ItemId: IOid; ItemType: string } => {
if (dataknifeUpgrades.indexOf(type) != -1) {
return {
ItemId: { $oid: "000000000000000000000000" },
ItemType: type
};
}
for (const upgradeId of dataknifeUpgrades) {
if (upgradeId.length == 24) {
const upgrade = inventory.Upgrades.id(upgradeId);
if (upgrade && upgrade.ItemType == type) {
return {
ItemId: { $oid: upgradeId },
ItemType: type
};
}
}
}
throw new Error(`${type} does not seem to be installed on parazon?!`);
};
export const consumeModCharge = (
response: IKnifeResponse,
inventory: TInventoryDatabaseDocument,