forked from OpenWF/SpaceNinjaServer
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