feat: classic lich vanquish inbox mesage (#2074)

Closes #1897

Reviewed-on: OpenWF/SpaceNinjaServer#2074
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-05-14 21:14:04 -07:00 committed by Sainan
parent daf721f7cd
commit 52c8802d57
3 changed files with 121 additions and 110 deletions

View File

@ -7,7 +7,7 @@ import {
getNemesisPasscodeModTypes, getNemesisPasscodeModTypes,
getWeaponsForManifest, getWeaponsForManifest,
IKnifeResponse, IKnifeResponse,
showdownNodes nemesisFactionInfos
} from "@/src/helpers/nemesisHelpers"; } from "@/src/helpers/nemesisHelpers";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Loadout } from "@/src/models/inventoryModels/loadoutModel"; import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
@ -223,7 +223,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
inventory.Nemesis!.InfNodes = [ inventory.Nemesis!.InfNodes = [
{ {
Node: showdownNodes[inventory.Nemesis!.Faction], Node: nemesisFactionInfos[inventory.Nemesis!.Faction].showdownNode,
Influence: 1 Influence: 1
} }
]; ];

View File

@ -1,5 +1,5 @@
import { ExportRegions, ExportWarframes } from "warframe-public-export-plus"; import { ExportRegions, ExportWarframes } from "warframe-public-export-plus";
import { IInfNode, ITypeCount, TNemesisFaction } from "@/src/types/inventoryTypes/inventoryTypes"; import { IInfNode, TNemesisFaction } from "@/src/types/inventoryTypes/inventoryTypes";
import { getRewardAtPercentage, SRng } from "@/src/services/rngService"; import { getRewardAtPercentage, SRng } from "@/src/services/rngService";
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel"; import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
import { logger } from "../utils/logger"; import { logger } from "../utils/logger";
@ -7,13 +7,51 @@ import { IOid } from "../types/commonTypes";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { addMods, generateRewardSeed } from "../services/inventoryService"; import { addMods, generateRewardSeed } from "../services/inventoryService";
import { isArchwingMission } from "../services/worldStateService"; import { isArchwingMission } from "../services/worldStateService";
import { fromStoreItem, toStoreItem } from "../services/itemDataService";
import { createMessage } from "../services/inboxService";
import { version_compare } from "./inventoryHelpers"; import { version_compare } from "./inventoryHelpers";
export interface INemesisFactionInfo {
systemIndexes: number[];
showdownNode: string;
ephemeraChance: number;
firstKillReward: string;
firstConvertReward: string;
messageTitle: string;
messageBody: string;
}
export const nemesisFactionInfos: Record<TNemesisFaction, INemesisFactionInfo> = {
FC_GRINEER: {
systemIndexes: [2, 3, 9, 11, 18],
showdownNode: "CrewBattleNode557",
ephemeraChance: 0.05,
firstKillReward: "/Lotus/StoreItems/Upgrades/Skins/Clan/LichKillerBadgeItem",
firstConvertReward: "/Lotus/StoreItems/Upgrades/Skins/Sigils/KuvaLichSigil",
messageTitle: "/Lotus/Language/Inbox/VanquishKuvaMsgTitle",
messageBody: "/Lotus/Language/Inbox/VanquishLichMsgBody"
},
FC_CORPUS: {
systemIndexes: [1, 15, 4, 7, 8],
showdownNode: "CrewBattleNode558",
ephemeraChance: 0.2,
firstKillReward: "/Lotus/StoreItems/Upgrades/Skins/Clan/CorpusLichBadgeItem",
firstConvertReward: "/Lotus/StoreItems/Upgrades/Skins/Sigils/CorpusLichSigil",
messageTitle: "/Lotus/Language/Inbox/VanquishLawyerMsgTitle",
messageBody: "/Lotus/Language/Inbox/VanquishLichMsgBody"
},
FC_INFESTATION: {
systemIndexes: [23],
showdownNode: "CrewBattleNode559",
ephemeraChance: 0,
firstKillReward: "/Lotus/StoreItems/Upgrades/Skins/Sigils/InfLichVanquishedSigil",
firstConvertReward: "/Lotus/StoreItems/Upgrades/Skins/Sigils/InfLichConvertedSigil",
messageTitle: "/Lotus/Language/Inbox/VanquishBandMsgTitle",
messageBody: "/Lotus/Language/Inbox/VanquishBandMsgBody"
}
};
export const getInfNodes = (faction: TNemesisFaction, rank: number): IInfNode[] => { export const getInfNodes = (faction: TNemesisFaction, rank: number): IInfNode[] => {
const infNodes = []; const infNodes = [];
const systemIndex = systemIndexes[faction][rank]; const systemIndex = nemesisFactionInfos[faction].systemIndexes[rank];
for (const [key, value] of Object.entries(ExportRegions)) { for (const [key, value] of Object.entries(ExportRegions)) {
if ( if (
value.systemIndex === systemIndex && value.systemIndex === systemIndex &&
@ -35,24 +73,6 @@ export const getInfNodes = (faction: TNemesisFaction, rank: number): IInfNode[]
return infNodes; return infNodes;
}; };
const systemIndexes: Record<TNemesisFaction, number[]> = {
FC_GRINEER: [2, 3, 9, 11, 18],
FC_CORPUS: [1, 15, 4, 7, 8],
FC_INFESTATION: [23]
};
export const showdownNodes: Record<TNemesisFaction, string> = {
FC_GRINEER: "CrewBattleNode557",
FC_CORPUS: "CrewBattleNode558",
FC_INFESTATION: "CrewBattleNode559"
};
const ephemeraProbabilities: Record<TNemesisFaction, number> = {
FC_GRINEER: 0.05,
FC_CORPUS: 0.2,
FC_INFESTATION: 0
};
type TInnateDamageTag = type TInnateDamageTag =
| "InnateElectricityDamage" | "InnateElectricityDamage"
| "InnateHeatDamage" | "InnateHeatDamage"
@ -311,11 +331,17 @@ export const getInnateDamageTag = (KillingSuit: string): TInnateDamageTag => {
return ExportWarframes[KillingSuit].nemesisUpgradeTag!; return ExportWarframes[KillingSuit].nemesisUpgradeTag!;
}; };
const petHeads = [
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC"
] as const;
export interface INemesisProfile { export interface INemesisProfile {
innateDamageTag: TInnateDamageTag; innateDamageTag: TInnateDamageTag;
innateDamageValue: number; innateDamageValue: number;
ephemera?: string; ephemera?: string;
petHead?: string; petHead?: (typeof petHeads)[number];
petBody?: string; petBody?: string;
petLegs?: string; petLegs?: string;
petTail?: string; petTail?: string;
@ -337,16 +363,12 @@ export const generateNemesisProfile = (
innateDamageTag: getInnateDamageTag(killingSuit), innateDamageTag: getInnateDamageTag(killingSuit),
innateDamageValue: Math.trunc(value * 0x40000000) // TODO: For -1399275245665749231n, the value should be 75306944, but we're off by 59 with 75307003. 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") { if (rng.randomFloat() <= nemesisFactionInfos[Faction].ephemeraChance && Faction != "FC_INFESTATION") {
profile.ephemera = ephmeraTypes[Faction][profile.innateDamageTag]; profile.ephemera = ephmeraTypes[Faction][profile.innateDamageTag];
} }
rng.randomFloat(); // something related to sentinel agent maybe rng.randomFloat(); // something related to sentinel agent maybe
if (Faction == "FC_CORPUS") { if (Faction == "FC_CORPUS") {
profile.petHead = rng.randomElement([ profile.petHead = rng.randomElement(petHeads)!;
"/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([ profile.petBody = rng.randomElement([
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyA", "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyA",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyB", "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyB",
@ -422,52 +444,3 @@ export const getInfestedLichItemRewards = (fp: bigint): string[] => {
const rotBReward = getRewardAtPercentage(infestedLichRotB, rng.randomFloat())!.type; const rotBReward = getRewardAtPercentage(infestedLichRotB, rng.randomFloat())!.type;
return [rotAReward, rotBReward]; return [rotAReward, rotBReward];
}; };
export const sendCodaFinishedMessage = async (
inventory: TInventoryDatabaseDocument,
fp: bigint = generateRewardSeed(),
name: string = "ZEKE_BEATWOMAN_TM.1999",
killed: boolean = true
): Promise<void> => {
const att: string[] = [];
// First vanquish/convert gives a sigil
const sigil = killed
? "/Lotus/Upgrades/Skins/Sigils/InfLichVanquishedSigil"
: "/Lotus/Upgrades/Skins/Sigils/InfLichConvertedSigil";
if (!inventory.WeaponSkins.find(x => x.ItemType == sigil)) {
att.push(toStoreItem(sigil));
}
const [rotAReward, rotBReward] = getInfestedLichItemRewards(fp);
att.push(fromStoreItem(rotAReward));
att.push(fromStoreItem(rotBReward));
let countedAtt: ITypeCount[] | undefined;
if (killed) {
countedAtt = [
{
ItemType: "/Lotus/Types/Items/MiscItems/CodaWeaponBucks",
ItemCount: getKillTokenRewardCount(fp)
}
];
}
await createMessage(inventory.accountOwnerId, [
{
sndr: "/Lotus/Language/Bosses/Ordis",
msg: "/Lotus/Language/Inbox/VanquishBandMsgBody",
arg: [
{
Key: "LICH_NAME",
Tag: name
}
],
att: att,
countedAtt: countedAtt,
sub: "/Lotus/Language/Inbox/VanquishBandMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
highPriority: true
}
]);
};

View File

@ -10,7 +10,7 @@ import {
import { IMissionInventoryUpdateRequest, IRewardInfo } from "../types/requestTypes"; import { IMissionInventoryUpdateRequest, IRewardInfo } from "../types/requestTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { IRngResult, SRng, getRandomElement, getRandomReward } from "@/src/services/rngService"; import { IRngResult, SRng, getRandomElement, getRandomReward } from "@/src/services/rngService";
import { equipmentKeys, IMission, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes"; import { equipmentKeys, IMission, ITypeCount, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import { import {
addBooster, addBooster,
addChallenges, addChallenges,
@ -44,7 +44,7 @@ import {
import { updateQuestKey } from "@/src/services/questService"; import { updateQuestKey } from "@/src/services/questService";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { IAffiliationMods, IInventoryChanges } from "@/src/types/purchaseTypes"; import { IAffiliationMods, IInventoryChanges } from "@/src/types/purchaseTypes";
import { getLevelKeyRewards, toStoreItem } from "@/src/services/itemDataService"; import { fromStoreItem, getLevelKeyRewards, toStoreItem } from "@/src/services/itemDataService";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { getEntriesUnsafe } from "@/src/utils/ts-utils"; import { getEntriesUnsafe } from "@/src/utils/ts-utils";
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
@ -58,10 +58,12 @@ import kuriaMessage100 from "@/static/fixed_responses/kuriaMessages/oneHundredPe
import conservationAnimals from "@/static/fixed_responses/conservationAnimals.json"; import conservationAnimals from "@/static/fixed_responses/conservationAnimals.json";
import { import {
generateNemesisProfile, generateNemesisProfile,
getInfestedLichItemRewards,
getInfNodes, getInfNodes,
getKillTokenRewardCount,
getNemesisPasscode, getNemesisPasscode,
getWeaponsForManifest, getWeaponsForManifest,
sendCodaFinishedMessage nemesisFactionInfos
} from "@/src/helpers/nemesisHelpers"; } from "@/src/helpers/nemesisHelpers";
import { Loadout } from "../models/inventoryModels/loadoutModel"; import { Loadout } from "../models/inventoryModels/loadoutModel";
import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes"; import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes";
@ -665,6 +667,9 @@ export const addMissionInventoryUpdates = async (
inventory.Nemesis.Faction, inventory.Nemesis.Faction,
inventory.Nemesis.KillingSuit inventory.Nemesis.KillingSuit
); );
const nemesisFactionInfo = nemesisFactionInfos[inventory.Nemesis.Faction];
const att: string[] = [];
let countedAtt: ITypeCount[] | undefined;
if (value.killed) { if (value.killed) {
if ( if (
@ -675,45 +680,78 @@ export const addMissionInventoryUpdates = async (
inventory.Nemesis.WeaponIdx inventory.Nemesis.WeaponIdx
]; ];
giveNemesisWeaponRecipe(inventory, weaponType, value.nemesisName, value.weaponLoc, profile); giveNemesisWeaponRecipe(inventory, weaponType, value.nemesisName, value.weaponLoc, profile);
att.push(weaponType);
} }
if (value.petLoc) { //if (value.petLoc) {
if (profile.petHead) {
giveNemesisPetRecipe(inventory, value.nemesisName, profile); giveNemesisPetRecipe(inventory, value.nemesisName, profile);
att.push(
{
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA":
"/Lotus/Types/Recipes/ZanukaPet/ZanukaPetCompleteHeadABlueprint",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB":
"/Lotus/Types/Recipes/ZanukaPet/ZanukaPetCompleteHeadBBlueprint",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC":
"/Lotus/Types/Recipes/ZanukaPet/ZanukaPetCompleteHeadCBlueprint"
}[profile.petHead]
);
} }
} }
// "Players will receive a Lich's Ephemera regardless of whether they Vanquish or Convert them." // "Players will receive a Lich's Ephemera regardless of whether they Vanquish or Convert them."
if (profile.ephemera) { if (profile.ephemera) {
addSkin(inventory, profile.ephemera); addSkin(inventory, profile.ephemera);
att.push(profile.ephemera);
} }
switch (inventory.Nemesis.Faction) { const skinRewardStoreItem = value.killed
case "FC_GRINEER": ? nemesisFactionInfo.firstKillReward
addSkin( : nemesisFactionInfo.firstConvertReward;
inventory, if (Object.keys(addSkin(inventory, fromStoreItem(skinRewardStoreItem))).length != 0) {
value.killed att.push(skinRewardStoreItem);
? "/Lotus/Upgrades/Skins/Clan/LichKillerBadgeItem" }
: "/Lotus/Upgrades/Skins/Sigils/KuvaLichSigil"
);
break;
case "FC_CORPUS": if (inventory.Nemesis.Faction == "FC_INFESTATION") {
addSkin( const [rotARewardStoreItem, rotBRewardStoreItem] = getInfestedLichItemRewards(
inventory, inventory.Nemesis.fp
value.killed );
? "/Lotus/Upgrades/Skins/Clan/CorpusLichBadgeItem" const rotAReward = fromStoreItem(rotARewardStoreItem);
: "/Lotus/Upgrades/Skins/Sigils/CorpusLichSigil" const rotBReward = fromStoreItem(rotBRewardStoreItem);
); await addItem(inventory, rotAReward);
break; await addItem(inventory, rotBReward);
att.push(rotAReward);
att.push(rotBReward);
case "FC_INFESTATION": if (value.killed) {
// TOVERIFY: Is the inbox message also sent when converting a lich? If not, how are the rewards given? countedAtt = [
await sendCodaFinishedMessage( {
inventory, ItemType: "/Lotus/Types/Items/MiscItems/CodaWeaponBucks",
inventory.Nemesis.fp, ItemCount: getKillTokenRewardCount(inventory.Nemesis.fp)
value.nemesisName, }
value.killed ];
); addMiscItems(inventory, countedAtt);
break; }
}
if (value.killed) {
await createMessage(inventory.accountOwnerId, [
{
sndr: "/Lotus/Language/Bosses/Ordis",
msg: nemesisFactionInfo.messageBody,
arg: [
{
Key: "LICH_NAME",
Tag: value.nemesisName
}
],
att: att,
countedAtt: countedAtt,
attVisualOnly: true,
sub: nemesisFactionInfo.messageTitle,
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
highPriority: true
}
]);
} }
inventory.Nemesis = undefined; inventory.Nemesis = undefined;