forked from OpenWF/SpaceNinjaServer
		
	feat: cracking relics in non-endless missions (#1010)
Closes #415 Reviewed-on: OpenWF/SpaceNinjaServer#1010 Co-authored-by: Sainan <sainan@calamity.inc> Co-committed-by: Sainan <sainan@calamity.inc>
This commit is contained in:
		
							parent
							
								
									f672f05db9
								
							
						
					
					
						commit
						39f0f7de9a
					
				@ -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;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										60
									
								
								src/helpers/relicHelper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/helpers/relicHelper.ts
									
									
									
									
									
										Normal 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
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -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];
 | 
				
			||||||
@ -221,7 +222,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) {
 | 
				
			||||||
@ -291,6 +293,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 };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user