feat: handle acquisition of booster packs (#452)

This commit is contained in:
Sainan 2024-07-03 12:30:32 +02:00 committed by GitHub
parent 84720a7058
commit c7c9d901b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 129 additions and 52 deletions

8
package-lock.json generated
View File

@ -12,7 +12,7 @@
"copyfiles": "^2.4.1", "copyfiles": "^2.4.1",
"express": "^5.0.0-beta.3", "express": "^5.0.0-beta.3",
"mongoose": "^8.1.1", "mongoose": "^8.1.1",
"warframe-public-export-plus": "^0.4.0", "warframe-public-export-plus": "^0.4.1",
"warframe-riven-info": "^0.1.0", "warframe-riven-info": "^0.1.0",
"winston": "^3.11.0", "winston": "^3.11.0",
"winston-daily-rotate-file": "^4.7.1" "winston-daily-rotate-file": "^4.7.1"
@ -3669,9 +3669,9 @@
} }
}, },
"node_modules/warframe-public-export-plus": { "node_modules/warframe-public-export-plus": {
"version": "0.4.0", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.4.0.tgz", "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.4.1.tgz",
"integrity": "sha512-8wOkh9dET4IHmHDSZ8g8RW0GlfEevHnBwEETAqy3jRhwssyF0TgQsOOpJVuhcPKedCYeudR92HJ3JoXoiTCr6A==" "integrity": "sha512-5SwnT/K/rMI0zJpdodzeEPlO/UnMlHiKv8NZGH647/5u52LZf8xfOpJHP4/yr/anjVVzDQJwY5K3CmbX0uMQdw=="
}, },
"node_modules/warframe-riven-info": { "node_modules/warframe-riven-info": {
"version": "0.1.0", "version": "0.1.0",

View File

@ -16,7 +16,7 @@
"copyfiles": "^2.4.1", "copyfiles": "^2.4.1",
"express": "^5.0.0-beta.3", "express": "^5.0.0-beta.3",
"mongoose": "^8.1.1", "mongoose": "^8.1.1",
"warframe-public-export-plus": "^0.4.0", "warframe-public-export-plus": "^0.4.1",
"warframe-riven-info": "^0.1.0", "warframe-riven-info": "^0.1.0",
"winston": "^3.11.0", "winston": "^3.11.0",
"winston-daily-rotate-file": "^4.7.1" "winston-daily-rotate-file": "^4.7.1"

View File

@ -2,7 +2,7 @@ import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
import new_inventory from "@/static/fixed_responses/postTutorialInventory.json"; import new_inventory from "@/static/fixed_responses/postTutorialInventory.json";
import { config } from "@/src/services/configService"; import { config } from "@/src/services/configService";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { SlotNames, IInventoryChanges } from "@/src/types/purchaseTypes"; import { SlotNames, IInventoryChanges, IBinChanges } from "@/src/types/purchaseTypes";
import { import {
IChallengeProgress, IChallengeProgress,
IConsumable, IConsumable,
@ -27,9 +27,16 @@ import {
} from "../types/requestTypes"; } from "../types/requestTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { getWeaponType, getExalted } from "@/src/services/itemDataService"; import { getWeaponType, getExalted } from "@/src/services/itemDataService";
import { getRandomWeightedReward } from "@/src/services/rngService";
import { ISyndicateSacrifice, ISyndicateSacrificeResponse } from "../types/syndicateTypes"; import { ISyndicateSacrifice, ISyndicateSacrificeResponse } from "../types/syndicateTypes";
import { IEquipmentClient } from "../types/inventoryTypes/commonInventoryTypes"; import { IEquipmentClient } from "../types/inventoryTypes/commonInventoryTypes";
import { ExportCustoms, ExportFlavour, ExportRecipes, ExportResources } from "warframe-public-export-plus"; import {
ExportBoosterPacks,
ExportCustoms,
ExportFlavour,
ExportRecipes,
ExportResources
} from "warframe-public-export-plus";
export const createInventory = async ( export const createInventory = async (
accountOwnerId: Types.ObjectId, accountOwnerId: Types.ObjectId,
@ -59,6 +66,31 @@ export const createInventory = async (
} }
}; };
export const combineInventoryChanges = (InventoryChanges: IInventoryChanges, delta: IInventoryChanges): void => {
for (const key in delta) {
if (!(key in InventoryChanges)) {
InventoryChanges[key] = delta[key];
} else if (Array.isArray(delta[key])) {
const left = InventoryChanges[key] as object[];
const right = delta[key] as object[];
for (const item of right) {
left.push(item);
}
} else {
console.assert(key.substring(-3) == "Bin");
const left = InventoryChanges[key] as IBinChanges;
const right = delta[key] as IBinChanges;
left.count += right.count;
left.platinum += right.platinum;
left.Slots += right.Slots;
if (right.Extra) {
left.Extra ??= 0;
left.Extra += right.Extra;
}
}
}
};
export const getInventory = async (accountOwnerId: string) => { export const getInventory = async (accountOwnerId: string) => {
const inventory = await Inventory.findOne({ accountOwnerId: accountOwnerId }); const inventory = await Inventory.findOne({ accountOwnerId: accountOwnerId });
@ -121,6 +153,21 @@ export const addItem = async (
} }
}; };
} }
if (typeName in ExportBoosterPacks) {
const pack = ExportBoosterPacks[typeName];
const InventoryChanges = {};
for (const weights of pack.rarityWeightsPerRoll) {
const result = getRandomWeightedReward(pack.components, weights);
if (result) {
logger.debug(`booster pack rolled`, result);
combineInventoryChanges(
InventoryChanges,
(await addItem(accountId, result.type, result.itemCount)).InventoryChanges
);
}
}
return { InventoryChanges };
}
// Path-based duck typing // Path-based duck typing
switch (typeName.substr(1).split("/")[1]) { switch (typeName.substr(1).split("/")[1]) {
@ -261,6 +308,25 @@ export const addItem = async (
} }
} }
} }
case "Game":
if (typeName.substr(1).split("/")[3] == "Projections") {
// Void Relics, e.g. /Lotus/Types/Game/Projections/T2VoidProjectionGaussPrimeDBronze
const inventory = await getInventory(accountId);
const miscItemChanges = [
{
ItemType: typeName,
ItemCount: quantity
} satisfies IMiscItem
];
addMiscItems(inventory, miscItemChanges);
await inventory.save();
return {
InventoryChanges: {
MiscItems: miscItemChanges
}
};
}
break;
case "Restoratives": // Codex Scanner, Remote Observer, Starburst case "Restoratives": // Codex Scanner, Remote Observer, Starburst
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
const consumablesChanges = [ const consumablesChanges = [

View File

@ -11,6 +11,7 @@ import {
} from "warframe-public-export-plus"; } from "warframe-public-export-plus";
import { IMissionInventoryUpdateRequest } from "../types/requestTypes"; import { IMissionInventoryUpdateRequest } from "../types/requestTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { IRngResult, getRandomReward } from "@/src/services/rngService";
// need reverse engineer rewardSeed, otherwise ingame displayed rotation reward will be different than added to db or displayed on mission end // need reverse engineer rewardSeed, otherwise ingame displayed rotation reward will be different than added to db or displayed on mission end
const getRewards = ({ const getRewards = ({
@ -23,7 +24,7 @@ const getRewards = ({
return { InventoryChanges: {}, MissionRewards: [] }; return { InventoryChanges: {}, MissionRewards: [] };
} }
const drops: IReward[] = []; const drops: IRngResult[] = [];
if (RewardInfo.node in ExportRegions) { if (RewardInfo.node in ExportRegions) {
const region = ExportRegions[RewardInfo.node]; const region = ExportRegions[RewardInfo.node];
const rewardManifests = region.rewardManifests ?? []; const rewardManifests = region.rewardManifests ?? [];
@ -117,21 +118,8 @@ const getRotations = (rotationCount: number): number[] => {
return rotatedValues; return rotatedValues;
}; };
const getRandomRewardByChance = (data: IReward[]): IReward | undefined => { const getRandomRewardByChance = (pool: IReward[]): IRngResult | undefined => {
if (data.length == 0) return; return getRandomReward(pool as IRngResult[]);
const totalChance = data.reduce((sum, item) => sum + item.probability!, 0);
const randomValue = Math.random() * totalChance;
let cumulativeChance = 0;
for (const item of data) {
cumulativeChance += item.probability!;
if (randomValue <= cumulativeChance) {
return item;
}
}
return;
}; };
const creditBundles: Record<string, number> = { const creditBundles: Record<string, number> = {
@ -157,7 +145,7 @@ const fusionBundles: Record<string, number> = {
}; };
const formatRewardsToInventoryType = ( const formatRewardsToInventoryType = (
rewards: IReward[] rewards: IRngResult[]
): { InventoryChanges: IMissionInventoryUpdateRequest; MissionRewards: IMissionRewardResponse[] } => { ): { InventoryChanges: IMissionInventoryUpdateRequest; MissionRewards: IMissionRewardResponse[] } => {
const InventoryChanges: IMissionInventoryUpdateRequest = {}; const InventoryChanges: IMissionInventoryUpdateRequest = {};
const MissionRewards: IMissionRewardResponse[] = []; const MissionRewards: IMissionRewardResponse[] = [];

View File

@ -1,7 +1,13 @@
import { parseSlotPurchaseName } from "@/src/helpers/purchaseHelpers"; import { parseSlotPurchaseName } from "@/src/helpers/purchaseHelpers";
import { getSubstringFromKeyword } from "@/src/helpers/stringHelpers"; import { getSubstringFromKeyword } from "@/src/helpers/stringHelpers";
import { addItem, addBooster, updateCurrency, updateSlots } from "@/src/services/inventoryService"; import {
import { IPurchaseRequest, SlotPurchase, IInventoryChanges, IBinChanges } from "@/src/types/purchaseTypes"; addItem,
addBooster,
combineInventoryChanges,
updateCurrency,
updateSlots
} from "@/src/services/inventoryService";
import { IPurchaseRequest, SlotPurchase, IInventoryChanges } from "@/src/types/purchaseTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { ExportBundles, ExportGear, TRarity } from "warframe-public-export-plus"; import { ExportBundles, ExportGear, TRarity } from "warframe-public-export-plus";
@ -46,31 +52,6 @@ export const handlePurchase = async (purchaseRequest: IPurchaseRequest, accountI
return purchaseResponse; return purchaseResponse;
}; };
const addInventoryChanges = (InventoryChanges: IInventoryChanges, delta: IInventoryChanges): void => {
for (const key in delta) {
if (!(key in InventoryChanges)) {
InventoryChanges[key] = delta[key];
} else if (Array.isArray(delta[key])) {
const left = InventoryChanges[key] as object[];
const right = delta[key] as object[];
for (const item of right) {
left.push(item);
}
} else {
console.assert(key.substring(-3) == "Bin");
const left = InventoryChanges[key] as IBinChanges;
const right = delta[key] as IBinChanges;
left.count += right.count;
left.platinum += right.platinum;
left.Slots += right.Slots;
if (right.Extra) {
left.Extra ??= 0;
left.Extra += right.Extra;
}
}
}
};
const handleStoreItemAcquisition = async ( const handleStoreItemAcquisition = async (
storeItemName: string, storeItemName: string,
accountId: string, accountId: string,
@ -86,7 +67,7 @@ const handleStoreItemAcquisition = async (
const bundle = ExportBundles[storeItemName]; const bundle = ExportBundles[storeItemName];
logger.debug("acquiring bundle", bundle); logger.debug("acquiring bundle", bundle);
for (const component of bundle.components) { for (const component of bundle.components) {
addInventoryChanges( combineInventoryChanges(
purchaseResponse.InventoryChanges, purchaseResponse.InventoryChanges,
( (
await handleStoreItemAcquisition( await handleStoreItemAcquisition(

View File

@ -0,0 +1,42 @@
import { TRarity } from "warframe-public-export-plus";
export interface IRngResult {
type: string;
itemCount: number;
probability: number;
}
export const getRandomReward = (pool: IRngResult[]): IRngResult | undefined => {
if (pool.length == 0) return;
const totalChance = pool.reduce((accum, item) => accum + item.probability, 0);
const randomValue = Math.random() * totalChance;
let cumulativeChance = 0;
for (const item of pool) {
cumulativeChance += item.probability;
if (randomValue <= cumulativeChance) {
return item;
}
}
throw new Error("What the fuck?");
};
export const getRandomWeightedReward = (
pool: { Item: string; 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.Item,
itemCount: 1,
probability: weights[entry.Rarity] / rarityCounts[entry.Rarity]
});
}
return getRandomReward(resultPool);
};