feat: opening relics in endless missions #713
99
src/controllers/api/getVoidProjectionRewardsController.ts
Normal file
99
src/controllers/api/getVoidProjectionRewardsController.ts
Normal file
@ -0,0 +1,99 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||
import { getRandomWeightedReward2 } from "@/src/services/rngService";
|
||||
import { ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { RequestHandler } from "express";
|
||||
import { ExportRelics, ExportRewards, TRarity } from "warframe-public-export-plus";
|
||||
|
||||
export const getVoidProjectionRewardsController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const data = getJSONfromString(String(req.body)) as IVoidProjectionRewardRequest;
|
||||
const response: IVoidProjectionRewardResponse = {
|
||||
CurrentWave: data.CurrentWave,
|
||||
ParticipantInfo: data.ParticipantInfo,
|
||||
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;
|
||||
|
||||
|
||||
// Remove relic
|
||||
const inventory = await getInventory(accountId);
|
||||
addMiscItems(inventory, [
|
||||
{
|
||||
ItemType: data.ParticipantInfo.VoidProjection,
|
||||
ItemCount: -1
|
||||
}
|
||||
]);
|
||||
await inventory.save();
|
||||
|
||||
// Give reward
|
||||
await handleStoreItemAcquisition(reward.type, accountId, reward.itemCount);
|
||||
}
|
||||
![]() 🛠️ Refactor suggestion Graceful handling of missing reward When calling
_:hammer_and_wrench: Refactor suggestion_
**Graceful handling of missing reward**
When calling `getRandomWeightedReward2`, the result could be `undefined` if the pool is empty. In such a case, handle that scenario explicitly instead of using the non-null assertion operator (`!`). This helps prevent unexpected crashes.
```diff
- )!;
+ );
+ if (!reward) {
+ logger.warn("Unable to determine reward from relic pool");
+ return res.status(400).json({ error: "Reward could not be determined" });
+ }
```
> Committable suggestion skipped: line range outside the PR's diff.
<!-- This is an auto-generated comment by CodeRabbit -->
|
||||
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 {
|
||||
CurrentWave: number;
|
||||
ParticipantInfo: IParticipantInfo;
|
||||
VoidTier: string;
|
||||
DifficultyTier: number;
|
||||
VoidProjectionRemovalHash: string;
|
||||
}
|
||||
|
||||
interface IVoidProjectionRewardResponse {
|
||||
CurrentWave: number;
|
||||
ParticipantInfo: IParticipantInfo;
|
||||
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;
|
||||
}
|
@ -26,6 +26,7 @@ import { getIgnoredUsersController } from "@/src/controllers/api/getIgnoredUsers
|
||||
import { getNewRewardSeedController } from "@/src/controllers/api/getNewRewardSeedController";
|
||||
import { getShipController } from "@/src/controllers/api/getShipController";
|
||||
import { getVendorInfoController } from "@/src/controllers/api/getVendorInfoController";
|
||||
import { getVoidProjectionRewardsController } from "@/src/controllers/api/getVoidProjectionRewardsController";
|
||||
import { gildWeaponController } from "@/src/controllers/api/gildWeaponController";
|
||||
import { guildTechController } from "../controllers/api/guildTechController";
|
||||
import { hostSessionController } from "@/src/controllers/api/hostSessionController";
|
||||
@ -120,6 +121,7 @@ apiRouter.post("/focus.php", focusController);
|
||||
apiRouter.post("/fusionTreasures.php", fusionTreasuresController);
|
||||
apiRouter.post("/genericUpdate.php", genericUpdateController);
|
||||
apiRouter.post("/getAlliance.php", getAllianceController);
|
||||
apiRouter.post("/getVoidProjectionRewards.php", getVoidProjectionRewardsController);
|
||||
apiRouter.post("/gildWeapon.php", gildWeaponController);
|
||||
apiRouter.post("/guildTech.php", guildTechController);
|
||||
apiRouter.post("/hostSession.php", hostSessionController);
|
||||
|
@ -40,3 +40,22 @@ export const getRandomWeightedReward = (
|
||||
}
|
||||
return getRandomReward(resultPool);
|
||||
};
|
||||
|
||||
export const getRandomWeightedReward2 = (
|
||||
pool: { type: string; itemCount: number; rarity: TRarity }[],
|
||||
weights: Record<TRarity, number>
|
||||
): IRngResult | undefined => {
|
||||
const resultPool: IRngResult[] = [];
|
||||
const rarityCounts: Record<TRarity, number> = { COMMON: 0, UNCOMMON: 0, RARE: 0, LEGENDARY: 0 };
|
||||
for (const entry of pool) {
|
||||
++rarityCounts[entry.rarity];
|
||||
}
|
||||
for (const entry of pool) {
|
||||
resultPool.push({
|
||||
type: entry.type,
|
||||
itemCount: entry.itemCount,
|
||||
probability: weights[entry.rarity] / rarityCounts[entry.rarity]
|
||||
});
|
||||
}
|
||||
return getRandomReward(resultPool);
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user
⚠️ Potential issue
Confirm relic existence
When retrieving
relic
fromExportRelics[data.ParticipantInfo.VoidProjection]
, handle the case where the key might not exist. An undefined relic lookup could cause runtime errors.