XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

SpaceNinjaServer

A simple server for a small space ninja game

公开
关注 0 Fork 1 Star 0
返回提交历史

XFEstudio/SpaceNinjaServer

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
Sainan <63328889+Sainan@users.noreply.github.com>
提交于

代码差异

4 个文件 +83 -31
Modified src/controllers/api/getVendorInfoController.ts +16 -10
@@ -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 };
Modified src/services/purchaseService.ts +4 -3
@@ -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 =
Modified src/services/serversideVendorsService.ts +57 -18
@@ -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 };
Modified src/types/vendorTypes.ts +6 -0
@@ -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 {