From 8fb676c906a43b8b7fbb18ecab779445d0a1490b Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Tue, 13 May 2025 20:41:49 -0700 Subject: [PATCH] feat: classic lich ephemera reward (#2067) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2067 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/helpers/nemesisHelpers.ts | 96 ++++++++++++++++--- src/services/inventoryService.ts | 39 +++----- src/services/missionInventoryUpdateService.ts | 23 +++-- 3 files changed, 110 insertions(+), 48 deletions(-) diff --git a/src/helpers/nemesisHelpers.ts b/src/helpers/nemesisHelpers.ts index e20d734f..ebfd1067 100644 --- a/src/helpers/nemesisHelpers.ts +++ b/src/helpers/nemesisHelpers.ts @@ -47,6 +47,42 @@ export const showdownNodes: Record = { FC_INFESTATION: "CrewBattleNode559" }; +const ephemeraProbabilities: Record = { + FC_GRINEER: 0.05, + FC_CORPUS: 0.2, + FC_INFESTATION: 0 +}; + +type TInnateDamageTag = + | "InnateElectricityDamage" + | "InnateHeatDamage" + | "InnateFreezeDamage" + | "InnateToxinDamage" + | "InnateMagDamage" + | "InnateRadDamage" + | "InnateImpactDamage"; + +const ephmeraTypes: Record<"FC_GRINEER" | "FC_CORPUS", Record> = { + FC_GRINEER: { + InnateElectricityDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaLightningEphemera", + InnateHeatDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaFireEphemera", + InnateFreezeDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaIceEphemera", + InnateToxinDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaToxinEphemera", + InnateMagDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaMagneticEphemera", + InnateRadDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaTricksterEphemera", + InnateImpactDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaImpactEphemera" + }, + FC_CORPUS: { + InnateElectricityDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraA", + InnateHeatDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraB", + InnateFreezeDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraC", + InnateToxinDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraD", + InnateMagDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraE", + InnateRadDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraF", + InnateImpactDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraG" + } +}; + // Get a parazon 'passcode' based on the nemesis fingerprint so it's always the same for the same nemesis. export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: TNemesisFaction }): number[] => { const rng = new SRng(nemesis.fp); @@ -271,21 +307,25 @@ export const isNemesisCompatibleWithVersion = ( return true; }; -export const getInnateDamageTag = ( - KillingSuit: string -): - | "InnateElectricityDamage" - | "InnateFreezeDamage" - | "InnateHeatDamage" - | "InnateImpactDamage" - | "InnateMagDamage" - | "InnateRadDamage" - | "InnateToxinDamage" => { +export const getInnateDamageTag = (KillingSuit: string): TInnateDamageTag => { return ExportWarframes[KillingSuit].nemesisUpgradeTag!; }; -// TODO: For -1399275245665749231n, the value should be 75306944, but we're off by 59 with 75307003. -export const getInnateDamageValue = (fp: bigint): number => { +export interface INemesisProfile { + innateDamageTag: TInnateDamageTag; + innateDamageValue: number; + ephemera?: string; + petHead?: string; + petBody?: string; + petLegs?: string; + petTail?: string; +} + +export const generateNemesisProfile = ( + fp: bigint = generateRewardSeed(), + Faction: TNemesisFaction = "FC_CORPUS", + killingSuit: string = "/Lotus/Powersuits/Ember/Ember" +): INemesisProfile => { const rng = new SRng(fp); rng.randomFloat(); // used for the weapon index const WeaponUpgradeValueAttenuationExponent = 2.25; @@ -293,7 +333,37 @@ export const getInnateDamageValue = (fp: bigint): number => { if (value >= 0.941428) { value = 1; } - return Math.trunc(value * 0x40000000); + const profile: INemesisProfile = { + innateDamageTag: getInnateDamageTag(killingSuit), + innateDamageValue: Math.trunc(value * 0x40000000) // TODO: For -1399275245665749231n, the value should be 75306944, but we're off by 59 with 75307003. + }; + if (rng.randomFloat() <= ephemeraProbabilities[Faction] && Faction != "FC_INFESTATION") { + profile.ephemera = ephmeraTypes[Faction][profile.innateDamageTag]; + } + rng.randomFloat(); // something related to sentinel agent maybe + if (Faction == "FC_CORPUS") { + profile.petHead = rng.randomElement([ + "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA", + "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB", + "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC" + ])!; + profile.petBody = rng.randomElement([ + "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyA", + "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyB", + "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyC" + ])!; + profile.petLegs = rng.randomElement([ + "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartLegsA", + "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartLegsB", + "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartLegsC" + ])!; + profile.petTail = rng.randomElement([ + "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartTailA", + "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartTailB", + "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartTailC" + ])!; + } + return profile; }; export const getKillTokenRewardCount = (fp: bigint): number => { diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index bbb68167..a4e33ee3 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -84,7 +84,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"; +import { generateNemesisProfile, INemesisProfile } from "../helpers/nemesisHelpers"; export const createInventory = async ( accountOwnerId: Types.ObjectId, @@ -1969,8 +1969,7 @@ export const giveNemesisWeaponRecipe = ( weaponType: string, nemesisName: string = "AGOR ROK", weaponLoc?: string, - KillingSuit: string = "/Lotus/Powersuits/Ember/Ember", - fp: bigint = generateRewardSeed() + profile: INemesisProfile = generateNemesisProfile() ): void => { if (!weaponLoc) { weaponLoc = ExportWeapons[weaponType].name; @@ -1991,8 +1990,8 @@ export const giveNemesisWeaponRecipe = ( compat: weaponType, buffs: [ { - Tag: getInnateDamageTag(KillingSuit), - Value: getInnateDamageValue(fp) + Tag: profile.innateDamageTag, + Value: profile.innateDamageValue } ] }, @@ -2001,27 +2000,15 @@ export const giveNemesisWeaponRecipe = ( }); }; -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" - ])!; +export const giveNemesisPetRecipe = ( + inventory: TInventoryDatabaseDocument, + nemesisName: string = "AGOR ROK", + profile: INemesisProfile = generateNemesisProfile() +): void => { + const head = profile.petHead!; + const body = profile.petBody!; + const legs = profile.petLegs!; + const tail = profile.petTail!; const recipeType = Object.entries(ExportRecipes).find(arr => arr[1].resultType == head)![0]; addRecipes(inventory, [ { diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index 6e90f5e1..45f2a953 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -57,6 +57,7 @@ import kuriaMessage75 from "@/static/fixed_responses/kuriaMessages/seventyFivePe import kuriaMessage100 from "@/static/fixed_responses/kuriaMessages/oneHundredPercent.json"; import conservationAnimals from "@/static/fixed_responses/conservationAnimals.json"; import { + generateNemesisProfile, getInfNodes, getNemesisPasscode, getWeaponsForManifest, @@ -659,6 +660,12 @@ export const addMissionInventoryUpdates = async ( k: value.killed }); + const profile = generateNemesisProfile( + inventory.Nemesis.fp, + inventory.Nemesis.Faction, + inventory.Nemesis.KillingSuit + ); + if (value.killed) { if ( value.weaponLoc && @@ -667,20 +674,18 @@ export const addMissionInventoryUpdates = async ( const weaponType = getWeaponsForManifest(inventory.Nemesis.manifest)[ inventory.Nemesis.WeaponIdx ]; - giveNemesisWeaponRecipe( - inventory, - weaponType, - value.nemesisName, - value.weaponLoc, - inventory.Nemesis.KillingSuit, - inventory.Nemesis.fp - ); + giveNemesisWeaponRecipe(inventory, weaponType, value.nemesisName, value.weaponLoc, profile); } if (value.petLoc) { - giveNemesisPetRecipe(inventory); + giveNemesisPetRecipe(inventory, value.nemesisName, profile); } } + // "Players will receive a Lich's Ephemera regardless of whether they Vanquish or Convert them." + if (profile.ephemera) { + addSkin(inventory, profile.ephemera); + } + // TOVERIFY: Is the inbox message also sent when converting a lich? If not, how are the rewards given? if (inventory.Nemesis.Faction == "FC_INFESTATION") { await sendCodaFinishedMessage(inventory, inventory.Nemesis.fp, value.nemesisName, value.killed);