返回提交历史
Modified
src/controllers/api/getVendorInfoController.ts
+16
-10
Modified
src/services/purchaseService.ts
+4
-3
Modified
src/services/serversideVendorsService.ts
+57
-18
Modified
src/types/vendorTypes.ts
+6
-0
XFEstudio/XFESpaceNinjaServer
feat: adjust server-side vendor prices according to syndicate standings (#2076)
For buying crew members from ticker Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2076 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
870ff2dd
代码差异
4 个文件
+83
-31
@@ -1,14 +1,20 @@
1
1
import { RequestHandler } from "express";
2
import { getVendorManifestByTypeName } from "@/src/services/serversideVendorsService";
2
import { applyStandingToVendorManifest, getVendorManifestByTypeName } from "@/src/services/serversideVendorsService";
3
import { getInventory } from "@/src/services/inventoryService";
4
import { getAccountIdForRequest } from "@/src/services/loginService";
3
5
4
export const getVendorInfoController: RequestHandler = (req, res) => {
5
if (typeof req.query.vendor == "string") {
6
const manifest = getVendorManifestByTypeName(req.query.vendor);
7
if (!manifest) {
8
throw new Error(`Unknown vendor: ${req.query.vendor}`);
9
}
10
res.json(manifest);
11
} else {
12
res.status(400).end();
6
export const getVendorInfoController: RequestHandler = async (req, res) => {
7
let manifest = getVendorManifestByTypeName(req.query.vendor as string);
8
if (!manifest) {
9
throw new Error(`Unknown vendor: ${req.query.vendor as string}`);
13
10
}
11
12
// For testing purposes, authenticating with this endpoint is optional here, but would be required on live.
13
if (req.query.accountId) {
14
const accountId = await getAccountIdForRequest(req);
15
const inventory = await getInventory(accountId);
16
manifest = applyStandingToVendorManifest(inventory, manifest);
17
}
18
19
res.json(manifest);
14
20
};
@@ -9,7 +9,7 @@ import {
9
9
updateSlots
10
10
} from "@/src/services/inventoryService";
11
11
import { getRandomWeightedRewardUc } from "@/src/services/rngService";
12
import { getVendorManifestByOid } from "@/src/services/serversideVendorsService";
12
import { applyStandingToVendorManifest, getVendorManifestByOid } from "@/src/services/serversideVendorsService";
13
13
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
14
14
import { IPurchaseRequest, IPurchaseResponse, SlotPurchase, IInventoryChanges } from "@/src/types/purchaseTypes";
15
15
import { logger } from "@/src/utils/logger";
@@ -53,8 +53,9 @@ export const handlePurchase = async (
53
53
const prePurchaseInventoryChanges: IInventoryChanges = {};
54
54
let seed: bigint | undefined;
55
55
if (purchaseRequest.PurchaseParams.Source == 7) {
56
const manifest = getVendorManifestByOid(purchaseRequest.PurchaseParams.SourceId!);
56
let manifest = getVendorManifestByOid(purchaseRequest.PurchaseParams.SourceId!);
57
57
if (manifest) {
58
manifest = applyStandingToVendorManifest(inventory, manifest);
58
59
let ItemId: string | undefined;
59
60
if (purchaseRequest.PurchaseParams.ExtraPurchaseInfoJson) {
60
61
ItemId = (JSON.parse(purchaseRequest.PurchaseParams.ExtraPurchaseInfoJson) as { ItemId: string })
@@ -92,7 +93,7 @@ export const handlePurchase = async (
92
93
if (!config.noVendorPurchaseLimits && ItemId) {
93
94
inventory.RecentVendorPurchases ??= [];
94
95
let vendorPurchases = inventory.RecentVendorPurchases.find(
95
x => x.VendorType == manifest.VendorInfo.TypeName
96
x => x.VendorType == manifest!.VendorInfo.TypeName
96
97
);
97
98
if (!vendorPurchases) {
98
99
vendorPurchases =
@@ -1,5 +1,6 @@
1
1
import { unixTimesInMs } from "@/src/constants/timeConstants";
2
2
import { catBreadHash } from "@/src/helpers/stringHelpers";
3
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
3
4
import { mixSeeds, SRng } from "@/src/services/rngService";
4
5
import { IMongoDate } from "@/src/types/commonTypes";
5
6
import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes";
@@ -159,6 +160,43 @@ export const getVendorManifestByOid = (oid: string): IVendorManifest | undefined
159
160
return undefined;
160
161
};
161
162
163
export const applyStandingToVendorManifest = (
164
inventory: TInventoryDatabaseDocument,
165
vendorManifest: IVendorManifest
166
): IVendorManifest => {
167
return {
168
VendorInfo: {
169
...vendorManifest.VendorInfo,
170
ItemManifest: [...vendorManifest.VendorInfo.ItemManifest].map(offer => {
171
if (offer.Affiliation && offer.ReductionPerPositiveRank && offer.IncreasePerNegativeRank) {
172
const title: number = inventory.Affiliations.find(x => x.Tag == offer.Affiliation)?.Title ?? 0;
173
const factor =
174
1 + (title < 0 ? offer.IncreasePerNegativeRank : offer.ReductionPerPositiveRank) * title * -1;
175
//console.log(offer.Affiliation, title, factor);
176
if (factor) {
177
offer = { ...offer };
178
if (offer.RegularPrice) {
179
offer.RegularPriceBeforeDiscount = offer.RegularPrice;
180
offer.RegularPrice = [
181
Math.trunc(offer.RegularPriceBeforeDiscount[0] * factor),
182
Math.trunc(offer.RegularPriceBeforeDiscount[1] * factor)
183
];
184
}
185
if (offer.ItemPrices) {
186
offer.ItemPricesBeforeDiscount = offer.ItemPrices;
187
offer.ItemPrices = [];
188
for (const item of offer.ItemPricesBeforeDiscount) {
189
offer.ItemPrices.push({ ...item, ItemCount: Math.trunc(item.ItemCount * factor) });
190
}
191
}
192
}
193
}
194
return offer;
195
})
196
}
197
};
198
};
199
162
200
const preprocessVendorManifest = (originalManifest: IVendorManifest): IVendorManifest => {
163
201
if (Date.now() >= parseInt(originalManifest.VendorInfo.Expiry.$date.$numberLong)) {
164
202
const manifest = structuredClone(originalManifest);
@@ -190,24 +228,27 @@ const toRange = (value: IRange | number): IRange => {
190
228
return value;
191
229
};
192
230
193
const vendorInfoCache: Record<string, IVendorInfo> = {};
231
const vendorManifestCache: Record<string, IVendorManifest> = {};
194
232
195
233
const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifest => {
196
if (!(vendorInfo.TypeName in vendorInfoCache)) {
234
if (!(vendorInfo.TypeName in vendorManifestCache)) {
197
235
// eslint-disable-next-line @typescript-eslint/no-unused-vars
198
236
const { cycleOffset, cycleDuration, ...clientVendorInfo } = vendorInfo;
199
vendorInfoCache[vendorInfo.TypeName] = {
200
...clientVendorInfo,
201
ItemManifest: [],
202
Expiry: { $date: { $numberLong: "0" } }
237
vendorManifestCache[vendorInfo.TypeName] = {
238
VendorInfo: {
239
...clientVendorInfo,
240
ItemManifest: [],
241
Expiry: { $date: { $numberLong: "0" } }
242
}
203
243
};
204
244
}
205
const processed = vendorInfoCache[vendorInfo.TypeName];
206
if (Date.now() >= parseInt(processed.Expiry.$date.$numberLong)) {
245
const cacheEntry = vendorManifestCache[vendorInfo.TypeName];
246
const info = cacheEntry.VendorInfo;
247
if (Date.now() >= parseInt(info.Expiry.$date.$numberLong)) {
207
248
// Remove expired offers
208
for (let i = 0; i != processed.ItemManifest.length; ) {
209
if (Date.now() >= parseInt(processed.ItemManifest[i].Expiry.$date.$numberLong)) {
210
processed.ItemManifest.splice(i, 1);
249
for (let i = 0; i != info.ItemManifest.length; ) {
250
if (Date.now() >= parseInt(info.ItemManifest[i].Expiry.$date.$numberLong)) {
251
info.ItemManifest.splice(i, 1);
211
252
} else {
212
253
++i;
213
254
}
@@ -228,7 +269,7 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
228
269
!manifest.isOneBinPerCycle
229
270
) {
230
271
const numItemsTarget = rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue);
231
while (processed.ItemManifest.length + offersToAdd.length < numItemsTarget) {
272
while (info.ItemManifest.length + offersToAdd.length < numItemsTarget) {
232
273
// TODO: Consider per-bin item limits
233
274
// TODO: Consider item probability weightings
234
275
offersToAdd.push(rng.randomElement(manifest.items)!);
@@ -307,20 +348,18 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
307
348
item.LocTagRandSeed = (BigInt(highDword) << 32n) | (BigInt(item.LocTagRandSeed) & 0xffffffffn);
308
349
}
309
350
}
310
processed.ItemManifest.push(item);
351
info.ItemManifest.push(item);
311
352
}
312
353
313
354
// Update vendor expiry
314
355
let soonestOfferExpiry: number = Number.MAX_SAFE_INTEGER;
315
for (const offer of processed.ItemManifest) {
356
for (const offer of info.ItemManifest) {
316
357
const offerExpiry = parseInt(offer.Expiry.$date.$numberLong);
317
358
if (soonestOfferExpiry > offerExpiry) {
318
359
soonestOfferExpiry = offerExpiry;
319
360
}
320
361
}
321
processed.Expiry.$date.$numberLong = soonestOfferExpiry.toString();
362
info.Expiry.$date.$numberLong = soonestOfferExpiry.toString();
322
363
}
323
return {
324
VendorInfo: processed
325
};
364
return cacheEntry;
326
365
};
@@ -15,10 +15,16 @@ export interface IItemManifest {
15
15
QuantityMultiplier: number;
16
16
Expiry: IMongoDate; // Either a date in the distant future or a period in milliseconds for preprocessing.
17
17
PurchaseQuantityLimit?: number;
18
Affiliation?: string;
19
MinAffiliationRank?: number;
20
ReductionPerPositiveRank?: number;
21
IncreasePerNegativeRank?: number;
18
22
RotatedWeekly?: boolean;
19
23
AllowMultipurchase: boolean;
20
24
LocTagRandSeed?: number | bigint;
21
25
Id: IOid;
26
RegularPriceBeforeDiscount?: number[];
27
ItemPricesBeforeDiscount?: IItemPrice[];
22
28
}
23
29
24
30
export interface IVendorInfo {