feat: handle duet encounter #1592
@ -1,10 +1,25 @@
|
|||||||
import { getInfNodes, getNemesisPasscode } from "@/src/helpers/nemesisHelpers";
|
import {
|
||||||
|
consumeModCharge,
|
||||||
|
encodeNemesisGuess,
|
||||||
|
getInfNodes,
|
||||||
|
getNemesisPasscode,
|
||||||
|
IKnifeResponse
|
||||||
|
} from "@/src/helpers/nemesisHelpers";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
|
||||||
import { freeUpSlot, getInventory } from "@/src/services/inventoryService";
|
import { freeUpSlot, getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { SRng } from "@/src/services/rngService";
|
import { SRng } from "@/src/services/rngService";
|
||||||
import { IMongoDate, IOid } from "@/src/types/commonTypes";
|
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,
|
||||||
|
LoadoutIndex,
|
||||||
|
TEquipmentKey
|
||||||
|
} from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
@ -49,7 +64,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
|||||||
} else if ((req.query.mode as string) == "p") {
|
} else if ((req.query.mode as string) == "p") {
|
||||||
const inventory = await getInventory(accountId, "Nemesis");
|
const inventory = await getInventory(accountId, "Nemesis");
|
||||||
const body = getJSONfromString<INemesisPrespawnCheckRequest>(String(req.body));
|
const body = getJSONfromString<INemesisPrespawnCheckRequest>(String(req.body));
|
||||||
const passcode = getNemesisPasscode(inventory.Nemesis!.fp, inventory.Nemesis!.Faction);
|
const passcode = getNemesisPasscode(inventory.Nemesis!);
|
||||||
let guessResult = 0;
|
let guessResult = 0;
|
||||||
if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
|
if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
|
||||||
for (let i = 0; i != 3; ++i) {
|
for (let i = 0; i != 3; ++i) {
|
||||||
@ -66,6 +81,88 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
res.json({ GuessResult: guessResult });
|
res.json({ GuessResult: guessResult });
|
||||||
|
} else if (req.query.mode == "r") {
|
||||||
|
const inventory = await getInventory(
|
||||||
|
accountId,
|
||||||
|
"Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades"
|
||||||
|
);
|
||||||
|
const body = getJSONfromString<INemesisRequiemRequest>(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;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
} else {
|
||||||
|
const passcode = getNemesisPasscode(inventory.Nemesis!);
|
||||||
|
if (passcode[body.position] != body.guess) {
|
||||||
|
res.end();
|
||||||
|
} else {
|
||||||
|
inventory.Nemesis!.Rank += 1;
|
||||||
|
inventory.Nemesis!.InfNodes = getInfNodes(inventory.Nemesis!.Faction, inventory.Nemesis!.Rank);
|
||||||
|
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");
|
||||||
|
inventory.Nemesis!.LastEnc = inventory.Nemesis!.MissionCount;
|
||||||
|
await inventory.save();
|
||||||
|
res.json({ LastEnc: inventory.Nemesis!.LastEnc });
|
||||||
} else if ((req.query.mode as string) == "s") {
|
} else if ((req.query.mode as string) == "s") {
|
||||||
const inventory = await getInventory(accountId, "Nemesis");
|
const inventory = await getInventory(accountId, "Nemesis");
|
||||||
const body = getJSONfromString<INemesisStartRequest>(String(req.body));
|
const body = getJSONfromString<INemesisStartRequest>(String(req.body));
|
||||||
@ -173,6 +270,20 @@ interface INemesisPrespawnCheckRequest {
|
|||||||
potency?: number[];
|
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 = [
|
const kuvaLichVersionSixWeapons = [
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Drakgoon/KuvaDrakgoon",
|
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Drakgoon/KuvaDrakgoon",
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Karak/KuvaKarak",
|
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Karak/KuvaKarak",
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import { ExportRegions } from "warframe-public-export-plus";
|
import { ExportRegions } from "warframe-public-export-plus";
|
||||||
import { IInfNode } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IInfNode } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { SRng } from "@/src/services/rngService";
|
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[] => {
|
export const getInfNodes = (faction: string, rank: number): IInfNode[] => {
|
||||||
const infNodes = [];
|
const infNodes = [];
|
||||||
@ -33,12 +38,93 @@ const systemIndexes: Record<string, number[]> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 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 = (fp: bigint, faction: string): number[] => {
|
export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: string }): number[] => {
|
||||||
const rng = new SRng(fp);
|
const rng = new SRng(nemesis.fp);
|
||||||
const passcode = [rng.randomInt(0, 7)];
|
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));
|
||||||
passcode.push(rng.randomInt(0, 7));
|
passcode.push(rng.randomInt(0, 7));
|
||||||
}
|
}
|
||||||
return passcode;
|
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];
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -699,25 +699,10 @@ export const addMissionRewards = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (inventory.Nemesis.Faction == "FC_INFESTATION") {
|
if (inventory.Nemesis.Faction == "FC_INFESTATION") {
|
||||||
inventoryChanges.Nemesis.HenchmenKilled ??= 0;
|
|
||||||
inventoryChanges.Nemesis.MissionCount ??= 0;
|
|
||||||
|
|
||||||
inventory.Nemesis.HenchmenKilled += 5;
|
|
||||||
inventory.Nemesis.MissionCount += 1;
|
inventory.Nemesis.MissionCount += 1;
|
||||||
|
|
||||||
inventoryChanges.Nemesis.HenchmenKilled += 5;
|
inventoryChanges.Nemesis.MissionCount ??= 0;
|
||||||
inventoryChanges.Nemesis.MissionCount += 1;
|
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;
|
inventoryChanges.Nemesis.InfNodes = inventory.Nemesis.InfNodes;
|
||||||
|
@ -157,6 +157,11 @@ export type TSolarMapRegion =
|
|||||||
|
|
||||||
//TODO: perhaps split response and database into their own files
|
//TODO: perhaps split response and database into their own files
|
||||||
|
|
||||||
|
export enum LoadoutIndex {
|
||||||
|
NORMAL = 0,
|
||||||
|
DATAKNIFE = 7
|
||||||
|
}
|
||||||
|
|
||||||
export interface IDailyAffiliations {
|
export interface IDailyAffiliations {
|
||||||
DailyAffiliation: number;
|
DailyAffiliation: number;
|
||||||
DailyAffiliationPvp: number;
|
DailyAffiliationPvp: number;
|
||||||
@ -220,7 +225,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
|
|||||||
ActiveQuest: string;
|
ActiveQuest: string;
|
||||||
FlavourItems: IFlavourItem[];
|
FlavourItems: IFlavourItem[];
|
||||||
LoadOutPresets: ILoadOutPresets;
|
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[];
|
Missions: IMission[];
|
||||||
RandomUpgradesIdentified?: number;
|
RandomUpgradesIdentified?: number;
|
||||||
LastRegionPlayed: TSolarMapRegion;
|
LastRegionPlayed: TSolarMapRegion;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user