feat: classic lich ephemera reward (#2067)
Reviewed-on: OpenWF/SpaceNinjaServer#2067 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:
		
							parent
							
								
									2ce5cc4562
								
							
						
					
					
						commit
						8fb676c906
					
				@ -47,6 +47,42 @@ export const showdownNodes: Record<TNemesisFaction, string> = {
 | 
				
			|||||||
    FC_INFESTATION: "CrewBattleNode559"
 | 
					    FC_INFESTATION: "CrewBattleNode559"
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ephemeraProbabilities: Record<TNemesisFaction, number> = {
 | 
				
			||||||
 | 
					    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<TInnateDamageTag, string>> = {
 | 
				
			||||||
 | 
					    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.
 | 
					// 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[] => {
 | 
					export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: TNemesisFaction }): number[] => {
 | 
				
			||||||
    const rng = new SRng(nemesis.fp);
 | 
					    const rng = new SRng(nemesis.fp);
 | 
				
			||||||
@ -271,21 +307,25 @@ export const isNemesisCompatibleWithVersion = (
 | 
				
			|||||||
    return true;
 | 
					    return true;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getInnateDamageTag = (
 | 
					export const getInnateDamageTag = (KillingSuit: string): TInnateDamageTag => {
 | 
				
			||||||
    KillingSuit: string
 | 
					 | 
				
			||||||
):
 | 
					 | 
				
			||||||
    | "InnateElectricityDamage"
 | 
					 | 
				
			||||||
    | "InnateFreezeDamage"
 | 
					 | 
				
			||||||
    | "InnateHeatDamage"
 | 
					 | 
				
			||||||
    | "InnateImpactDamage"
 | 
					 | 
				
			||||||
    | "InnateMagDamage"
 | 
					 | 
				
			||||||
    | "InnateRadDamage"
 | 
					 | 
				
			||||||
    | "InnateToxinDamage" => {
 | 
					 | 
				
			||||||
    return ExportWarframes[KillingSuit].nemesisUpgradeTag!;
 | 
					    return ExportWarframes[KillingSuit].nemesisUpgradeTag!;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: For -1399275245665749231n, the value should be 75306944, but we're off by 59 with 75307003.
 | 
					export interface INemesisProfile {
 | 
				
			||||||
export const getInnateDamageValue = (fp: bigint): number => {
 | 
					    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);
 | 
					    const rng = new SRng(fp);
 | 
				
			||||||
    rng.randomFloat(); // used for the weapon index
 | 
					    rng.randomFloat(); // used for the weapon index
 | 
				
			||||||
    const WeaponUpgradeValueAttenuationExponent = 2.25;
 | 
					    const WeaponUpgradeValueAttenuationExponent = 2.25;
 | 
				
			||||||
@ -293,7 +333,37 @@ export const getInnateDamageValue = (fp: bigint): number => {
 | 
				
			|||||||
    if (value >= 0.941428) {
 | 
					    if (value >= 0.941428) {
 | 
				
			||||||
        value = 1;
 | 
					        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 => {
 | 
					export const getKillTokenRewardCount = (fp: bigint): number => {
 | 
				
			||||||
 | 
				
			|||||||
@ -84,7 +84,7 @@ import { getRandomElement, getRandomInt, getRandomWeightedReward, SRng } from ".
 | 
				
			|||||||
import { createMessage } from "./inboxService";
 | 
					import { createMessage } from "./inboxService";
 | 
				
			||||||
import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper";
 | 
					import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper";
 | 
				
			||||||
import { getWorldState } from "./worldStateService";
 | 
					import { getWorldState } from "./worldStateService";
 | 
				
			||||||
import { getInnateDamageTag, getInnateDamageValue } from "../helpers/nemesisHelpers";
 | 
					import { generateNemesisProfile, INemesisProfile } from "../helpers/nemesisHelpers";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const createInventory = async (
 | 
					export const createInventory = async (
 | 
				
			||||||
    accountOwnerId: Types.ObjectId,
 | 
					    accountOwnerId: Types.ObjectId,
 | 
				
			||||||
@ -1969,8 +1969,7 @@ export const giveNemesisWeaponRecipe = (
 | 
				
			|||||||
    weaponType: string,
 | 
					    weaponType: string,
 | 
				
			||||||
    nemesisName: string = "AGOR ROK",
 | 
					    nemesisName: string = "AGOR ROK",
 | 
				
			||||||
    weaponLoc?: string,
 | 
					    weaponLoc?: string,
 | 
				
			||||||
    KillingSuit: string = "/Lotus/Powersuits/Ember/Ember",
 | 
					    profile: INemesisProfile = generateNemesisProfile()
 | 
				
			||||||
    fp: bigint = generateRewardSeed()
 | 
					 | 
				
			||||||
): void => {
 | 
					): void => {
 | 
				
			||||||
    if (!weaponLoc) {
 | 
					    if (!weaponLoc) {
 | 
				
			||||||
        weaponLoc = ExportWeapons[weaponType].name;
 | 
					        weaponLoc = ExportWeapons[weaponType].name;
 | 
				
			||||||
@ -1991,8 +1990,8 @@ export const giveNemesisWeaponRecipe = (
 | 
				
			|||||||
                compat: weaponType,
 | 
					                compat: weaponType,
 | 
				
			||||||
                buffs: [
 | 
					                buffs: [
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        Tag: getInnateDamageTag(KillingSuit),
 | 
					                        Tag: profile.innateDamageTag,
 | 
				
			||||||
                        Value: getInnateDamageValue(fp)
 | 
					                        Value: profile.innateDamageValue
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                ]
 | 
					                ]
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
@ -2001,27 +2000,15 @@ export const giveNemesisWeaponRecipe = (
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const giveNemesisPetRecipe = (inventory: TInventoryDatabaseDocument, nemesisName: string = "AGOR ROK"): void => {
 | 
					export const giveNemesisPetRecipe = (
 | 
				
			||||||
    const head = getRandomElement([
 | 
					    inventory: TInventoryDatabaseDocument,
 | 
				
			||||||
        "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA",
 | 
					    nemesisName: string = "AGOR ROK",
 | 
				
			||||||
        "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB",
 | 
					    profile: INemesisProfile = generateNemesisProfile()
 | 
				
			||||||
        "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC"
 | 
					): void => {
 | 
				
			||||||
    ])!;
 | 
					    const head = profile.petHead!;
 | 
				
			||||||
    const body = getRandomElement([
 | 
					    const body = profile.petBody!;
 | 
				
			||||||
        "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyA",
 | 
					    const legs = profile.petLegs!;
 | 
				
			||||||
        "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyB",
 | 
					    const tail = profile.petTail!;
 | 
				
			||||||
        "/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];
 | 
					    const recipeType = Object.entries(ExportRecipes).find(arr => arr[1].resultType == head)![0];
 | 
				
			||||||
    addRecipes(inventory, [
 | 
					    addRecipes(inventory, [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 | 
				
			|||||||
@ -57,6 +57,7 @@ import kuriaMessage75 from "@/static/fixed_responses/kuriaMessages/seventyFivePe
 | 
				
			|||||||
import kuriaMessage100 from "@/static/fixed_responses/kuriaMessages/oneHundredPercent.json";
 | 
					import kuriaMessage100 from "@/static/fixed_responses/kuriaMessages/oneHundredPercent.json";
 | 
				
			||||||
import conservationAnimals from "@/static/fixed_responses/conservationAnimals.json";
 | 
					import conservationAnimals from "@/static/fixed_responses/conservationAnimals.json";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					    generateNemesisProfile,
 | 
				
			||||||
    getInfNodes,
 | 
					    getInfNodes,
 | 
				
			||||||
    getNemesisPasscode,
 | 
					    getNemesisPasscode,
 | 
				
			||||||
    getWeaponsForManifest,
 | 
					    getWeaponsForManifest,
 | 
				
			||||||
@ -659,6 +660,12 @@ export const addMissionInventoryUpdates = async (
 | 
				
			|||||||
                        k: value.killed
 | 
					                        k: value.killed
 | 
				
			||||||
                    });
 | 
					                    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    const profile = generateNemesisProfile(
 | 
				
			||||||
 | 
					                        inventory.Nemesis.fp,
 | 
				
			||||||
 | 
					                        inventory.Nemesis.Faction,
 | 
				
			||||||
 | 
					                        inventory.Nemesis.KillingSuit
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if (value.killed) {
 | 
					                    if (value.killed) {
 | 
				
			||||||
                        if (
 | 
					                        if (
 | 
				
			||||||
                            value.weaponLoc &&
 | 
					                            value.weaponLoc &&
 | 
				
			||||||
@ -667,20 +674,18 @@ export const addMissionInventoryUpdates = async (
 | 
				
			|||||||
                            const weaponType = getWeaponsForManifest(inventory.Nemesis.manifest)[
 | 
					                            const weaponType = getWeaponsForManifest(inventory.Nemesis.manifest)[
 | 
				
			||||||
                                inventory.Nemesis.WeaponIdx
 | 
					                                inventory.Nemesis.WeaponIdx
 | 
				
			||||||
                            ];
 | 
					                            ];
 | 
				
			||||||
                            giveNemesisWeaponRecipe(
 | 
					                            giveNemesisWeaponRecipe(inventory, weaponType, value.nemesisName, value.weaponLoc, profile);
 | 
				
			||||||
                                inventory,
 | 
					 | 
				
			||||||
                                weaponType,
 | 
					 | 
				
			||||||
                                value.nemesisName,
 | 
					 | 
				
			||||||
                                value.weaponLoc,
 | 
					 | 
				
			||||||
                                inventory.Nemesis.KillingSuit,
 | 
					 | 
				
			||||||
                                inventory.Nemesis.fp
 | 
					 | 
				
			||||||
                            );
 | 
					 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        if (value.petLoc) {
 | 
					                        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?
 | 
					                    // TOVERIFY: Is the inbox message also sent when converting a lich? If not, how are the rewards given?
 | 
				
			||||||
                    if (inventory.Nemesis.Faction == "FC_INFESTATION") {
 | 
					                    if (inventory.Nemesis.Faction == "FC_INFESTATION") {
 | 
				
			||||||
                        await sendCodaFinishedMessage(inventory, inventory.Nemesis.fp, value.nemesisName, value.killed);
 | 
					                        await sendCodaFinishedMessage(inventory, inventory.Nemesis.fp, value.nemesisName, value.killed);
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user