返回提交历史
Modified
package-lock.json
+4
-4
Modified
package.json
+1
-1
Modified
src/services/inventoryService.ts
+68
-2
Modified
src/services/missionInventoryUpdateService.ts
+5
-17
Modified
src/services/purchaseService.ts
+9
-28
Added
src/services/rngService.ts
+42
-0
XFEstudio/XFESpaceNinjaServer
feat: handle acquisition of booster packs (#452)
c7c9d901
代码差异
6 个文件
+129
-52
@@ -12,7 +12,7 @@
12
12
"copyfiles": "^2.4.1",
13
13
"express": "^5.0.0-beta.3",
14
14
"mongoose": "^8.1.1",
15
"warframe-public-export-plus": "^0.4.0",
15
"warframe-public-export-plus": "^0.4.1",
16
16
"warframe-riven-info": "^0.1.0",
17
17
"winston": "^3.11.0",
18
18
"winston-daily-rotate-file": "^4.7.1"
@@ -3669,9 +3669,9 @@
3669
3669
}
3670
3670
},
3671
3671
"node_modules/warframe-public-export-plus": {
3672
"version": "0.4.0",
3673
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.4.0.tgz",
3674
"integrity": "sha512-8wOkh9dET4IHmHDSZ8g8RW0GlfEevHnBwEETAqy3jRhwssyF0TgQsOOpJVuhcPKedCYeudR92HJ3JoXoiTCr6A=="
3672
"version": "0.4.1",
3673
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.4.1.tgz",
3674
"integrity": "sha512-5SwnT/K/rMI0zJpdodzeEPlO/UnMlHiKv8NZGH647/5u52LZf8xfOpJHP4/yr/anjVVzDQJwY5K3CmbX0uMQdw=="
3675
3675
},
3676
3676
"node_modules/warframe-riven-info": {
3677
3677
"version": "0.1.0",
@@ -16,7 +16,7 @@
16
16
"copyfiles": "^2.4.1",
17
17
"express": "^5.0.0-beta.3",
18
18
"mongoose": "^8.1.1",
19
"warframe-public-export-plus": "^0.4.0",
19
"warframe-public-export-plus": "^0.4.1",
20
20
"warframe-riven-info": "^0.1.0",
21
21
"winston": "^3.11.0",
22
22
"winston-daily-rotate-file": "^4.7.1"
@@ -2,7 +2,7 @@ import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
2
2
import new_inventory from "@/static/fixed_responses/postTutorialInventory.json";
3
3
import { config } from "@/src/services/configService";
4
4
import { Types } from "mongoose";
5
import { SlotNames, IInventoryChanges } from "@/src/types/purchaseTypes";
5
import { SlotNames, IInventoryChanges, IBinChanges } from "@/src/types/purchaseTypes";
6
6
import {
7
7
IChallengeProgress,
8
8
IConsumable,
@@ -27,9 +27,16 @@ import {
27
27
} from "../types/requestTypes";
28
28
import { logger } from "@/src/utils/logger";
29
29
import { getWeaponType, getExalted } from "@/src/services/itemDataService";
30
import { getRandomWeightedReward } from "@/src/services/rngService";
30
31
import { ISyndicateSacrifice, ISyndicateSacrificeResponse } from "../types/syndicateTypes";
31
32
import { IEquipmentClient } from "../types/inventoryTypes/commonInventoryTypes";
32
import { ExportCustoms, ExportFlavour, ExportRecipes, ExportResources } from "warframe-public-export-plus";
33
import {
34
ExportBoosterPacks,
35
ExportCustoms,
36
ExportFlavour,
37
ExportRecipes,
38
ExportResources
39
} from "warframe-public-export-plus";
33
40
34
41
export const createInventory = async (
35
42
accountOwnerId: Types.ObjectId,
@@ -59,6 +66,31 @@ export const createInventory = async (
59
66
}
60
67
};
61
68
69
export const combineInventoryChanges = (InventoryChanges: IInventoryChanges, delta: IInventoryChanges): void => {
70
for (const key in delta) {
71
if (!(key in InventoryChanges)) {
72
InventoryChanges[key] = delta[key];
73
} else if (Array.isArray(delta[key])) {
74
const left = InventoryChanges[key] as object[];
75
const right = delta[key] as object[];
76
for (const item of right) {
77
left.push(item);
78
}
79
} else {
80
console.assert(key.substring(-3) == "Bin");
81
const left = InventoryChanges[key] as IBinChanges;
82
const right = delta[key] as IBinChanges;
83
left.count += right.count;
84
left.platinum += right.platinum;
85
left.Slots += right.Slots;
86
if (right.Extra) {
87
left.Extra ??= 0;
88
left.Extra += right.Extra;
89
}
90
}
91
}
92
};
93
62
94
export const getInventory = async (accountOwnerId: string) => {
63
95
const inventory = await Inventory.findOne({ accountOwnerId: accountOwnerId });
64
96
@@ -121,6 +153,21 @@ export const addItem = async (
121
153
}
122
154
};
123
155
}
156
if (typeName in ExportBoosterPacks) {
157
const pack = ExportBoosterPacks[typeName];
158
const InventoryChanges = {};
159
for (const weights of pack.rarityWeightsPerRoll) {
160
const result = getRandomWeightedReward(pack.components, weights);
161
if (result) {
162
logger.debug(`booster pack rolled`, result);
163
combineInventoryChanges(
164
InventoryChanges,
165
(await addItem(accountId, result.type, result.itemCount)).InventoryChanges
166
);
167
}
168
}
169
return { InventoryChanges };
170
}
124
171
125
172
// Path-based duck typing
126
173
switch (typeName.substr(1).split("/")[1]) {
@@ -261,6 +308,25 @@ export const addItem = async (
261
308
}
262
309
}
263
310
}
311
case "Game":
312
if (typeName.substr(1).split("/")[3] == "Projections") {
313
// Void Relics, e.g. /Lotus/Types/Game/Projections/T2VoidProjectionGaussPrimeDBronze
314
const inventory = await getInventory(accountId);
315
const miscItemChanges = [
316
{
317
ItemType: typeName,
318
ItemCount: quantity
319
} satisfies IMiscItem
320
];
321
addMiscItems(inventory, miscItemChanges);
322
await inventory.save();
323
return {
324
InventoryChanges: {
325
MiscItems: miscItemChanges
326
}
327
};
328
}
329
break;
264
330
case "Restoratives": // Codex Scanner, Remote Observer, Starburst
265
331
const inventory = await getInventory(accountId);
266
332
const consumablesChanges = [
@@ -11,6 +11,7 @@ import {
11
11
} from "warframe-public-export-plus";
12
12
import { IMissionInventoryUpdateRequest } from "../types/requestTypes";
13
13
import { logger } from "@/src/utils/logger";
14
import { IRngResult, getRandomReward } from "@/src/services/rngService";
14
15
15
16
// need reverse engineer rewardSeed, otherwise ingame displayed rotation reward will be different than added to db or displayed on mission end
16
17
const getRewards = ({
@@ -23,7 +24,7 @@ const getRewards = ({
23
24
return { InventoryChanges: {}, MissionRewards: [] };
24
25
}
25
26
26
const drops: IReward[] = [];
27
const drops: IRngResult[] = [];
27
28
if (RewardInfo.node in ExportRegions) {
28
29
const region = ExportRegions[RewardInfo.node];
29
30
const rewardManifests = region.rewardManifests ?? [];
@@ -117,21 +118,8 @@ const getRotations = (rotationCount: number): number[] => {
117
118
return rotatedValues;
118
119
};
119
120
120
const getRandomRewardByChance = (data: IReward[]): IReward | undefined => {
121
if (data.length == 0) return;
122
123
const totalChance = data.reduce((sum, item) => sum + item.probability!, 0);
124
const randomValue = Math.random() * totalChance;
125
126
let cumulativeChance = 0;
127
for (const item of data) {
128
cumulativeChance += item.probability!;
129
if (randomValue <= cumulativeChance) {
130
return item;
131
}
132
}
133
134
return;
121
const getRandomRewardByChance = (pool: IReward[]): IRngResult | undefined => {
122
return getRandomReward(pool as IRngResult[]);
135
123
};
136
124
137
125
const creditBundles: Record<string, number> = {
@@ -157,7 +145,7 @@ const fusionBundles: Record<string, number> = {
157
145
};
158
146
159
147
const formatRewardsToInventoryType = (
160
rewards: IReward[]
148
rewards: IRngResult[]
161
149
): { InventoryChanges: IMissionInventoryUpdateRequest; MissionRewards: IMissionRewardResponse[] } => {
162
150
const InventoryChanges: IMissionInventoryUpdateRequest = {};
163
151
const MissionRewards: IMissionRewardResponse[] = [];
@@ -1,7 +1,13 @@
1
1
import { parseSlotPurchaseName } from "@/src/helpers/purchaseHelpers";
2
2
import { getSubstringFromKeyword } from "@/src/helpers/stringHelpers";
3
import { addItem, addBooster, updateCurrency, updateSlots } from "@/src/services/inventoryService";
4
import { IPurchaseRequest, SlotPurchase, IInventoryChanges, IBinChanges } from "@/src/types/purchaseTypes";
3
import {
4
addItem,
5
addBooster,
6
combineInventoryChanges,
7
updateCurrency,
8
updateSlots
9
} from "@/src/services/inventoryService";
10
import { IPurchaseRequest, SlotPurchase, IInventoryChanges } from "@/src/types/purchaseTypes";
5
11
import { logger } from "@/src/utils/logger";
6
12
import { ExportBundles, ExportGear, TRarity } from "warframe-public-export-plus";
7
13
@@ -46,31 +52,6 @@ export const handlePurchase = async (purchaseRequest: IPurchaseRequest, accountI
46
52
return purchaseResponse;
47
53
};
48
54
49
const addInventoryChanges = (InventoryChanges: IInventoryChanges, delta: IInventoryChanges): void => {
50
for (const key in delta) {
51
if (!(key in InventoryChanges)) {
52
InventoryChanges[key] = delta[key];
53
} else if (Array.isArray(delta[key])) {
54
const left = InventoryChanges[key] as object[];
55
const right = delta[key] as object[];
56
for (const item of right) {
57
left.push(item);
58
}
59
} else {
60
console.assert(key.substring(-3) == "Bin");
61
const left = InventoryChanges[key] as IBinChanges;
62
const right = delta[key] as IBinChanges;
63
left.count += right.count;
64
left.platinum += right.platinum;
65
left.Slots += right.Slots;
66
if (right.Extra) {
67
left.Extra ??= 0;
68
left.Extra += right.Extra;
69
}
70
}
71
}
72
};
73
74
55
const handleStoreItemAcquisition = async (
75
56
storeItemName: string,
76
57
accountId: string,
@@ -86,7 +67,7 @@ const handleStoreItemAcquisition = async (
86
67
const bundle = ExportBundles[storeItemName];
87
68
logger.debug("acquiring bundle", bundle);
88
69
for (const component of bundle.components) {
89
addInventoryChanges(
70
combineInventoryChanges(
90
71
purchaseResponse.InventoryChanges,
91
72
(
92
73
await handleStoreItemAcquisition(
@@ -0,0 +1,42 @@
1
import { TRarity } from "warframe-public-export-plus";
2
3
export interface IRngResult {
4
type: string;
5
itemCount: number;
6
probability: number;
7
}
8
9
export const getRandomReward = (pool: IRngResult[]): IRngResult | undefined => {
10
if (pool.length == 0) return;
11
12
const totalChance = pool.reduce((accum, item) => accum + item.probability, 0);
13
const randomValue = Math.random() * totalChance;
14
15
let cumulativeChance = 0;
16
for (const item of pool) {
17
cumulativeChance += item.probability;
18
if (randomValue <= cumulativeChance) {
19
return item;
20
}
21
}
22
throw new Error("What the fuck?");
23
};
24
25
export const getRandomWeightedReward = (
26
pool: { Item: string; Rarity: TRarity }[],
27
weights: Record<TRarity, number>
28
): IRngResult | undefined => {
29
const resultPool: IRngResult[] = [];
30
const rarityCounts: Record<TRarity, number> = { COMMON: 0, UNCOMMON: 0, RARE: 0, LEGENDARY: 0 };
31
for (const entry of pool) {
32
++rarityCounts[entry.Rarity];
33
}
34
for (const entry of pool) {
35
resultPool.push({
36
type: entry.Item,
37
itemCount: 1,
38
probability: weights[entry.Rarity] / rarityCounts[entry.Rarity]
39
});
40
}
41
return getRandomReward(resultPool);
42
};