2025-05-17 23:29:23 -07:00
import { version_compare } from "@/src/helpers/inventoryHelpers" ;
2025-04-13 05:50:51 -07:00
import {
consumeModCharge ,
2025-06-07 12:14:17 +02:00
decodeNemesisGuess ,
2025-04-13 05:50:51 -07:00
encodeNemesisGuess ,
getInfNodes ,
2025-04-28 13:59:54 -07:00
getKnifeUpgrade ,
2025-05-17 23:29:23 -07:00
getNemesisManifest ,
2025-04-13 05:50:51 -07:00
getNemesisPasscode ,
2025-04-28 13:59:54 -07:00
getNemesisPasscodeModTypes ,
2025-06-07 12:14:17 +02:00
GUESS_CORRECT ,
GUESS_INCORRECT ,
GUESS_NEUTRAL ,
GUESS_NONE ,
2025-06-07 02:16:50 -07:00
GUESS_WILDCARD ,
2025-06-07 12:14:17 +02:00
IKnifeResponse
2025-04-13 05:50:51 -07:00
} from "@/src/helpers/nemesisHelpers" ;
2025-03-20 05:36:09 -07:00
import { getJSONfromString } from "@/src/helpers/stringHelpers" ;
2025-04-13 05:50:51 -07:00
import { Loadout } from "@/src/models/inventoryModels/loadoutModel" ;
2025-03-20 15:27:37 -07:00
import { freeUpSlot , getInventory } from "@/src/services/inventoryService" ;
2025-05-17 23:29:23 -07:00
import { getAccountForRequest } from "@/src/services/loginService" ;
2025-03-20 05:36:09 -07:00
import { SRng } from "@/src/services/rngService" ;
2025-03-20 15:27:37 -07:00
import { IMongoDate , IOid } from "@/src/types/commonTypes" ;
2025-04-13 05:50:51 -07:00
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes" ;
import {
IInnateDamageFingerprint ,
2025-04-28 13:59:54 -07:00
IInventoryClient ,
INemesisClient ,
2025-04-13 05:50:51 -07:00
InventorySlot ,
IUpgradeClient ,
IWeaponSkinClient ,
LoadoutIndex ,
2025-05-13 12:21:10 +02:00
TEquipmentKey ,
TNemesisFaction
2025-04-13 05:50:51 -07:00
} from "@/src/types/inventoryTypes/inventoryTypes" ;
2025-03-20 05:36:09 -07:00
import { logger } from "@/src/utils/logger" ;
import { RequestHandler } from "express" ;
export const nemesisController : RequestHandler = async ( req , res ) = > {
2025-05-17 23:29:23 -07:00
const account = await getAccountForRequest ( req ) ;
2025-03-20 15:27:37 -07:00
if ( ( req . query . mode as string ) == "f" ) {
const body = getJSONfromString < IValenceFusionRequest > ( String ( req . body ) ) ;
2025-05-17 23:29:23 -07:00
const inventory = await getInventory ( account . _id . toString ( ) , body . Category + " WeaponBin" ) ;
2025-03-20 15:27:37 -07:00
const destWeapon = inventory [ body . Category ] . id ( body . DestWeapon . $oid ) ! ;
const sourceWeapon = inventory [ body . Category ] . id ( body . SourceWeapon . $oid ) ! ;
const destFingerprint = JSON . parse ( destWeapon . UpgradeFingerprint ! ) as IInnateDamageFingerprint ;
const sourceFingerprint = JSON . parse ( sourceWeapon . UpgradeFingerprint ! ) as IInnateDamageFingerprint ;
2025-03-30 13:50:59 -07:00
// Update destination damage type if desired
2025-03-20 15:27:37 -07:00
if ( body . UseSourceDmgType ) {
destFingerprint . buffs [ 0 ] . Tag = sourceFingerprint . buffs [ 0 ] . Tag ;
}
// Upgrade destination damage value
const destDamage = 0.25 + ( destFingerprint . buffs [ 0 ] . Value / 0x3fffffff ) * ( 0.6 - 0.25 ) ;
const sourceDamage = 0.25 + ( sourceFingerprint . buffs [ 0 ] . Value / 0x3fffffff ) * ( 0.6 - 0.25 ) ;
let newDamage = Math . max ( destDamage , sourceDamage ) * 1.1 ;
2025-04-01 02:29:51 -07:00
if ( newDamage >= 0.5794998 ) {
2025-03-20 15:27:37 -07:00
newDamage = 0.6 ;
}
destFingerprint . buffs [ 0 ] . Value = Math . trunc ( ( ( newDamage - 0.25 ) / ( 0.6 - 0.25 ) ) * 0x3fffffff ) ;
// Commit fingerprint
destWeapon . UpgradeFingerprint = JSON . stringify ( destFingerprint ) ;
// Remove source weapon
inventory [ body . Category ] . pull ( { _id : body.SourceWeapon.$oid } ) ;
freeUpSlot ( inventory , InventorySlot . WEAPONS ) ;
await inventory . save ( ) ;
res . json ( {
InventoryChanges : {
2025-03-30 13:50:59 -07:00
[ body . Category ] : [ destWeapon . toJSON ( ) ] ,
RemovedIdItems : [ { ItemId : body.SourceWeapon } ]
2025-03-20 15:27:37 -07:00
}
} ) ;
2025-03-22 07:30:16 -07:00
} else if ( ( req . query . mode as string ) == "p" ) {
2025-05-17 23:29:23 -07:00
const inventory = await getInventory ( account . _id . toString ( ) , "Nemesis" ) ;
2025-03-22 07:30:16 -07:00
const body = getJSONfromString < INemesisPrespawnCheckRequest > ( String ( req . body ) ) ;
2025-04-13 05:50:51 -07:00
const passcode = getNemesisPasscode ( inventory . Nemesis ! ) ;
2025-03-22 07:30:16 -07:00
let guessResult = 0 ;
if ( inventory . Nemesis ! . Faction == "FC_INFESTATION" ) {
for ( let i = 0 ; i != 3 ; ++ i ) {
if ( body . guess [ i ] == passcode [ 0 ] ) {
guessResult = 1 + i ;
break ;
}
}
} else {
for ( let i = 0 ; i != 3 ; ++ i ) {
2025-06-07 02:16:50 -07:00
if ( body . guess [ i ] == passcode [ i ] || body . guess [ i ] == GUESS_WILDCARD ) {
2025-03-22 07:30:16 -07:00
++ guessResult ;
}
}
}
res . json ( { GuessResult : guessResult } ) ;
2025-04-13 05:50:51 -07:00
} else if ( req . query . mode == "r" ) {
const inventory = await getInventory (
2025-05-17 23:29:23 -07:00
account . _id . toString ( ) ,
2025-04-13 05:50:51 -07:00
"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 ] ;
2025-06-07 12:14:17 +02:00
const result1 = passcode == guess [ 0 ] ? GUESS_CORRECT : GUESS_INCORRECT ;
const result2 = passcode == guess [ 1 ] ? GUESS_CORRECT : GUESS_INCORRECT ;
const result3 = passcode == guess [ 2 ] ? GUESS_CORRECT : GUESS_INCORRECT ;
2025-04-13 05:50:51 -07:00
inventory . Nemesis ! . GuessHistory . push (
2025-06-07 11:48:40 +02:00
encodeNemesisGuess ( [
{
symbol : guess [ 0 ] ,
result : result1
} ,
{
symbol : guess [ 1 ] ,
result : result2
} ,
{
symbol : guess [ 2 ] ,
result : result3
}
] )
2025-04-13 05:50:51 -07:00
) ;
2025-04-28 13:59:54 -07:00
// Increase antivirus if correct antivirus mod is installed
2025-04-13 05:50:51 -07:00
const response : IKnifeResponse = { } ;
2025-06-07 12:14:17 +02:00
if ( result1 == GUESS_CORRECT || result2 == GUESS_CORRECT || result3 == GUESS_CORRECT ) {
2025-04-28 13:59:54 -07:00
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 ! ;
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 ;
}
2025-04-13 05:50:51 -07:00
}
2025-04-28 13:59:54 -07:00
inventory . Nemesis ! . HenchmenKilled += antivirusGain ;
2025-04-13 05:50:51 -07:00
}
2025-04-28 13:59:54 -07:00
2025-04-13 05:50:51 -07:00
if ( inventory . Nemesis ! . HenchmenKilled >= 100 ) {
inventory . Nemesis ! . HenchmenKilled = 100 ;
}
2025-05-17 23:29:23 -07:00
inventory . Nemesis ! . InfNodes = getInfNodes ( getNemesisManifest ( inventory . Nemesis ! . manifest ) , 0 ) ;
2025-04-13 05:50:51 -07:00
await inventory . save ( ) ;
res . json ( response ) ;
} else {
2025-06-07 12:14:17 +02:00
// For first guess, create a new entry.
if ( body . position == 0 ) {
inventory . Nemesis ! . GuessHistory . push (
encodeNemesisGuess ( [
{
symbol : GUESS_NONE ,
result : GUESS_NEUTRAL
} ,
{
symbol : GUESS_NONE ,
result : GUESS_NEUTRAL
} ,
{
symbol : GUESS_NONE ,
result : GUESS_NEUTRAL
}
] )
) ;
}
// Evaluate guess
const correct =
body . guess == GUESS_WILDCARD || getNemesisPasscode ( inventory . Nemesis ! ) [ body . position ] == body . guess ;
// Update entry
const guess = decodeNemesisGuess (
inventory . Nemesis ! . GuessHistory [ inventory . Nemesis ! . GuessHistory . length - 1 ]
) ;
guess [ body . position ] . symbol = body . guess ;
guess [ body . position ] . result = correct ? GUESS_CORRECT : GUESS_INCORRECT ;
inventory . Nemesis ! . GuessHistory [ inventory . Nemesis ! . GuessHistory . length - 1 ] = encodeNemesisGuess ( guess ) ;
// Increase rank if incorrect
2025-06-06 16:59:13 -07:00
let RankIncrease : number | undefined ;
2025-06-07 12:14:17 +02:00
if ( ! correct ) {
RankIncrease = 1 ;
const manifest = getNemesisManifest ( inventory . Nemesis ! . manifest ) ;
inventory . Nemesis ! . Rank = Math . min ( inventory . Nemesis ! . Rank + 1 , manifest . systemIndexes . length - 1 ) ;
inventory . Nemesis ! . InfNodes = getInfNodes ( manifest , inventory . Nemesis ! . Rank ) ;
2025-04-13 05:50:51 -07:00
}
2025-06-07 12:14:17 +02:00
await inventory . save ( ) ;
2025-06-06 16:59:13 -07:00
res . json ( { RankIncrease } ) ;
2025-04-13 05:50:51 -07:00
}
} else if ( ( req . query . mode as string ) == "rs" ) {
// report spawn; POST but no application data in body
2025-05-17 23:29:23 -07:00
const inventory = await getInventory ( account . _id . toString ( ) , "Nemesis" ) ;
2025-04-13 05:50:51 -07:00
inventory . Nemesis ! . LastEnc = inventory . Nemesis ! . MissionCount ;
await inventory . save ( ) ;
res . json ( { LastEnc : inventory.Nemesis ! . LastEnc } ) ;
2025-03-20 15:27:37 -07:00
} else if ( ( req . query . mode as string ) == "s" ) {
2025-05-17 23:29:23 -07:00
const inventory = await getInventory ( account . _id . toString ( ) , "Nemesis" ) ;
2025-06-07 02:17:05 -07:00
if ( inventory . Nemesis ) {
logger . warn ( ` overwriting an existing nemesis as a new one is being requested ` ) ;
}
2025-03-20 05:36:09 -07:00
const body = getJSONfromString < INemesisStartRequest > ( String ( req . body ) ) ;
2025-03-20 15:27:15 -07:00
body . target . fp = BigInt ( body . target . fp ) ;
2025-05-17 23:29:23 -07:00
const manifest = getNemesisManifest ( body . target . manifest ) ;
2025-06-06 08:59:22 -07:00
if ( account . BuildLabel && version_compare ( account . BuildLabel , manifest . minBuild ) < 0 ) {
2025-05-17 23:29:23 -07:00
logger . warn (
` 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. `
) ;
}
2025-03-20 15:27:15 -07:00
let weaponIdx = - 1 ;
2025-03-22 06:08:00 -07:00
if ( body . target . Faction != "FC_INFESTATION" ) {
2025-05-17 23:29:23 -07:00
const weapons : readonly string [ ] = manifest . weapons ;
2025-03-20 15:27:15 -07:00
const initialWeaponIdx = new SRng ( body . target . fp ) . randomInt ( 0 , weapons . length - 1 ) ;
weaponIdx = initialWeaponIdx ;
2025-06-07 02:17:05 -07:00
if ( body . target . DisallowedWeapons ) {
do {
const weapon = weapons [ weaponIdx ] ;
if ( body . target . DisallowedWeapons . indexOf ( weapon ) == - 1 ) {
break ;
}
weaponIdx = ( weaponIdx + 1 ) % weapons . length ;
} while ( weaponIdx != initialWeaponIdx ) ;
}
2025-03-20 05:36:09 -07:00
}
inventory . Nemesis = {
fp : body.target.fp ,
manifest : body.target.manifest ,
KillingSuit : body.target.KillingSuit ,
killingDamageType : body.target.killingDamageType ,
ShoulderHelmet : body.target.ShoulderHelmet ,
WeaponIdx : weaponIdx ,
AgentIdx : body.target.AgentIdx ,
BirthNode : body.target.BirthNode ,
Faction : body.target.Faction ,
Rank : 0 ,
k : false ,
Traded : false ,
d : new Date ( ) ,
2025-05-17 23:29:23 -07:00
InfNodes : getInfNodes ( manifest , 0 ) ,
2025-03-20 05:36:09 -07:00
GuessHistory : [ ] ,
Hints : [ ] ,
HintProgress : 0 ,
2025-06-07 02:17:05 -07:00
Weakened : false ,
2025-03-20 05:36:09 -07:00
PrevOwners : 0 ,
HenchmenKilled : 0 ,
2025-06-07 02:17:05 -07:00
SecondInCommand : false ,
2025-03-22 06:08:00 -07:00
MissionCount : 0 ,
LastEnc : 0
2025-03-20 05:36:09 -07:00
} ;
await inventory . save ( ) ;
res . json ( {
target : inventory.toJSON ( ) . Nemesis
} ) ;
2025-04-28 13:59:54 -07:00
} else if ( ( req . query . mode as string ) == "w" ) {
const inventory = await getInventory (
2025-05-17 23:29:23 -07:00
account . _id . toString ( ) ,
2025-04-28 13:59:54 -07:00
"Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades"
) ;
//const body = getJSONfromString<INemesisWeakenRequest>(String(req.body));
inventory . Nemesis ! . InfNodes = [
{
2025-05-17 23:29:23 -07:00
Node : getNemesisManifest ( inventory . Nemesis ! . manifest ) . showdownNode ,
2025-04-28 13:59:54 -07:00
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 ) ;
2025-03-20 05:36:09 -07:00
} else {
logger . debug ( ` data provided to ${ req . path } : ${ String ( req . body ) } ` ) ;
throw new Error ( ` unknown nemesis mode: ${ String ( req . query . mode ) } ` ) ;
}
} ;
2025-03-20 15:27:37 -07:00
interface IValenceFusionRequest {
DestWeapon : IOid ;
SourceWeapon : IOid ;
Category : TEquipmentKey ;
UseSourceDmgType : boolean ;
}
interface INemesisStartRequest {
2025-03-20 05:36:09 -07:00
target : {
fp : number | bigint ;
manifest : string ;
KillingSuit : string ;
killingDamageType : number ;
ShoulderHelmet : string ;
2025-06-07 02:17:05 -07:00
DisallowedWeapons? : string [ ] ;
2025-03-20 05:36:09 -07:00
WeaponIdx : number ;
AgentIdx : number ;
BirthNode : string ;
2025-05-13 12:21:10 +02:00
Faction : TNemesisFaction ;
2025-03-20 05:36:09 -07:00
Rank : number ;
k : boolean ;
Traded : boolean ;
d : IMongoDate ;
InfNodes : [ ] ;
GuessHistory : [ ] ;
Hints : [ ] ;
HintProgress : number ;
Weakened : boolean ;
PrevOwners : number ;
HenchmenKilled : number ;
2025-03-20 15:27:15 -07:00
MissionCount? : number ; // Added in 38.5.0
LastEnc? : number ; // Added in 38.5.0
2025-03-20 05:36:09 -07:00
SecondInCommand : boolean ;
} ;
}
2025-03-22 07:30:16 -07:00
interface INemesisPrespawnCheckRequest {
guess : number [ ] ; // .length == 3
potency? : number [ ] ;
}
2025-04-13 05:50:51 -07:00
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
2025-04-28 13:59:54 -07:00
knife? : IKnife ;
}
// interface INemesisWeakenRequest {
// target: INemesisClient;
// knife: IKnife;
// }
interface IKnife {
Item : IEquipmentClient ;
Skins : IWeaponSkinClient [ ] ;
ModSlot : number ;
CustSlot : number ;
AttachedUpgrades : IUpgradeClient [ ] ;
HiddenWhenHolstered : boolean ;
2025-04-13 05:50:51 -07:00
}