feat: handle NemesisKillConvert at missionInventoryUpdate (#1880)
Some checks failed
Build Docker image / docker (push) Has been cancelled
Build / build (push) Has been cancelled

Closes #1848

Reviewed-on: #1880
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-27 12:38:48 -07:00 committed by Sainan
parent 9e94083875
commit ee1a49f5f2
8 changed files with 344 additions and 61 deletions

View File

@ -133,7 +133,14 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
if (recipe.secretIngredientAction != "SIA_UNBRAND") {
InventoryChanges = {
...InventoryChanges,
...(await addItem(inventory, recipe.resultType, recipe.num, false))
...(await addItem(
inventory,
recipe.resultType,
recipe.num,
false,
undefined,
pendingRecipe.TargetFingerprint
))
};
}
await inventory.save();

View File

@ -3,6 +3,7 @@ import {
encodeNemesisGuess,
getInfNodes,
getNemesisPasscode,
getWeaponsForManifest,
IKnifeResponse
} from "@/src/helpers/nemesisHelpers";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
@ -170,18 +171,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
let weaponIdx = -1;
if (body.target.Faction != "FC_INFESTATION") {
let weapons: readonly string[];
if (body.target.manifest == "/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionSix") {
weapons = kuvaLichVersionSixWeapons;
} else if (
body.target.manifest == "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionFour" ||
body.target.manifest == "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionThree"
) {
weapons = corpusVersionThreeWeapons;
} else {
throw new Error(`unknown nemesis manifest: ${body.target.manifest}`);
}
const weapons = getWeaponsForManifest(body.target.manifest);
const initialWeaponIdx = new SRng(body.target.fp).randomInt(0, weapons.length - 1);
weaponIdx = initialWeaponIdx;
do {
@ -283,39 +273,3 @@ interface INemesisRequiemRequest {
HiddenWhenHolstered: boolean;
};
}
const kuvaLichVersionSixWeapons = [
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Drakgoon/KuvaDrakgoon",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Karak/KuvaKarak",
"/Lotus/Weapons/Grineer/Melee/GrnKuvaLichScythe/GrnKuvaLichScytheWeapon",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Kohm/KuvaKohm",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Ogris/KuvaOgris",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Quartakk/KuvaQuartakk",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Tonkor/KuvaTonkor",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Brakk/KuvaBrakk",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Kraken/KuvaKraken",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Seer/KuvaSeer",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Stubba/KuvaStubba",
"/Lotus/Weapons/Grineer/HeavyWeapons/GrnHeavyGrenadeLauncher",
"/Lotus/Weapons/Grineer/LongGuns/GrnKuvaLichRifle/GrnKuvaLichRifleWeapon",
"/Lotus/Weapons/Grineer/Bows/GrnBow/GrnBowWeapon",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hind/KuvaHind",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Nukor/KuvaNukor",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hek/KuvaHekWeapon",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Zarr/KuvaZarr",
"/Lotus/Weapons/Grineer/KuvaLich/HeavyWeapons/Grattler/KuvaGrattler",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Sobek/KuvaSobek"
];
const corpusVersionThreeWeapons = [
"/Lotus/Weapons/Corpus/LongGuns/CrpBriefcaseLauncher/CrpBriefcaseLauncher",
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEArcaPlasmor/CrpBEArcaPlasmor",
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEFluxRifle/CrpBEFluxRifle",
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBETetra/CrpBETetra",
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBECycron/CrpBECycron",
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEDetron/CrpBEDetron",
"/Lotus/Weapons/Corpus/Pistols/CrpIgniterPistol/CrpIgniterPistol",
"/Lotus/Weapons/Corpus/Pistols/CrpBriefcaseAkimbo/CrpBriefcaseAkimboPistol",
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEPlinx/CrpBEPlinxWeapon",
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEGlaxion/CrpBEGlaxion"
];

View File

@ -1,4 +1,4 @@
import { ExportRegions } from "warframe-public-export-plus";
import { ExportRegions, ExportWarframes } from "warframe-public-export-plus";
import { IInfNode } from "@/src/types/inventoryTypes/inventoryTypes";
import { SRng } from "@/src/services/rngService";
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
@ -129,3 +129,147 @@ export const consumeModCharge = (
response.UpgradeNew.push(true);
}
};
const kuvaLichVersionSixWeapons = [
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Drakgoon/KuvaDrakgoon",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Karak/KuvaKarak",
"/Lotus/Weapons/Grineer/Melee/GrnKuvaLichScythe/GrnKuvaLichScytheWeapon",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Kohm/KuvaKohm",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Ogris/KuvaOgris",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Quartakk/KuvaQuartakk",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Tonkor/KuvaTonkor",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Brakk/KuvaBrakk",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Kraken/KuvaKraken",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Seer/KuvaSeer",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Stubba/KuvaStubba",
"/Lotus/Weapons/Grineer/HeavyWeapons/GrnHeavyGrenadeLauncher",
"/Lotus/Weapons/Grineer/LongGuns/GrnKuvaLichRifle/GrnKuvaLichRifleWeapon",
"/Lotus/Weapons/Grineer/Bows/GrnBow/GrnBowWeapon",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hind/KuvaHind",
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Nukor/KuvaNukor",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hek/KuvaHekWeapon",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Zarr/KuvaZarr",
"/Lotus/Weapons/Grineer/KuvaLich/HeavyWeapons/Grattler/KuvaGrattler",
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Sobek/KuvaSobek"
];
const corpusVersionThreeWeapons = [
"/Lotus/Weapons/Corpus/LongGuns/CrpBriefcaseLauncher/CrpBriefcaseLauncher",
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEArcaPlasmor/CrpBEArcaPlasmor",
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEFluxRifle/CrpBEFluxRifle",
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBETetra/CrpBETetra",
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBECycron/CrpBECycron",
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEDetron/CrpBEDetron",
"/Lotus/Weapons/Corpus/Pistols/CrpIgniterPistol/CrpIgniterPistol",
"/Lotus/Weapons/Corpus/Pistols/CrpBriefcaseAkimbo/CrpBriefcaseAkimboPistol",
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEPlinx/CrpBEPlinxWeapon",
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEGlaxion/CrpBEGlaxion"
];
export const getWeaponsForManifest = (manifest: string): readonly string[] => {
switch (manifest) {
case "/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionSix":
return kuvaLichVersionSixWeapons;
case "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionThree":
case "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionFour":
return corpusVersionThreeWeapons;
}
throw new Error(`unknown nemesis manifest: ${manifest}`);
};
// TODO: This sucks.
export const getInnateDamageTag = (
KillingSuit: string
):
| "InnateElectricityDamage"
| "InnateFreezeDamage"
| "InnateHeatDamage"
| "InnateImpactDamage"
| "InnateMagDamage"
| "InnateRadDamage"
| "InnateToxinDamage" => {
const baseSuitType = ExportWarframes[KillingSuit].parentName;
switch (baseSuitType) {
case "/Lotus/Powersuits/Volt/VoltBaseSuit":
case "/Lotus/Powersuits/Excalibur/ExcaliburBaseSuit":
case "/Lotus/Powersuits/AntiMatter/NovaBaseSuit":
case "/Lotus/Powersuits/Banshee/BansheeBaseSuit":
case "/Lotus/Powersuits/Berserker/BerserkerBaseSuit":
case "/Lotus/Powersuits/Magician/MagicianBaseSuit":
case "/Lotus/Powersuits/Sentient/SentientBaseSuit":
case "/Lotus/Powersuits/Gyre/GyreBaseSuit":
return "InnateElectricityDamage";
case "/Lotus/Powersuits/Ember/EmberBaseSuit":
case "/Lotus/Powersuits/Dragon/DragonBaseSuit":
case "/Lotus/Powersuits/Nezha/NezhaBaseSuit":
case "/Lotus/Powersuits/Sandman/SandmanBaseSuit":
case "/Lotus/Powersuits/Trapper/TrapperBaseSuit":
case "/Lotus/Powersuits/Wisp/WispBaseSuit":
case "/Lotus/Powersuits/Odalisk/OdaliskBaseSuit":
case "/Lotus/Powersuits/PaxDuviricus/PaxDuviricusBaseSuit":
case "/Lotus/Powersuits/Choir/ChoirBaseSuit":
case "/Lotus/Powersuits/Temple/TempleBaseSuit":
return "InnateHeatDamage";
case "/Lotus/Powersuits/Frost/FrostBaseSuit":
case "/Lotus/Powersuits/Glass/GlassBaseSuit":
case "/Lotus/Powersuits/Fairy/FairyBaseSuit":
case "/Lotus/Powersuits/IronFrame/IronFrameBaseSuit":
case "/Lotus/Powersuits/Revenant/RevenantBaseSuit":
case "/Lotus/Powersuits/Trinity/TrinityBaseSuit":
case "/Lotus/Powersuits/Hoplite/HopliteBaseSuit":
case "/Lotus/Powersuits/Koumei/KoumeiBaseSuit":
return "InnateFreezeDamage";
case "/Lotus/Powersuits/Saryn/SarynBaseSuit":
case "/Lotus/Powersuits/Paladin/PaladinBaseSuit":
case "/Lotus/Powersuits/Brawler/BrawlerBaseSuit":
case "/Lotus/Powersuits/Infestation/InfestationBaseSuit":
case "/Lotus/Powersuits/Necro/NecroBaseSuit":
case "/Lotus/Powersuits/Khora/KhoraBaseSuit":
case "/Lotus/Powersuits/Ranger/RangerBaseSuit":
case "/Lotus/Powersuits/Dagath/DagathBaseSuit":
return "InnateToxinDamage";
case "/Lotus/Powersuits/Mag/MagBaseSuit":
case "/Lotus/Powersuits/Pirate/PirateBaseSuit":
case "/Lotus/Powersuits/Cowgirl/CowgirlBaseSuit":
case "/Lotus/Powersuits/Priest/PriestBaseSuit":
case "/Lotus/Powersuits/BrokenFrame/BrokenFrameBaseSuit":
case "/Lotus/Powersuits/Alchemist/AlchemistBaseSuit":
case "/Lotus/Powersuits/Yareli/YareliBaseSuit":
case "/Lotus/Powersuits/Geode/GeodeBaseSuit":
case "/Lotus/Powersuits/Frumentarius/FrumentariusBaseSuit":
return "InnateMagDamage";
case "/Lotus/Powersuits/Loki/LokiBaseSuit":
case "/Lotus/Powersuits/Ninja/NinjaBaseSuit":
case "/Lotus/Powersuits/Jade/JadeBaseSuit":
case "/Lotus/Powersuits/Bard/BardBaseSuit":
case "/Lotus/Powersuits/Harlequin/HarlequinBaseSuit":
case "/Lotus/Powersuits/Garuda/GarudaBaseSuit":
case "/Lotus/Powersuits/YinYang/YinYangBaseSuit":
case "/Lotus/Powersuits/Werewolf/WerewolfBaseSuit":
case "/Lotus/Powersuits/ConcreteFrame/ConcreteFrameBaseSuit":
return "InnateRadDamage";
case "/Lotus/Powersuits/Rhino/RhinoBaseSuit":
case "/Lotus/Powersuits/Tengu/TenguBaseSuit":
case "/Lotus/Powersuits/MonkeyKing/MonkeyKingBaseSuit":
case "/Lotus/Powersuits/Runner/RunnerBaseSuit":
case "/Lotus/Powersuits/Pacifist/PacifistBaseSuit":
case "/Lotus/Powersuits/Devourer/DevourerBaseSuit":
case "/Lotus/Powersuits/Wraith/WraithBaseSuit":
case "/Lotus/Powersuits/Pagemaster/PagemasterBaseSuit":
return "InnateImpactDamage";
}
logger.warn(`unknown innate damage type for ${KillingSuit}, using heat as a fallback`);
return "InnateHeatDamage";
};
// TODO: For -1399275245665749231n, the value should be 75306944, but we're off by 59 with 75307003.
export const getInnateDamageValue = (fp: bigint): number => {
const rng = new SRng(fp);
rng.randomFloat(); // used for the weapon index
const WeaponUpgradeValueAttenuationExponent = 2.25;
let value = Math.pow(rng.randomFloat(), WeaponUpgradeValueAttenuationExponent);
if (value >= 0.941428) {
value = 1;
}
return Math.trunc(value * 0x40000000);
};

View File

@ -1039,6 +1039,8 @@ const pendingRecipeSchema = new Schema<IPendingRecipeDatabase>(
{
ItemType: String,
CompletionDate: Date,
TargetItemId: String,
TargetFingerprint: String,
LongGuns: { type: [EquipmentSchema], default: undefined },
Pistols: { type: [EquipmentSchema], default: undefined },
Melee: { type: [EquipmentSchema], default: undefined },
@ -1260,11 +1262,11 @@ const nemesisSchema = new Schema<INemesisDatabase>(
PrevOwners: Number,
SecondInCommand: Boolean,
Weakened: Boolean,
InfNodes: [infNodeSchema],
InfNodes: { type: [infNodeSchema], default: undefined },
HenchmenKilled: Number,
HintProgress: Number,
Hints: [Number],
GuessHistory: [Number],
Hints: { type: [Number], default: undefined },
GuessHistory: { type: [Number], default: undefined },
MissionCount: Number,
LastEnc: Number
},
@ -1609,7 +1611,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
//CorpusLich or GrineerLich
NemesisAbandonedRewards: { type: [String], default: [] },
Nemesis: nemesisSchema,
NemesisHistory: [Schema.Types.Mixed],
NemesisHistory: { type: [nemesisSchema], default: undefined },
LastNemesisAllySpawnTime: Schema.Types.Mixed,
//TradingRulesConfirmed,ShowFriendInvNotifications(Option->Social)

View File

@ -26,7 +26,9 @@ import {
Status,
IKubrowPetDetailsDatabase,
ITraits,
ICalendarProgress
ICalendarProgress,
INemesisWeaponTargetFingerprint,
INemesisPetTargetFingerprint
} from "@/src/types/inventoryTypes/inventoryTypes";
import { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/genericUpdate";
import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes";
@ -79,6 +81,7 @@ import { getRandomElement, getRandomInt, getRandomWeightedReward, SRng } from ".
import { createMessage } from "./inboxService";
import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper";
import { getWorldState } from "./worldStateService";
import { getInnateDamageTag, getInnateDamageValue } from "../helpers/nemesisHelpers";
export const createInventory = async (
accountOwnerId: Types.ObjectId,
@ -327,7 +330,8 @@ export const addItem = async (
typeName: string,
quantity: number = 1,
premiumPurchase: boolean = false,
seed?: bigint
seed?: bigint,
targetFingerprint?: string
): Promise<IInventoryChanges> => {
// Bundles are technically StoreItems but a) they don't have a normal counterpart, and b) they are used in non-StoreItem contexts, e.g. email attachments.
if (typeName in ExportBundles) {
@ -530,6 +534,12 @@ export const addItem = async (
]
});
}
if (targetFingerprint) {
const targetFingerprintObj = JSON.parse(targetFingerprint) as INemesisWeaponTargetFingerprint;
defaultOverwrites.UpgradeType = targetFingerprintObj.ItemType;
defaultOverwrites.UpgradeFingerprint = JSON.stringify(targetFingerprintObj.UpgradeFingerprint);
defaultOverwrites.ItemName = targetFingerprintObj.Name;
}
const inventoryChanges = addEquipment(inventory, weapon.productCategory, typeName, defaultOverwrites);
if (weapon.additionalItems) {
for (const item of weapon.additionalItems) {
@ -544,6 +554,27 @@ export const addItem = async (
premiumPurchase
)
};
} else if (targetFingerprint) {
// Sister's Hound
const targetFingerprintObj = JSON.parse(targetFingerprint) as INemesisPetTargetFingerprint;
const head = targetFingerprintObj.Parts[0];
const defaultOverwrites: Partial<IEquipmentDatabase> = {
ModularParts: targetFingerprintObj.Parts,
ItemName: targetFingerprintObj.Name,
Configs: applyDefaultUpgrades(inventory, ExportWeapons[head].defaultUpgrades)
};
const itemType = {
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA":
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetAPowerSuit",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB":
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetBPowerSuit",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC":
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetCPowerSuit"
}[head] as string;
return {
...addEquipment(inventory, "MoaPets", itemType, defaultOverwrites),
...occupySlot(inventory, InventorySlot.SENTINELS, premiumPurchase)
};
} else {
// Modular weapon parts
const miscItemChanges = [
@ -1851,3 +1882,78 @@ export const getCalendarProgress = (inventory: TInventoryDatabaseDocument): ICal
return inventory.CalendarProgress;
};
export const giveNemesisWeaponRecipe = (
inventory: TInventoryDatabaseDocument,
weaponType: string,
nemesisName: string = "AGOR ROK",
weaponLoc?: string,
KillingSuit: string = "/Lotus/Powersuits/Ember/Ember",
fp: bigint = generateRewardSeed()
): void => {
if (!weaponLoc) {
weaponLoc = ExportWeapons[weaponType].name;
}
const recipeType = Object.entries(ExportRecipes).find(arr => arr[1].resultType == weaponType)![0];
addRecipes(inventory, [
{
ItemType: recipeType,
ItemCount: 1
}
]);
inventory.PendingRecipes.push({
CompletionDate: new Date(),
ItemType: recipeType,
TargetFingerprint: JSON.stringify({
ItemType: "/Lotus/Weapons/Grineer/KuvaLich/Upgrades/InnateDamageRandomMod",
UpgradeFingerprint: {
compat: weaponType,
buffs: [
{
Tag: getInnateDamageTag(KillingSuit),
Value: getInnateDamageValue(fp)
}
]
},
Name: weaponLoc + "|" + nemesisName
} satisfies INemesisWeaponTargetFingerprint)
});
};
export const giveNemesisPetRecipe = (inventory: TInventoryDatabaseDocument, nemesisName: string = "AGOR ROK"): void => {
const head = getRandomElement([
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC"
]);
const body = getRandomElement([
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyA",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyB",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyC"
]);
const legs = getRandomElement([
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartLegsA",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartLegsB",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartLegsC"
]);
const tail = getRandomElement([
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartTailA",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartTailB",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartTailC"
]);
const recipeType = Object.entries(ExportRecipes).find(arr => arr[1].resultType == head)![0];
addRecipes(inventory, [
{
ItemType: recipeType,
ItemCount: 1
}
]);
inventory.PendingRecipes.push({
CompletionDate: new Date(),
ItemType: recipeType,
TargetFingerprint: JSON.stringify({
Parts: [head, body, legs, tail],
Name: "/Lotus/Language/Pets/ZanukaPetName|" + nemesisName
} satisfies INemesisPetTargetFingerprint)
});
};

View File

@ -35,6 +35,8 @@ import {
combineInventoryChanges,
generateRewardSeed,
getCalendarProgress,
giveNemesisPetRecipe,
giveNemesisWeaponRecipe,
updateCurrency,
updateSyndicate
} from "@/src/services/inventoryService";
@ -53,7 +55,7 @@ import kuriaMessage50 from "@/static/fixed_responses/kuriaMessages/fiftyPercent.
import kuriaMessage75 from "@/static/fixed_responses/kuriaMessages/seventyFivePercent.json";
import kuriaMessage100 from "@/static/fixed_responses/kuriaMessages/oneHundredPercent.json";
import conservationAnimals from "@/static/fixed_responses/conservationAnimals.json";
import { getInfNodes } from "@/src/helpers/nemesisHelpers";
import { getInfNodes, getWeaponsForManifest } from "@/src/helpers/nemesisHelpers";
import { Loadout } from "../models/inventoryModels/loadoutModel";
import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes";
import { getLiteSortie, getWorldState, idToWeek } from "./worldStateService";
@ -612,6 +614,52 @@ export const addMissionInventoryUpdates = async (
}
break;
}
case "NemesisKillConvert":
if (inventory.Nemesis) {
inventory.NemesisHistory ??= [];
inventory.NemesisHistory.push({
// Copy over all 'base' values
fp: inventory.Nemesis.fp,
d: inventory.Nemesis.d,
manifest: inventory.Nemesis.manifest,
KillingSuit: inventory.Nemesis.KillingSuit,
killingDamageType: inventory.Nemesis.killingDamageType,
ShoulderHelmet: inventory.Nemesis.ShoulderHelmet,
WeaponIdx: inventory.Nemesis.WeaponIdx,
AgentIdx: inventory.Nemesis.AgentIdx,
BirthNode: inventory.Nemesis.BirthNode,
Faction: inventory.Nemesis.Faction,
Rank: inventory.Nemesis.Rank,
Traded: inventory.Nemesis.Traded,
PrevOwners: inventory.Nemesis.PrevOwners,
SecondInCommand: inventory.Nemesis.SecondInCommand,
Weakened: inventory.Nemesis.Weakened,
// And set killed flag
k: value.killed
});
if (value.killed) {
if (value.weaponLoc) {
const weaponType = getWeaponsForManifest(inventory.Nemesis.manifest)[
inventory.Nemesis.WeaponIdx
];
giveNemesisWeaponRecipe(
inventory,
weaponType,
value.nemesisName,
value.weaponLoc,
inventory.Nemesis.KillingSuit,
inventory.Nemesis.fp
);
}
if (value.petLoc) {
giveNemesisPetRecipe(inventory);
}
}
inventory.Nemesis = undefined;
}
break;
default:
// Equipment XP updates
if (equipmentKeys.includes(key as TEquipmentKey)) {

View File

@ -43,6 +43,7 @@ export interface IInventoryDatabase
| "RecentVendorPurchases"
| "NextRefill"
| "Nemesis"
| "NemesisHistory"
| "EntratiVaultCountResetDate"
| "BrandedSuits"
| "LockedWeaponGroup"
@ -79,6 +80,7 @@ export interface IInventoryDatabase
RecentVendorPurchases?: IRecentVendorPurchaseDatabase[];
NextRefill?: Date;
Nemesis?: INemesisDatabase;
NemesisHistory?: INemesisBaseDatabase[];
EntratiVaultCountResetDate?: Date;
BrandedSuits?: Types.ObjectId[];
LockedWeaponGroup?: ILockedWeaponGroupDatabase;
@ -313,7 +315,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
EquippedInstrument?: string;
InvasionChainProgress: IInvasionChainProgress[];
Nemesis?: INemesisClient;
NemesisHistory: INemesisBaseClient[];
NemesisHistory?: INemesisBaseClient[];
LastNemesisAllySpawnTime?: IMongoDate;
Settings?: ISettings;
PersonalTechProjects: IPersonalTechProjectClient[];
@ -902,8 +904,8 @@ export interface IPendingRecipeDatabase {
ItemType: string;
CompletionDate: Date;
ItemId: IOid;
TargetItemId?: string; // likely related to liches
TargetFingerprint?: string; // likely related to liches
TargetItemId?: string; // unsure what this is for
TargetFingerprint?: string;
LongGuns?: IEquipmentDatabase[];
Pistols?: IEquipmentDatabase[];
Melee?: IEquipmentDatabase[];
@ -951,6 +953,17 @@ export interface ICrewShipComponentFingerprint extends IInnateDamageFingerprint
SubroutineIndex?: number;
}
export interface INemesisWeaponTargetFingerprint {
ItemType: string;
UpgradeFingerprint: IInnateDamageFingerprint;
Name: string;
}
export interface INemesisPetTargetFingerprint {
Parts: string[];
Name: string;
}
export enum GettingSlotOrderInfo {
Empty = "",
LotusUpgradesModsRandomizedPlayerMeleeWeaponRandomModRare0 = "/Lotus/Upgrades/Mods/Randomized/PlayerMeleeWeaponRandomModRare:0",

View File

@ -22,7 +22,8 @@ import {
ILoadOutPresets,
IInvasionProgressClient,
IWeaponSkinClient,
IKubrowPetEggClient
IKubrowPetEggClient,
INemesisClient
} from "./inventoryTypes/inventoryTypes";
import { IGroup } from "./loginTypes";
@ -74,6 +75,14 @@ export type IMissionInventoryUpdateRequest = {
PS: string;
ActiveDojoColorResearch: string;
RewardInfo?: IRewardInfo;
NemesisKillConvert?: {
nemesisName: string;
weaponLoc: string;
petLoc: "" | "/Lotus/Language/Pets/ZanukaPetName";
fingerprint: bigint | number;
killed: boolean;
};
target?: INemesisClient;
ReceivedCeremonyMsg: boolean;
LastCeremonyResetDate: number;
MissionPTS: number;