返回提交历史
Modified
src/controllers/api/purchaseController.ts
+1
-0
Modified
src/models/inventoryModels/inventoryModel.ts
+8
-6
Modified
src/services/importService.ts
+6
-0
Modified
src/services/inventoryService.ts
+14
-2
Modified
src/services/purchaseService.ts
+130
-51
Modified
src/types/inventoryTypes/inventoryTypes.ts
+3
-2
Modified
src/types/worldStateTypes.ts
+1
-0
Modified
static/fixed_responses/worldState/baro.json
+2
-1
XFEstudio/SpaceNinjaServer
feat: baro's void surplus (#2334)
Closes #2284 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2334 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
c4c622d8
代码差异
8 个文件
+165
-62
@@ -11,6 +11,7 @@ export const purchaseController: RequestHandler = async (req, res) => {
11
11
const inventory = await getInventory(accountId);
12
12
const response = await handlePurchase(purchaseRequest, inventory);
13
13
await inventory.save();
14
//console.log(JSON.stringify(response, null, 2));
14
15
res.json(response);
15
16
sendWsBroadcastTo(accountId, { update_inventory: true });
16
17
};
@@ -91,7 +91,7 @@ import {
91
91
ICrewMemberSkillEfficiency,
92
92
ICrewMemberDatabase,
93
93
ICrewMemberClient,
94
ISortieRewardAttenuation,
94
IRewardAttenuation,
95
95
IInvasionProgressDatabase,
96
96
IInvasionProgressClient,
97
97
IAccolades,
@@ -1417,10 +1417,10 @@ lastSortieRewardSchema.set("toJSON", {
1417
1417
}
1418
1418
});
1419
1419
1420
const sortieRewardAttenutationSchema = new Schema<ISortieRewardAttenuation>(
1420
const rewardAttenutationSchema = new Schema<IRewardAttenuation>(
1421
1421
{
1422
Tag: String,
1423
Atten: Number
1422
Tag: { type: String, required: true },
1423
Atten: { type: Number, required: true }
1424
1424
},
1425
1425
{ _id: false }
1426
1426
);
@@ -1666,7 +1666,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
1666
1666
CompletedSorties: [String],
1667
1667
LastSortieReward: { type: [lastSortieRewardSchema], default: undefined },
1668
1668
LastLiteSortieReward: { type: [lastSortieRewardSchema], default: undefined },
1669
SortieRewardAttenuation: { type: [sortieRewardAttenutationSchema], default: undefined },
1669
SortieRewardAttenuation: { type: [rewardAttenutationSchema], default: undefined },
1670
1670
1671
1671
// Resource Extractor Drones
1672
1672
Drones: [droneSchema],
@@ -1805,7 +1805,9 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
1805
1805
1806
1806
HubNpcCustomizations: { type: [hubNpcCustomizationSchema], default: undefined },
1807
1807
1808
ClaimedJunctionChallengeRewards: { type: [String], default: undefined }
1808
ClaimedJunctionChallengeRewards: { type: [String], default: undefined },
1809
1810
SpecialItemRewardAttenuation: { type: [rewardAttenutationSchema], default: undefined }
1809
1811
},
1810
1812
{ timestamps: { createdAt: "Created", updatedAt: false } }
1811
1813
);
@@ -296,6 +296,12 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
296
296
db[key] = client[key];
297
297
}
298
298
}
299
// IRewardAtten[]
300
for (const key of ["SortieRewardAttenuation", "SpecialItemRewardAttenuation"] as const) {
301
if (client[key] !== undefined) {
302
db[key] = client[key];
303
}
304
}
299
305
if (client.XPInfo !== undefined) {
300
306
db.XPInfo = client.XPInfo;
301
307
}
@@ -44,6 +44,7 @@ import {
44
44
} from "../types/inventoryTypes/commonInventoryTypes";
45
45
import {
46
46
ExportArcanes,
47
ExportBoosters,
47
48
ExportBundles,
48
49
ExportChallenges,
49
50
ExportCustoms,
@@ -671,6 +672,17 @@ export const addItem = async (
671
672
return await addEmailItem(inventory, typeName);
672
673
}
673
674
675
// Boosters are an odd case. They're only added like this via Baro's Void Surplus afaik.
676
{
677
const boosterEntry = Object.entries(ExportBoosters).find(arr => arr[1].typeName == typeName);
678
if (boosterEntry) {
679
addBooster(typeName, quantity, inventory);
680
return {
681
Boosters: [{ ItemType: typeName, ExpiryDate: quantity }]
682
};
683
}
684
}
685
674
686
// Path-based duck typing
675
687
switch (typeName.substr(1).split("/")[1]) {
676
688
case "Powersuits":
@@ -1354,7 +1366,7 @@ export const addCustomization = (
1354
1366
customizationName: string,
1355
1367
inventoryChanges: IInventoryChanges = {}
1356
1368
): IInventoryChanges => {
1357
if (!inventory.FlavourItems.find(x => x.ItemType == customizationName)) {
1369
if (!inventory.FlavourItems.some(x => x.ItemType == customizationName)) {
1358
1370
const flavourItemIndex = inventory.FlavourItems.push({ ItemType: customizationName }) - 1;
1359
1371
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1360
1372
inventoryChanges.FlavourItems ??= [];
@@ -1370,7 +1382,7 @@ export const addSkin = (
1370
1382
typeName: string,
1371
1383
inventoryChanges: IInventoryChanges = {}
1372
1384
): IInventoryChanges => {
1373
if (inventory.WeaponSkins.find(x => x.ItemType == typeName)) {
1385
if (inventory.WeaponSkins.some(x => x.ItemType == typeName)) {
1374
1386
logger.debug(`refusing to add WeaponSkin ${typeName} because account already owns it`);
1375
1387
} else {
1376
1388
const index = inventory.WeaponSkins.push({ ItemType: typeName, IsNew: true }) - 1;
@@ -8,7 +8,7 @@ import {
8
8
updateCurrency,
9
9
updateSlots
10
10
} from "@/src/services/inventoryService";
11
import { getRandomWeightedRewardUc } from "@/src/services/rngService";
11
import { getRandomReward, getRandomWeightedRewardUc } from "@/src/services/rngService";
12
12
import { applyStandingToVendorManifest, getVendorManifestByOid } from "@/src/services/serversideVendorsService";
13
13
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
14
14
import {
@@ -37,6 +37,7 @@ import { config } from "./configService";
37
37
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
38
38
import { fromStoreItem, toStoreItem } from "./itemDataService";
39
39
import { DailyDeal } from "../models/worldStateModel";
40
import { fromMongoDate, toMongoDate } from "../helpers/inventoryHelpers";
40
41
41
42
export const getStoreItemCategory = (storeItem: string): string => {
42
43
const storeItemString = getSubstringFromKeyword(storeItem, "StoreItems/");
@@ -53,6 +54,58 @@ export const getStoreItemTypesCategory = (typesItem: string): string => {
53
54
return typeElements[1];
54
55
};
55
56
57
const tallyVendorPurchase = (
58
inventory: TInventoryDatabaseDocument,
59
inventoryChanges: IInventoryChanges,
60
VendorType: string,
61
ItemId: string,
62
numPurchased: number,
63
Expiry: Date
64
): void => {
65
if (!config.noVendorPurchaseLimits) {
66
inventory.RecentVendorPurchases ??= [];
67
let vendorPurchases = inventory.RecentVendorPurchases.find(x => x.VendorType == VendorType);
68
if (!vendorPurchases) {
69
vendorPurchases =
70
inventory.RecentVendorPurchases[
71
inventory.RecentVendorPurchases.push({
72
VendorType: VendorType,
73
PurchaseHistory: []
74
}) - 1
75
];
76
}
77
let historyEntry = vendorPurchases.PurchaseHistory.find(x => x.ItemId == ItemId);
78
if (historyEntry) {
79
if (Date.now() >= historyEntry.Expiry.getTime()) {
80
historyEntry.NumPurchased = numPurchased;
81
historyEntry.Expiry = Expiry;
82
} else {
83
historyEntry.NumPurchased += numPurchased;
84
}
85
} else {
86
historyEntry =
87
vendorPurchases.PurchaseHistory[
88
vendorPurchases.PurchaseHistory.push({
89
ItemId: ItemId,
90
NumPurchased: numPurchased,
91
Expiry: Expiry
92
}) - 1
93
];
94
}
95
inventoryChanges.NewVendorPurchase = {
96
VendorType: VendorType,
97
PurchaseHistory: [
98
{
99
ItemId: ItemId,
100
NumPurchased: historyEntry.NumPurchased,
101
Expiry: toMongoDate(Expiry)
102
}
103
]
104
};
105
inventoryChanges.RecentVendorPurchases = inventoryChanges.NewVendorPurchase;
106
}
107
};
108
56
109
export const handlePurchase = async (
57
110
purchaseRequest: IPurchaseRequest,
58
111
inventory: TInventoryDatabaseDocument
@@ -99,20 +152,7 @@ export const handlePurchase = async (
99
152
if (offer.LocTagRandSeed !== undefined) {
100
153
seed = BigInt(offer.LocTagRandSeed);
101
154
}
102
if (!config.noVendorPurchaseLimits && ItemId) {
103
inventory.RecentVendorPurchases ??= [];
104
let vendorPurchases = inventory.RecentVendorPurchases.find(
105
x => x.VendorType == manifest!.VendorInfo.TypeName
106
);
107
if (!vendorPurchases) {
108
vendorPurchases =
109
inventory.RecentVendorPurchases[
110
inventory.RecentVendorPurchases.push({
111
VendorType: manifest.VendorInfo.TypeName,
112
PurchaseHistory: []
113
}) - 1
114
];
115
}
155
if (ItemId) {
116
156
let expiry = parseInt(offer.Expiry.$date.$numberLong);
117
157
if (purchaseRequest.PurchaseParams.IsWeekly) {
118
158
const EPOCH = 1734307200 * 1000; // Monday
@@ -120,34 +160,14 @@ export const handlePurchase = async (
120
160
const weekStart = EPOCH + week * 604800000;
121
161
expiry = weekStart + 604800000;
122
162
}
123
const historyEntry = vendorPurchases.PurchaseHistory.find(x => x.ItemId == ItemId);
124
let numPurchased = purchaseRequest.PurchaseParams.Quantity;
125
if (historyEntry) {
126
if (Date.now() >= historyEntry.Expiry.getTime()) {
127
historyEntry.NumPurchased = numPurchased;
128
historyEntry.Expiry = new Date(expiry);
129
} else {
130
numPurchased += historyEntry.NumPurchased;
131
historyEntry.NumPurchased += purchaseRequest.PurchaseParams.Quantity;
132
}
133
} else {
134
vendorPurchases.PurchaseHistory.push({
135
ItemId: ItemId,
136
NumPurchased: purchaseRequest.PurchaseParams.Quantity,
137
Expiry: new Date(expiry)
138
});
139
}
140
prePurchaseInventoryChanges.NewVendorPurchase = {
141
VendorType: manifest.VendorInfo.TypeName,
142
PurchaseHistory: [
143
{
144
ItemId: ItemId,
145
NumPurchased: numPurchased,
146
Expiry: { $date: { $numberLong: expiry.toString() } }
147
}
148
]
149
};
150
prePurchaseInventoryChanges.RecentVendorPurchases = prePurchaseInventoryChanges.NewVendorPurchase;
163
tallyVendorPurchase(
164
inventory,
165
prePurchaseInventoryChanges,
166
manifest.VendorInfo.TypeName,
167
ItemId,
168
purchaseRequest.PurchaseParams.Quantity,
169
new Date(expiry)
170
);
151
171
}
152
172
purchaseRequest.PurchaseParams.Quantity *= offer.QuantityMultiplier;
153
173
} else {
@@ -193,7 +213,7 @@ export const handlePurchase = async (
193
213
throw new Error(`vendor purchase should not have an expected price`);
194
214
}
195
215
196
if (!config.dontSubtractPurchaseItemCost) {
216
if (offer.PrimePrice && !config.dontSubtractPurchaseItemCost) {
197
217
const invItem: IMiscItem = {
198
218
ItemType: "/Lotus/Types/Items/MiscItems/PrimeBucks",
199
219
ItemCount: offer.PrimePrice * purchaseRequest.PurchaseParams.Quantity * -1
@@ -202,6 +222,17 @@ export const handlePurchase = async (
202
222
purchaseResponse.InventoryChanges.MiscItems ??= [];
203
223
purchaseResponse.InventoryChanges.MiscItems.push(invItem);
204
224
}
225
226
if (offer.Limit) {
227
tallyVendorPurchase(
228
inventory,
229
purchaseResponse.InventoryChanges,
230
"VoidTrader",
231
offer.ItemType,
232
purchaseRequest.PurchaseParams.Quantity,
233
fromMongoDate(worldState.VoidTraders[0].Expiry)
234
);
235
}
205
236
}
206
237
break;
207
238
}
@@ -509,12 +540,57 @@ const handleBoosterPackPurchase = async (
509
540
"attempt to roll over 100 booster packs in a single go. possible but unlikely to be desirable for the user or the server."
510
541
);
511
542
}
543
const specialItemReward = pack.components.find(x => x.PityIncreaseRate);
512
544
for (let i = 0; i != quantity; ++i) {
513
const disallowedItems = new Set();
514
for (let roll = 0; roll != pack.rarityWeightsPerRoll.length; ) {
515
const weights = pack.rarityWeightsPerRoll[roll];
516
const result = getRandomWeightedRewardUc(pack.components, weights);
517
if (result) {
545
if (specialItemReward) {
546
{
547
const normalComponents = [];
548
for (const comp of pack.components) {
549
if (!comp.PityIncreaseRate) {
550
const { Probability, ...rest } = comp;
551
normalComponents.push({
552
...rest,
553
probability: Probability!
554
});
555
}
556
}
557
const result = getRandomReward(normalComponents)!;
558
logger.debug(`booster pack rolled`, result);
559
purchaseResponse.BoosterPackItems += toStoreItem(result.Item) + ',{"lvl":0};';
560
combineInventoryChanges(
561
purchaseResponse.InventoryChanges,
562
await addItem(inventory, result.Item, result.Amount)
563
);
564
}
565
566
if (!inventory.WeaponSkins.some(x => x.ItemType == specialItemReward.Item)) {
567
inventory.SpecialItemRewardAttenuation ??= [];
568
let atten = inventory.SpecialItemRewardAttenuation.find(x => x.Tag == specialItemReward.Item);
569
if (!atten) {
570
atten =
571
inventory.SpecialItemRewardAttenuation[
572
inventory.SpecialItemRewardAttenuation.push({
573
Tag: specialItemReward.Item,
574
Atten: specialItemReward.Probability!
575
}) - 1
576
];
577
}
578
if (Math.random() < atten.Atten) {
579
purchaseResponse.BoosterPackItems += toStoreItem(specialItemReward.Item) + ',{"lvl":0};';
580
combineInventoryChanges(
581
purchaseResponse.InventoryChanges,
582
await addItem(inventory, specialItemReward.Item)
583
);
584
// TOVERIFY: Is the SpecialItemRewardAttenuation entry removed now?
585
} else {
586
atten.Atten += specialItemReward.PityIncreaseRate!;
587
}
588
}
589
} else {
590
const disallowedItems = new Set();
591
for (let roll = 0; roll != pack.rarityWeightsPerRoll.length; ) {
592
const weights = pack.rarityWeightsPerRoll[roll];
593
const result = getRandomWeightedRewardUc(pack.components, weights)!;
518
594
logger.debug(`booster pack rolled`, result);
519
595
if (disallowedItems.has(result.Item)) {
520
596
logger.debug(`oops, can't use that one; trying again`);
@@ -524,9 +600,12 @@ const handleBoosterPackPurchase = async (
524
600
disallowedItems.add(result.Item);
525
601
}
526
602
purchaseResponse.BoosterPackItems += toStoreItem(result.Item) + ',{"lvl":0};';
527
combineInventoryChanges(purchaseResponse.InventoryChanges, await addItem(inventory, result.Item, 1));
603
combineInventoryChanges(
604
purchaseResponse.InventoryChanges,
605
await addItem(inventory, result.Item, result.Amount)
606
);
607
++roll;
528
608
}
529
++roll;
530
609
}
531
610
}
532
611
return purchaseResponse;
@@ -298,7 +298,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
298
298
CompletedSorties: string[];
299
299
LastSortieReward?: ILastSortieRewardClient[];
300
300
LastLiteSortieReward?: ILastSortieRewardClient[];
301
SortieRewardAttenuation?: ISortieRewardAttenuation[];
301
SortieRewardAttenuation?: IRewardAttenuation[];
302
302
Drones: IDroneClient[];
303
303
StepSequencers: IStepSequencer[];
304
304
ActiveAvatarImageType?: string;
@@ -383,6 +383,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
383
383
HubNpcCustomizations?: IHubNpcCustomization[];
384
384
Ship?: IOrbiter; // U22 and below, response only
385
385
ClaimedJunctionChallengeRewards?: string[]; // U39
386
SpecialItemRewardAttenuation?: IRewardAttenuation[]; // Baro's Void Surplus
386
387
}
387
388
388
389
export interface IAffiliation {
@@ -785,7 +786,7 @@ export interface ILastSortieRewardDatabase extends Omit<ILastSortieRewardClient,
785
786
SortieId: Types.ObjectId;
786
787
}
787
788
788
export interface ISortieRewardAttenuation {
789
export interface IRewardAttenuation {
789
790
Tag: string;
790
791
Atten: number;
791
792
}
@@ -160,6 +160,7 @@ export interface IVoidTraderOffer {
160
160
ItemType: string;
161
161
PrimePrice: number;
162
162
RegularPrice: number;
163
Limit?: number;
163
164
}
164
165
165
166
export interface IVoidStorm {
@@ -1,7 +1,8 @@
1
1
{
2
2
"evergreen": [
3
3
{ "ItemType": "/Lotus/StoreItems/Types/Keys/MummyQuestKeyBlueprint", "PrimePrice": 100, "RegularPrice": 25000 },
4
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Effects/FootstepsMaple", "PrimePrice": 15, "RegularPrice": 1000 }
4
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Effects/FootstepsMaple", "PrimePrice": 15, "RegularPrice": 1000 },
5
{ "ItemType": "/Lotus/StoreItems/Types/BoosterPacks/BaroTreasureBox", "PrimePrice": 0, "RegularPrice": 50000, "Limit": 1 }
5
6
],
6
7
"armorSets": [
7
8
[