feat: cracking relics in non-endless missions #1010

Merged
OrdisPrime merged 1 commits from relics into main 2025-02-25 04:38:48 -08:00
4 changed files with 103 additions and 73 deletions

View File

@ -1,77 +1,31 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { addMiscItems, getInventory } from "@/src/services/inventoryService"; import { crackRelic } from "@/src/helpers/relicHelper";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService"; import { IVoidTearParticipantInfo } from "@/src/types/requestTypes";
import { getRandomWeightedReward2 } from "@/src/services/rngService";
import { ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { ExportRelics, ExportRewards, TRarity } from "warframe-public-export-plus";
export const getVoidProjectionRewardsController: RequestHandler = async (req, res) => { export const getVoidProjectionRewardsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const data = getJSONfromString<IVoidProjectionRewardRequest>(String(req.body)); const data = getJSONfromString<IVoidProjectionRewardRequest>(String(req.body));
if (data.ParticipantInfo.QualifiesForReward && !data.ParticipantInfo.HaveRewardResponse) {
const inventory = await getInventory(accountId);
await crackRelic(inventory, data.ParticipantInfo);
await inventory.save();
}
const response: IVoidProjectionRewardResponse = { const response: IVoidProjectionRewardResponse = {
CurrentWave: data.CurrentWave, CurrentWave: data.CurrentWave,
ParticipantInfo: data.ParticipantInfo, ParticipantInfo: data.ParticipantInfo,
DifficultyTier: data.DifficultyTier DifficultyTier: data.DifficultyTier
}; };
if (data.ParticipantInfo.QualifiesForReward) {
const relic = ExportRelics[data.ParticipantInfo.VoidProjection];
const weights = refinementToWeights[relic.quality];
logger.debug(`opening a relic of quality ${relic.quality}; rarity weights are`, weights);
const reward = getRandomWeightedReward2(
ExportRewards[relic.rewardManifest][0] as { type: string; itemCount: number; rarity: TRarity }[], // rarity is nullable in PE+ typings, but always present for relics
weights
)!;
logger.debug(`relic rolled`, reward);
response.ParticipantInfo.Reward = reward.type;
const inventory = await getInventory(accountId);
// Remove relic
addMiscItems(inventory, [
{
ItemType: data.ParticipantInfo.VoidProjection,
ItemCount: -1
}
]);
// Give reward
await handleStoreItemAcquisition(reward.type, inventory, reward.itemCount);
await inventory.save();
}
res.json(response); res.json(response);
}; };
const refinementToWeights = {
VPQ_BRONZE: {
COMMON: 0.76,
UNCOMMON: 0.22,
RARE: 0.02,
LEGENDARY: 0
},
VPQ_SILVER: {
COMMON: 0.7,
UNCOMMON: 0.26,
RARE: 0.04,
LEGENDARY: 0
},
VPQ_GOLD: {
COMMON: 0.6,
UNCOMMON: 0.34,
RARE: 0.06,
LEGENDARY: 0
},
VPQ_PLATINUM: {
COMMON: 0.5,
UNCOMMON: 0.4,
RARE: 0.1,
LEGENDARY: 0
}
};
interface IVoidProjectionRewardRequest { interface IVoidProjectionRewardRequest {
CurrentWave: number; CurrentWave: number;
ParticipantInfo: IParticipantInfo; ParticipantInfo: IVoidTearParticipantInfo;
VoidTier: string; VoidTier: string;
DifficultyTier: number; DifficultyTier: number;
VoidProjectionRemovalHash: string; VoidProjectionRemovalHash: string;
@ -79,20 +33,6 @@ interface IVoidProjectionRewardRequest {
interface IVoidProjectionRewardResponse { interface IVoidProjectionRewardResponse {
CurrentWave: number; CurrentWave: number;
ParticipantInfo: IParticipantInfo; ParticipantInfo: IVoidTearParticipantInfo;
DifficultyTier: number; DifficultyTier: number;
} }
interface IParticipantInfo {
AccountId: string;
Name: string;
ChosenRewardOwner: string;
MissionHash: string;
VoidProjection: string;
Reward: string;
QualifiesForReward: boolean;
HaveRewardResponse: boolean;
RewardsMultiplier: number;
RewardProjection: string;
HardModeReward: ITypeCount;
}

View File

@ -0,0 +1,60 @@
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { IVoidTearParticipantInfo } from "@/src/types/requestTypes";
import { ExportRelics, ExportRewards, TRarity } from "warframe-public-export-plus";
import { getRandomWeightedReward2 } from "@/src/services/rngService";
import { logger } from "@/src/utils/logger";
import { addMiscItems } from "@/src/services/inventoryService";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
export const crackRelic = async (
inventory: TInventoryDatabaseDocument,
participant: IVoidTearParticipantInfo
): Promise<void> => {
const relic = ExportRelics[participant.VoidProjection];
const weights = refinementToWeights[relic.quality];
logger.debug(`opening a relic of quality ${relic.quality}; rarity weights are`, weights);
const reward = getRandomWeightedReward2(
ExportRewards[relic.rewardManifest][0] as { type: string; itemCount: number; rarity: TRarity }[], // rarity is nullable in PE+ typings, but always present for relics
weights
)!;
logger.debug(`relic rolled`, reward);
participant.Reward = reward.type;
// Remove relic
addMiscItems(inventory, [
{
ItemType: participant.VoidProjection,
ItemCount: -1
}
]);
// Give reward
await handleStoreItemAcquisition(reward.type, inventory, reward.itemCount);
};
const refinementToWeights = {
VPQ_BRONZE: {
COMMON: 0.76,
UNCOMMON: 0.22,
RARE: 0.02,
LEGENDARY: 0
},
VPQ_SILVER: {
COMMON: 0.7,
UNCOMMON: 0.26,
RARE: 0.04,
LEGENDARY: 0
},
VPQ_GOLD: {
COMMON: 0.6,
UNCOMMON: 0.34,
RARE: 0.06,
LEGENDARY: 0
},
VPQ_PLATINUM: {
COMMON: 0.5,
UNCOMMON: 0.4,
RARE: 0.1,
LEGENDARY: 0
}
};

View File

@ -33,6 +33,7 @@ import { getEntriesUnsafe } from "@/src/utils/ts-utils";
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { handleStoreItemAcquisition } from "./purchaseService"; import { handleStoreItemAcquisition } from "./purchaseService";
import { IMissionReward } from "../types/missionTypes"; import { IMissionReward } from "../types/missionTypes";
import { crackRelic } from "@/src/helpers/relicHelper";
const getRotations = (rotationCount: number): number[] => { const getRotations = (rotationCount: number): number[] => {
if (rotationCount === 0) return [0]; if (rotationCount === 0) return [0];
@ -242,7 +243,8 @@ export const addMissionRewards = async (
RewardInfo: rewardInfo, RewardInfo: rewardInfo,
LevelKeyName: levelKeyName, LevelKeyName: levelKeyName,
Missions: missions, Missions: missions,
RegularCredits: creditDrops RegularCredits: creditDrops,
VoidTearParticipantsCurrWave: voidTearWave
}: IMissionInventoryUpdateRequest }: IMissionInventoryUpdateRequest
) => { ) => {
if (!rewardInfo) { if (!rewardInfo) {
@ -312,6 +314,15 @@ export const addMissionRewards = async (
rngRewardCredits: inventoryChanges.RegularCredits ?? 0 rngRewardCredits: inventoryChanges.RegularCredits ?? 0
}); });
if (
voidTearWave &&
voidTearWave.Participants[0].QualifiesForReward &&
!voidTearWave.Participants[0].HaveRewardResponse
) {
await crackRelic(inventory, voidTearWave.Participants[0]);
MissionRewards.push({ StoreItem: voidTearWave.Participants[0].Reward, ItemCount: 1 });
}
return { inventoryChanges, MissionRewards, credits }; return { inventoryChanges, MissionRewards, credits };
}; };

View File

@ -87,6 +87,11 @@ export type IMissionInventoryUpdateRequest = {
PlayerSkillGains: IPlayerSkills; PlayerSkillGains: IPlayerSkills;
CustomMarkers?: ICustomMarkers[]; CustomMarkers?: ICustomMarkers[];
LoreFragmentScans?: ILoreFragmentScan[]; LoreFragmentScans?: ILoreFragmentScan[];
VoidTearParticipantsCurrWave?: {
Wave: number;
IsFinalWave: boolean;
Participants: IVoidTearParticipantInfo[];
};
} & { } & {
[K in TEquipmentKey]?: IEquipmentClient[]; [K in TEquipmentKey]?: IEquipmentClient[];
}; };
@ -136,3 +141,17 @@ export interface IUnlockShipFeatureRequest {
KeyChain: string; KeyChain: string;
ChainStage: number; ChainStage: number;
} }
export interface IVoidTearParticipantInfo {
AccountId: string;
Name: string;
ChosenRewardOwner: string;
MissionHash: string;
VoidProjection: string;
Reward: string;
QualifiesForReward: boolean;
HaveRewardResponse: boolean;
RewardsMultiplier: number;
RewardProjection: string;
HardModeReward: ITypeCount;
}