feat: opening relics in endless missions #713

Merged
Sainan merged 3 commits from open-relic into main 2025-01-05 20:35:36 -08:00
3 changed files with 120 additions and 0 deletions

View 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;
coderabbitai[bot] commented 2025-01-04 19:05:07 -08:00 (Migrated from github.com)
Review

⚠️ Potential issue

Confirm relic existence

When retrieving relic from ExportRelics[data.ParticipantInfo.VoidProjection], handle the case where the key might not exist. An undefined relic lookup could cause runtime errors.

-        const relic = ExportRelics[data.ParticipantInfo.VoidProjection];
+        const relicKey = data.ParticipantInfo.VoidProjection;
+        if (!ExportRelics[relicKey]) {
+            logger.warn(`Relic with key '${relicKey}' not found`);
+            return res.status(400).json({ error: "Invalid relic" });
+        }
+        const relic = ExportRelics[relicKey];

Committable suggestion skipped: line range outside the PR's diff.

_:warning: Potential issue_ **Confirm relic existence** When retrieving `relic` from `ExportRelics[data.ParticipantInfo.VoidProjection]`, handle the case where the key might not exist. An undefined relic lookup could cause runtime errors. ```diff - const relic = ExportRelics[data.ParticipantInfo.VoidProjection]; + const relicKey = data.ParticipantInfo.VoidProjection; + if (!ExportRelics[relicKey]) { + logger.warn(`Relic with key '${relicKey}' not found`); + return res.status(400).json({ error: "Invalid relic" }); + } + const relic = ExportRelics[relicKey]; ``` > Committable suggestion skipped: line range outside the PR's diff. <!-- This is an auto-generated comment by CodeRabbit -->
// 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);
}
coderabbitai[bot] commented 2025-01-04 19:05:07 -08:00 (Migrated from github.com)
Review

🛠️ 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.

-        )!;
+        );
+        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.

_: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;
}

View File

@ -26,6 +26,7 @@ import { getIgnoredUsersController } from "@/src/controllers/api/getIgnoredUsers
import { getNewRewardSeedController } from "@/src/controllers/api/getNewRewardSeedController"; import { getNewRewardSeedController } from "@/src/controllers/api/getNewRewardSeedController";
import { getShipController } from "@/src/controllers/api/getShipController"; import { getShipController } from "@/src/controllers/api/getShipController";
import { getVendorInfoController } from "@/src/controllers/api/getVendorInfoController"; import { getVendorInfoController } from "@/src/controllers/api/getVendorInfoController";
import { getVoidProjectionRewardsController } from "@/src/controllers/api/getVoidProjectionRewardsController";
import { gildWeaponController } from "@/src/controllers/api/gildWeaponController"; import { gildWeaponController } from "@/src/controllers/api/gildWeaponController";
import { guildTechController } from "../controllers/api/guildTechController"; import { guildTechController } from "../controllers/api/guildTechController";
import { hostSessionController } from "@/src/controllers/api/hostSessionController"; import { hostSessionController } from "@/src/controllers/api/hostSessionController";
@ -120,6 +121,7 @@ apiRouter.post("/focus.php", focusController);
apiRouter.post("/fusionTreasures.php", fusionTreasuresController); apiRouter.post("/fusionTreasures.php", fusionTreasuresController);
apiRouter.post("/genericUpdate.php", genericUpdateController); apiRouter.post("/genericUpdate.php", genericUpdateController);
apiRouter.post("/getAlliance.php", getAllianceController); apiRouter.post("/getAlliance.php", getAllianceController);
apiRouter.post("/getVoidProjectionRewards.php", getVoidProjectionRewardsController);
apiRouter.post("/gildWeapon.php", gildWeaponController); apiRouter.post("/gildWeapon.php", gildWeaponController);
apiRouter.post("/guildTech.php", guildTechController); apiRouter.post("/guildTech.php", guildTechController);
apiRouter.post("/hostSession.php", hostSessionController); apiRouter.post("/hostSession.php", hostSessionController);

View File

@ -40,3 +40,22 @@ export const getRandomWeightedReward = (
} }
return getRandomReward(resultPool); 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);
};