diff --git a/src/controllers/api/claimCompletedRecipeController.ts b/src/controllers/api/claimCompletedRecipeController.ts index a2c8139f..f1fa6f6e 100644 --- a/src/controllers/api/claimCompletedRecipeController.ts +++ b/src/controllers/api/claimCompletedRecipeController.ts @@ -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(); diff --git a/src/controllers/api/nemesisController.ts b/src/controllers/api/nemesisController.ts index 6f789e98..252092f5 100644 --- a/src/controllers/api/nemesisController.ts +++ b/src/controllers/api/nemesisController.ts @@ -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" -]; diff --git a/src/helpers/nemesisHelpers.ts b/src/helpers/nemesisHelpers.ts index ce4190fa..6543813f 100644 --- a/src/helpers/nemesisHelpers.ts +++ b/src/helpers/nemesisHelpers.ts @@ -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); +}; diff --git a/src/models/inventoryModels/inventoryModel.ts b/src/models/inventoryModels/inventoryModel.ts index ad1b06ad..7dae8540 100644 --- a/src/models/inventoryModels/inventoryModel.ts +++ b/src/models/inventoryModels/inventoryModel.ts @@ -1039,6 +1039,8 @@ const pendingRecipeSchema = new Schema( { 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( 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( //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) diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index ebce1ee6..90ed22f5 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -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 => { // 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 = { + 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) + }); +}; diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index a76f9292..3ab2d680 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -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)) { diff --git a/src/types/inventoryTypes/inventoryTypes.ts b/src/types/inventoryTypes/inventoryTypes.ts index 2cec4037..e294e1ce 100644 --- a/src/types/inventoryTypes/inventoryTypes.ts +++ b/src/types/inventoryTypes/inventoryTypes.ts @@ -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", diff --git a/src/types/requestTypes.ts b/src/types/requestTypes.ts index 821b4d2f..976c48f5 100644 --- a/src/types/requestTypes.ts +++ b/src/types/requestTypes.ts @@ -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;