From f1d0a5591fe4898da8388531a32564c449b1e566 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sat, 12 Apr 2025 19:23:23 +0200 Subject: [PATCH 1/7] simplify getNemesisPasscode API --- src/controllers/api/nemesisController.ts | 2 +- src/helpers/nemesisHelpers.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/controllers/api/nemesisController.ts b/src/controllers/api/nemesisController.ts index 34e6bd4a..a2a78cf1 100644 --- a/src/controllers/api/nemesisController.ts +++ b/src/controllers/api/nemesisController.ts @@ -49,7 +49,7 @@ export const nemesisController: RequestHandler = async (req, res) => { } else if ((req.query.mode as string) == "p") { const inventory = await getInventory(accountId, "Nemesis"); const body = getJSONfromString(String(req.body)); - const passcode = getNemesisPasscode(inventory.Nemesis!.fp, inventory.Nemesis!.Faction); + const passcode = getNemesisPasscode(inventory.Nemesis!); let guessResult = 0; if (inventory.Nemesis!.Faction == "FC_INFESTATION") { for (let i = 0; i != 3; ++i) { diff --git a/src/helpers/nemesisHelpers.ts b/src/helpers/nemesisHelpers.ts index 839e0675..9b9f54b6 100644 --- a/src/helpers/nemesisHelpers.ts +++ b/src/helpers/nemesisHelpers.ts @@ -33,10 +33,10 @@ const systemIndexes: Record = { }; // Get a parazon 'passcode' based on the nemesis fingerprint so it's always the same for the same nemesis. -export const getNemesisPasscode = (fp: bigint, faction: string): number[] => { - const rng = new SRng(fp); +export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: string }): number[] => { + const rng = new SRng(nemesis.fp); const passcode = [rng.randomInt(0, 7)]; - if (faction != "FC_INFESTATION") { + if (nemesis.Faction != "FC_INFESTATION") { passcode.push(rng.randomInt(0, 7)); passcode.push(rng.randomInt(0, 7)); } -- 2.47.2 From fd0eedb0422015ecb136a1853ef329678b3522e8 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sat, 12 Apr 2025 19:24:03 +0200 Subject: [PATCH 2/7] handle nemesis mode=rs --- src/controllers/api/nemesisController.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/controllers/api/nemesisController.ts b/src/controllers/api/nemesisController.ts index a2a78cf1..43fe2cb7 100644 --- a/src/controllers/api/nemesisController.ts +++ b/src/controllers/api/nemesisController.ts @@ -66,6 +66,12 @@ export const nemesisController: RequestHandler = async (req, res) => { } } res.json({ GuessResult: guessResult }); + } else if ((req.query.mode as string) == "rs") { + // report spawn; POST but no application data in body + const inventory = await getInventory(accountId, "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 body = getJSONfromString(String(req.body)); -- 2.47.2 From 66e197fe22d9fa51bc2e8eca0d241ffe4b9ce553 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sat, 12 Apr 2025 19:57:55 +0200 Subject: [PATCH 3/7] handle nemesis mode=r --- src/controllers/api/nemesisController.ts | 88 ++++++++++++++++++- src/helpers/nemesisHelpers.ts | 22 +++++ src/services/missionInventoryUpdateService.ts | 17 +--- 3 files changed, 109 insertions(+), 18 deletions(-) diff --git a/src/controllers/api/nemesisController.ts b/src/controllers/api/nemesisController.ts index 43fe2cb7..8c192a16 100644 --- a/src/controllers/api/nemesisController.ts +++ b/src/controllers/api/nemesisController.ts @@ -1,10 +1,17 @@ -import { getInfNodes, getNemesisPasscode } from "@/src/helpers/nemesisHelpers"; +import { encodeNemesisGuess, getInfNodes, getNemesisPasscode } from "@/src/helpers/nemesisHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { freeUpSlot, getInventory } from "@/src/services/inventoryService"; import { getAccountIdForRequest } from "@/src/services/loginService"; import { SRng } from "@/src/services/rngService"; import { IMongoDate, IOid } from "@/src/types/commonTypes"; -import { IInnateDamageFingerprint, InventorySlot, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes"; +import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; +import { + IInnateDamageFingerprint, + InventorySlot, + IUpgradeClient, + IWeaponSkinClient, + TEquipmentKey +} from "@/src/types/inventoryTypes/inventoryTypes"; import { logger } from "@/src/utils/logger"; import { RequestHandler } from "express"; @@ -66,6 +73,69 @@ export const nemesisController: RequestHandler = async (req, res) => { } } res.json({ GuessResult: guessResult }); + } else if (req.query.mode == "r") { + const inventory = await getInventory(accountId, "Nemesis"); + const body = getJSONfromString(String(req.body)); + if (inventory.Nemesis!.Faction == "FC_INFESTATION") { + const guess: number[] = [body.guess & 0xf, (body.guess >> 4) & 0xf, (body.guess >> 8) & 0xf]; + const passcode = getNemesisPasscode(inventory.Nemesis!)[0]; + + // Add to GuessHistory + const result1 = passcode == guess[0] ? 0 : 1; + const result2 = passcode == guess[1] ? 0 : 1; + const result3 = passcode == guess[2] ? 0 : 1; + inventory.Nemesis!.GuessHistory.push( + encodeNemesisGuess(guess[0], result1, guess[1], result2, guess[2], result3) + ); + + // Increase antivirus + let antivirusGain = 5; + for (const upgrade of body.knife!.AttachedUpgrades) { + switch (upgrade.ItemType) { + case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndSpeedOnUseMod": + antivirusGain += 10; + break; + case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndWeaponDamageOnUseMod": + antivirusGain += 10; + break; + case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusLargeOnSingleUseMod": // Instant Secure + antivirusGain += 15; + break; + case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusOnUseMod": // Immuno Shield + antivirusGain += 15; + break; + case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusSmallOnSingleUseMod": + antivirusGain += 10; + break; + } + } + inventory.Nemesis!.HenchmenKilled += antivirusGain; + if (inventory.Nemesis!.HenchmenKilled >= 100) { + inventory.Nemesis!.InfNodes = [ + { + Node: "CrewBattleNode559", + Influence: 1 + } + ]; + inventory.Nemesis!.Weakened = true; + } else { + inventory.Nemesis!.InfNodes = getInfNodes("FC_INFESTATION", 0); + } + + // TODO: Consume mod charges? + + await inventory.save(); + res.end(); + } else { + const passcode = getNemesisPasscode(inventory.Nemesis!); + if (passcode[body.position] != body.guess) { + res.end(); + } else { + inventory.Nemesis!.Rank += 1; + 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"); @@ -179,6 +249,20 @@ interface INemesisPrespawnCheckRequest { potency?: number[]; } +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?: { + Item: IEquipmentClient; + Skins: IWeaponSkinClient[]; + ModSlot: number; + CustSlot: number; + AttachedUpgrades: IUpgradeClient[]; + HiddenWhenHolstered: boolean; + }; +} + const kuvaLichVersionSixWeapons = [ "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Drakgoon/KuvaDrakgoon", "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Karak/KuvaKarak", diff --git a/src/helpers/nemesisHelpers.ts b/src/helpers/nemesisHelpers.ts index 9b9f54b6..9dcaa2cf 100644 --- a/src/helpers/nemesisHelpers.ts +++ b/src/helpers/nemesisHelpers.ts @@ -42,3 +42,25 @@ export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: string }): nu } return passcode; }; + +export const encodeNemesisGuess = ( + symbol1: number, + result1: number, + symbol2: number, + result2: number, + symbol3: number, + result3: number +): number => { + return ( + (symbol1 & 0xf) | + ((result1 & 3) << 12) | + ((symbol2 << 4) & 0xff) | + ((result2 << 14) & 0xffff) | + ((symbol3 & 0xf) << 8) | + ((result3 & 3) << 16) + ); +}; + +export const decodeNemesisGuess = (val: number): number[] => { + return [val & 0xf, (val >> 12) & 3, (val & 0xff) >> 4, (val & 0xffff) >> 14, (val >> 8) & 0xf, (val >> 16) & 3]; +}; diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index e6492a6e..de488ec4 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -699,25 +699,10 @@ export const addMissionRewards = async ( } if (inventory.Nemesis.Faction == "FC_INFESTATION") { - inventoryChanges.Nemesis.HenchmenKilled ??= 0; - inventoryChanges.Nemesis.MissionCount ??= 0; - - inventory.Nemesis.HenchmenKilled += 5; inventory.Nemesis.MissionCount += 1; - inventoryChanges.Nemesis.HenchmenKilled += 5; + inventoryChanges.Nemesis.MissionCount ??= 0; inventoryChanges.Nemesis.MissionCount += 1; - - if (inventory.Nemesis.HenchmenKilled >= 100) { - inventory.Nemesis.InfNodes = [ - { - Node: "CrewBattleNode559", - Influence: 1 - } - ]; - inventory.Nemesis.Weakened = true; - inventoryChanges.Nemesis.Weakened = true; - } } inventoryChanges.Nemesis.InfNodes = inventory.Nemesis.InfNodes; -- 2.47.2 From 78756133bf6f2cc9fc8b0cd36dd28c6708c6b071 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sat, 12 Apr 2025 20:02:31 +0200 Subject: [PATCH 4/7] update infnodes when ranking up lich --- src/controllers/api/nemesisController.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/controllers/api/nemesisController.ts b/src/controllers/api/nemesisController.ts index 8c192a16..bdb8f73c 100644 --- a/src/controllers/api/nemesisController.ts +++ b/src/controllers/api/nemesisController.ts @@ -132,6 +132,7 @@ export const nemesisController: RequestHandler = async (req, res) => { res.end(); } else { inventory.Nemesis!.Rank += 1; + inventory.Nemesis!.InfNodes = getInfNodes(inventory.Nemesis!.Faction, inventory.Nemesis!.Rank); await inventory.save(); res.json({ RankIncrease: 1 }); } -- 2.47.2 From c1702229bc23055fba165b880499728a6f524d4a Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sat, 12 Apr 2025 22:34:33 +0200 Subject: [PATCH 5/7] consume potency mods when they add to antivirus gain --- src/controllers/api/nemesisController.ts | 29 ++++++++-- src/helpers/nemesisHelpers.ts | 64 ++++++++++++++++++++++ src/types/inventoryTypes/inventoryTypes.ts | 7 ++- 3 files changed, 94 insertions(+), 6 deletions(-) diff --git a/src/controllers/api/nemesisController.ts b/src/controllers/api/nemesisController.ts index bdb8f73c..dc1e37e5 100644 --- a/src/controllers/api/nemesisController.ts +++ b/src/controllers/api/nemesisController.ts @@ -1,5 +1,12 @@ -import { encodeNemesisGuess, getInfNodes, getNemesisPasscode } from "@/src/helpers/nemesisHelpers"; +import { + consumeModCharge, + encodeNemesisGuess, + getInfNodes, + getNemesisPasscode, + 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 { SRng } from "@/src/services/rngService"; @@ -10,6 +17,7 @@ import { InventorySlot, IUpgradeClient, IWeaponSkinClient, + LoadoutIndex, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes"; import { logger } from "@/src/utils/logger"; @@ -74,7 +82,10 @@ export const nemesisController: RequestHandler = async (req, res) => { } res.json({ GuessResult: guessResult }); } else if (req.query.mode == "r") { - const inventory = await getInventory(accountId, "Nemesis"); + const inventory = await getInventory( + accountId, + "Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades" + ); const body = getJSONfromString(String(req.body)); if (inventory.Nemesis!.Faction == "FC_INFESTATION") { const guess: number[] = [body.guess & 0xf, (body.guess >> 4) & 0xf, (body.guess >> 8) & 0xf]; @@ -90,22 +101,32 @@ export const nemesisController: RequestHandler = async (req, res) => { // Increase antivirus 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": antivirusGain += 10; + consumeModCharge(response, inventory, upgrade, dataknifeUpgrades); break; case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndWeaponDamageOnUseMod": antivirusGain += 10; + consumeModCharge(response, inventory, upgrade, dataknifeUpgrades); break; case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusLargeOnSingleUseMod": // Instant Secure antivirusGain += 15; + consumeModCharge(response, inventory, upgrade, dataknifeUpgrades); break; case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusOnUseMod": // Immuno Shield antivirusGain += 15; + consumeModCharge(response, inventory, upgrade, dataknifeUpgrades); break; case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusSmallOnSingleUseMod": antivirusGain += 10; + consumeModCharge(response, inventory, upgrade, dataknifeUpgrades); break; } } @@ -122,10 +143,8 @@ export const nemesisController: RequestHandler = async (req, res) => { inventory.Nemesis!.InfNodes = getInfNodes("FC_INFESTATION", 0); } - // TODO: Consume mod charges? - await inventory.save(); - res.end(); + res.json(response); } else { const passcode = getNemesisPasscode(inventory.Nemesis!); if (passcode[body.position] != body.guess) { diff --git a/src/helpers/nemesisHelpers.ts b/src/helpers/nemesisHelpers.ts index 9dcaa2cf..6d0d4d35 100644 --- a/src/helpers/nemesisHelpers.ts +++ b/src/helpers/nemesisHelpers.ts @@ -1,6 +1,11 @@ import { ExportRegions } from "warframe-public-export-plus"; import { IInfNode } from "@/src/types/inventoryTypes/inventoryTypes"; import { SRng } from "@/src/services/rngService"; +import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel"; +import { logger } from "../utils/logger"; +import { IOid } from "../types/commonTypes"; +import { Types } from "mongoose"; +import { addMods } from "../services/inventoryService"; export const getInfNodes = (faction: string, rank: number): IInfNode[] => { const infNodes = []; @@ -64,3 +69,62 @@ export const encodeNemesisGuess = ( export const decodeNemesisGuess = (val: number): number[] => { return [val & 0xf, (val >> 12) & 3, (val & 0xff) >> 4, (val & 0xffff) >> 14, (val >> 8) & 0xf, (val >> 16) & 3]; }; + +export interface IKnifeResponse { + UpgradeIds?: string[]; + UpgradeTypes?: string[]; + UpgradeFingerprints?: { lvl: number }[]; + UpgradeNew?: boolean[]; + HasKnife?: boolean; +} + +export const consumeModCharge = ( + response: IKnifeResponse, + inventory: TInventoryDatabaseDocument, + upgrade: { ItemId: IOid; ItemType: string }, + dataknifeUpgrades: string[] +): void => { + response.UpgradeIds ??= []; + response.UpgradeTypes ??= []; + response.UpgradeFingerprints ??= []; + response.UpgradeNew ??= []; + response.HasKnife ??= true; + + if (upgrade.ItemId.$oid != "000000000000000000000000") { + const dbUpgrade = inventory.Upgrades.id(upgrade.ItemId.$oid)!; + const fingerprint = JSON.parse(dbUpgrade.UpgradeFingerprint!) as { lvl: number }; + fingerprint.lvl += 1; + dbUpgrade.UpgradeFingerprint = JSON.stringify(fingerprint); + + response.UpgradeIds.push(upgrade.ItemId.$oid); + response.UpgradeTypes.push(upgrade.ItemType); + response.UpgradeFingerprints.push(fingerprint); + response.UpgradeNew.push(false); + } else { + const id = new Types.ObjectId(); + inventory.Upgrades.push({ + _id: id, + ItemType: upgrade.ItemType, + UpgradeFingerprint: `{"lvl":1}` + }); + + addMods(inventory, [ + { + ItemType: upgrade.ItemType, + ItemCount: -1 + } + ]); + + const dataknifeRawUpgradeIndex = dataknifeUpgrades.indexOf(upgrade.ItemType); + if (dataknifeRawUpgradeIndex != -1) { + dataknifeUpgrades[dataknifeRawUpgradeIndex] = id.toString(); + } else { + logger.warn(`${upgrade.ItemType} not found in dataknife config`); + } + + response.UpgradeIds.push(id.toString()); + response.UpgradeTypes.push(upgrade.ItemType); + response.UpgradeFingerprints.push({ lvl: 1 }); + response.UpgradeNew.push(true); + } +}; diff --git a/src/types/inventoryTypes/inventoryTypes.ts b/src/types/inventoryTypes/inventoryTypes.ts index 63d1b315..39894564 100644 --- a/src/types/inventoryTypes/inventoryTypes.ts +++ b/src/types/inventoryTypes/inventoryTypes.ts @@ -157,6 +157,11 @@ export type TSolarMapRegion = //TODO: perhaps split response and database into their own files +export enum LoadoutIndex { + NORMAL = 0, + DATAKNIFE = 7 +} + export interface IDailyAffiliations { DailyAffiliation: number; DailyAffiliationPvp: number; @@ -220,7 +225,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu ActiveQuest: string; FlavourItems: IFlavourItem[]; LoadOutPresets: ILoadOutPresets; - CurrentLoadOutIds: IOid[]; //TODO: we store it in the database using this representation as well :/ + CurrentLoadOutIds: IOid[]; // we store it in the database using this representation as well :/ Missions: IMission[]; RandomUpgradesIdentified?: number; LastRegionPlayed: TSolarMapRegion; -- 2.47.2 From aa7673169cc2b06583f86c2fd156f9c7955646b8 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sat, 12 Apr 2025 22:34:41 +0200 Subject: [PATCH 6/7] cap antivirus at 100 for good measure --- src/controllers/api/nemesisController.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/controllers/api/nemesisController.ts b/src/controllers/api/nemesisController.ts index dc1e37e5..6f789e98 100644 --- a/src/controllers/api/nemesisController.ts +++ b/src/controllers/api/nemesisController.ts @@ -132,6 +132,7 @@ 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", -- 2.47.2 From 1a9ec6da175793a89827fa56bb56a5ac9c9a1697 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sat, 12 Apr 2025 22:45:34 +0200 Subject: [PATCH 7/7] remove needless conditional --- src/helpers/nemesisHelpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/nemesisHelpers.ts b/src/helpers/nemesisHelpers.ts index 6d0d4d35..fff5e94e 100644 --- a/src/helpers/nemesisHelpers.ts +++ b/src/helpers/nemesisHelpers.ts @@ -88,7 +88,7 @@ export const consumeModCharge = ( response.UpgradeTypes ??= []; response.UpgradeFingerprints ??= []; response.UpgradeNew ??= []; - response.HasKnife ??= true; + response.HasKnife = true; if (upgrade.ItemId.$oid != "000000000000000000000000") { const dbUpgrade = inventory.Upgrades.id(upgrade.ItemId.$oid)!; -- 2.47.2