Fix flea selling issues (!374)
Co-authored-by: Dev <dev@dev.sp-tarkov.com> Reviewed-on: https://dev.sp-tarkov.com/SPT/Server/pulls/374
This commit is contained in:
parent
77da49bb9e
commit
4fd113d00d
@ -414,8 +414,6 @@ export class RagfairController
|
||||
{
|
||||
const output = this.eventOutputHolder.getOutput(sessionID);
|
||||
const fullProfile = this.saveServer.getProfile(sessionID);
|
||||
const sellAsPack = offerRequest.sellInOnePiece; // a group of items that much be all purchased at once
|
||||
const itemsToListCount = offerRequest.items.length; // Count of root items being sold (no children)
|
||||
|
||||
const validationMessage = "";
|
||||
if (!this.isValidPlayerOfferRequest(offerRequest, validationMessage))
|
||||
@ -429,60 +427,70 @@ export class RagfairController
|
||||
return this.httpResponse.appendErrorToOutput(output, "Unknown offer type, cannot list item on flea");
|
||||
}
|
||||
|
||||
switch (typeOfOffer)
|
||||
{
|
||||
case FleaOfferType.SINGLE:
|
||||
return this.createSingleOffer(sessionID, offerRequest, fullProfile, output);
|
||||
case FleaOfferType.MULTI:
|
||||
return this.createMultiOffer(sessionID, offerRequest, fullProfile, output);
|
||||
case FleaOfferType.PACK:
|
||||
return this.createPackOffer(sessionID, offerRequest, fullProfile, output);
|
||||
}
|
||||
}
|
||||
|
||||
protected createSingleOffer(
|
||||
sessionID: string,
|
||||
offerRequest: IAddOfferRequestData,
|
||||
fullProfile: ISptProfile,
|
||||
output: IItemEventRouterResponse): IItemEventRouterResponse
|
||||
{
|
||||
const pmcData = fullProfile.characters.pmc;
|
||||
const itemsToListCount = offerRequest.items.length; // Does not count stack size, only items
|
||||
|
||||
// Find items to be listed on flea from player inventory
|
||||
const { items: itemsInInventoryToList, errorMessage: itemsInInventoryError }
|
||||
const { items: itemsAndChildrenInInventoryToList, errorMessage: itemsInInventoryError }
|
||||
= this.getItemsToListOnFleaFromInventory(pmcData, offerRequest.items);
|
||||
if (!itemsInInventoryToList || itemsInInventoryError)
|
||||
if (!itemsAndChildrenInInventoryToList || itemsInInventoryError)
|
||||
{
|
||||
this.httpResponse.appendErrorToOutput(output, itemsInInventoryError);
|
||||
}
|
||||
|
||||
// Total count of items summed using their stack counts
|
||||
const stackCountTotal = this.ragfairOfferHelper.getTotalStackCountSize(itemsAndChildrenInInventoryToList);
|
||||
|
||||
// Checks are done, create the offer
|
||||
const playerListedPriceInRub = this.calculateRequirementsPriceInRub(offerRequest.requirements);
|
||||
const offer = this.createPlayerOffer(
|
||||
sessionID,
|
||||
offerRequest.requirements,
|
||||
this.ragfairHelper.mergeStackable(itemsInInventoryToList),
|
||||
sellAsPack,
|
||||
itemsAndChildrenInInventoryToList[0],
|
||||
false,
|
||||
);
|
||||
const rootItem = offer.items[0];
|
||||
|
||||
// Get average of items quality+children
|
||||
const qualityMultiplier = this.itemHelper.getItemQualityModifierForItems(offer.items, true);
|
||||
let averageOfferPrice = this.ragfairPriceService.getFleaPriceForOfferItems(offer.items);
|
||||
|
||||
// Average offer price for single item (or whole weapon)
|
||||
let averageOfferPriceSingleItem = this.ragfairPriceService.getFleaPriceForOfferItems(offer.items);
|
||||
|
||||
// Check for and apply item price modifer if it exists in config
|
||||
const itemPriceModifer = this.ragfairConfig.dynamic.itemPriceMultiplier[rootItem._tpl];
|
||||
if (itemPriceModifer)
|
||||
{
|
||||
averageOfferPrice *= itemPriceModifer;
|
||||
averageOfferPriceSingleItem *= itemPriceModifer;
|
||||
}
|
||||
|
||||
// Multiply single item price by quality
|
||||
averageOfferPrice *= qualityMultiplier;
|
||||
|
||||
// Define packs as a single count item
|
||||
const itemStackCount = sellAsPack
|
||||
? 1
|
||||
: itemsToListCount;
|
||||
|
||||
// Average out price of offer
|
||||
const averageSingleItemPrice = sellAsPack
|
||||
? averageOfferPrice / itemsToListCount // Packs contains multiple items sold as one
|
||||
: averageOfferPrice / itemStackCount; // Normal offer, single items can be purchased from listing
|
||||
|
||||
// Get averaged price of player listing to use when calculating sell chance
|
||||
const averagePlayerListedPriceInRub = sellAsPack
|
||||
? playerListedPriceInRub / itemsToListCount
|
||||
: playerListedPriceInRub;
|
||||
averageOfferPriceSingleItem *= qualityMultiplier;
|
||||
|
||||
// Packs are reduced to the average price of a single item in the pack vs the averaged single price of an item
|
||||
const sellChancePercent = this.ragfairSellHelper.calculateSellChance(
|
||||
averageSingleItemPrice,
|
||||
averagePlayerListedPriceInRub,
|
||||
averageOfferPriceSingleItem,
|
||||
playerListedPriceInRub,
|
||||
qualityMultiplier,
|
||||
);
|
||||
offer.sellResult = this.ragfairSellHelper.rollForSale(sellChancePercent, itemStackCount);
|
||||
offer.sellResult = this.ragfairSellHelper.rollForSale(sellChancePercent, itemsToListCount);
|
||||
|
||||
// Subtract flea market fee from stash
|
||||
if (this.ragfairConfig.sell.fees)
|
||||
@ -492,7 +500,217 @@ export class RagfairController
|
||||
rootItem,
|
||||
pmcData,
|
||||
playerListedPriceInRub,
|
||||
itemStackCount,
|
||||
stackCountTotal,
|
||||
offerRequest,
|
||||
output,
|
||||
);
|
||||
if (taxFeeChargeFailed)
|
||||
{
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
// Add offer to players profile + add to client response
|
||||
fullProfile.characters.pmc.RagfairInfo.offers.push(offer);
|
||||
output.profileChanges[sessionID].ragFairOffers.push(offer);
|
||||
|
||||
// Remove items from inventory after creating offer
|
||||
for (const itemToRemove of offerRequest.items)
|
||||
{
|
||||
this.inventoryHelper.removeItem(pmcData, itemToRemove, sessionID, output);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
protected createMultiOffer(
|
||||
sessionID: string,
|
||||
offerRequest: IAddOfferRequestData,
|
||||
fullProfile: ISptProfile,
|
||||
output: IItemEventRouterResponse): IItemEventRouterResponse
|
||||
{
|
||||
const pmcData = fullProfile.characters.pmc;
|
||||
const itemsToListCount = offerRequest.items.length; // Does not count stack size, only items
|
||||
|
||||
// multi-offers are all the same item,
|
||||
// Get first item and its children and use as template
|
||||
const firstListingAndChidren = this.itemHelper.findAndReturnChildrenAsItems(
|
||||
pmcData.Inventory.items,
|
||||
offerRequest.items[0]);
|
||||
|
||||
// Find items to be listed on flea (+ children) from player inventory
|
||||
const { items: itemsAndChildrenInInventoryToList, errorMessage: itemsInInventoryError }
|
||||
= this.getItemsToListOnFleaFromInventory(pmcData, offerRequest.items);
|
||||
if (!itemsAndChildrenInInventoryToList || itemsInInventoryError)
|
||||
{
|
||||
this.httpResponse.appendErrorToOutput(output, itemsInInventoryError);
|
||||
}
|
||||
|
||||
// Total count of items summed using their stack counts
|
||||
const stackCountTotal = this.ragfairOfferHelper.getTotalStackCountSize(itemsAndChildrenInInventoryToList);
|
||||
|
||||
// When listing identical items on flea, condense separate items into one stack with a merged stack count
|
||||
// e.g. 2 ammo items, stackObjectCount = 3 for each, will result in 1 stack of 6
|
||||
if (!firstListingAndChidren[0].upd)
|
||||
{
|
||||
firstListingAndChidren[0].upd = {};
|
||||
}
|
||||
firstListingAndChidren[0].upd.StackObjectsCount = stackCountTotal;
|
||||
|
||||
// Create flea object
|
||||
const offer = this.createPlayerOffer(
|
||||
sessionID,
|
||||
offerRequest.requirements,
|
||||
firstListingAndChidren,
|
||||
false,
|
||||
);
|
||||
|
||||
// This is the item that will be listed on flea, has merged stackObjectCount
|
||||
const newRootOfferItem = offer.items[0];
|
||||
|
||||
// Average offer price for single item (or whole weapon)
|
||||
let averageOfferPrice = this.ragfairPriceService.getFleaPriceForOfferItems(offer.items);
|
||||
|
||||
// Check for and apply item price modifer if it exists in config
|
||||
const itemPriceModifer = this.ragfairConfig.dynamic.itemPriceMultiplier[newRootOfferItem._tpl];
|
||||
if (itemPriceModifer)
|
||||
{
|
||||
averageOfferPrice *= itemPriceModifer;
|
||||
}
|
||||
|
||||
// Get average of item+children quality
|
||||
const qualityMultiplier = this.itemHelper.getItemQualityModifierForItems(offer.items, true);
|
||||
|
||||
// Multiply single item price by quality
|
||||
averageOfferPrice *= qualityMultiplier;
|
||||
|
||||
// Get price player listed items for in roubles
|
||||
const playerListedPriceInRub = this.calculateRequirementsPriceInRub(offerRequest.requirements);
|
||||
|
||||
// Roll sale chance
|
||||
const sellChancePercent = this.ragfairSellHelper.calculateSellChance(
|
||||
averageOfferPrice,
|
||||
playerListedPriceInRub,
|
||||
qualityMultiplier,
|
||||
);
|
||||
|
||||
// Create array of sell times for items listed
|
||||
offer.sellResult = this.ragfairSellHelper.rollForSale(sellChancePercent, itemsToListCount);
|
||||
|
||||
// Subtract flea market fee from stash
|
||||
if (this.ragfairConfig.sell.fees)
|
||||
{
|
||||
const taxFeeChargeFailed = this.chargePlayerTaxFee(
|
||||
sessionID,
|
||||
newRootOfferItem,
|
||||
pmcData,
|
||||
playerListedPriceInRub,
|
||||
stackCountTotal,
|
||||
offerRequest,
|
||||
output,
|
||||
);
|
||||
if (taxFeeChargeFailed)
|
||||
{
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
// Add offer to players profile + add to client response
|
||||
fullProfile.characters.pmc.RagfairInfo.offers.push(offer);
|
||||
output.profileChanges[sessionID].ragFairOffers.push(offer);
|
||||
|
||||
// Remove items from inventory after creating offer
|
||||
for (const itemToRemove of offerRequest.items)
|
||||
{
|
||||
this.inventoryHelper.removeItem(pmcData, itemToRemove, sessionID, output);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
protected createPackOffer(
|
||||
sessionID: string,
|
||||
offerRequest: IAddOfferRequestData,
|
||||
fullProfile: ISptProfile,
|
||||
output: IItemEventRouterResponse): IItemEventRouterResponse
|
||||
{
|
||||
const pmcData = fullProfile.characters.pmc;
|
||||
const itemsToListCount = offerRequest.items.length; // Does not count stack size, only items
|
||||
|
||||
// multi-offers are all the same item,
|
||||
// Get first item and its children and use as template
|
||||
const firstListingAndChidren = this.itemHelper.findAndReturnChildrenAsItems(
|
||||
pmcData.Inventory.items,
|
||||
offerRequest.items[0]);
|
||||
|
||||
// Find items to be listed on flea (+ children) from player inventory
|
||||
const { items: itemsAndChildrenInInventoryToList, errorMessage: itemsInInventoryError }
|
||||
= this.getItemsToListOnFleaFromInventory(pmcData, offerRequest.items);
|
||||
if (!itemsAndChildrenInInventoryToList || itemsInInventoryError)
|
||||
{
|
||||
this.httpResponse.appendErrorToOutput(output, itemsInInventoryError);
|
||||
}
|
||||
|
||||
// Total count of items summed using their stack counts
|
||||
const stackCountTotal = this.ragfairOfferHelper.getTotalStackCountSize(itemsAndChildrenInInventoryToList);
|
||||
|
||||
// When listing identical items on flea, condense separate items into one stack with a merged stack count
|
||||
// e.g. 2 ammo items, stackObjectCount = 3 for each, will result in 1 stack of 6
|
||||
if (!firstListingAndChidren[0].upd)
|
||||
{
|
||||
firstListingAndChidren[0].upd = {};
|
||||
}
|
||||
firstListingAndChidren[0].upd.StackObjectsCount = stackCountTotal;
|
||||
|
||||
// Create flea object
|
||||
const offer = this.createPlayerOffer(
|
||||
sessionID,
|
||||
offerRequest.requirements,
|
||||
firstListingAndChidren,
|
||||
true,
|
||||
);
|
||||
|
||||
// This is the item that will be listed on flea, has merged stackObjectCount
|
||||
const newRootOfferItem = offer.items[0];
|
||||
|
||||
// Single price for an item
|
||||
let singleItemPrice = this.ragfairPriceService.getFleaPriceForItem(firstListingAndChidren[0]._tpl);
|
||||
|
||||
// Check for and apply item price modifer if it exists in config
|
||||
const itemPriceModifer = this.ragfairConfig.dynamic.itemPriceMultiplier[newRootOfferItem._tpl];
|
||||
if (itemPriceModifer)
|
||||
{
|
||||
singleItemPrice *= itemPriceModifer;
|
||||
}
|
||||
|
||||
// Get average of item+children quality
|
||||
const qualityMultiplier = this.itemHelper.getItemQualityModifierForItems(offer.items, true);
|
||||
|
||||
// Multiply single item price by quality
|
||||
singleItemPrice *= qualityMultiplier;
|
||||
|
||||
// Get price player listed items for in roubles
|
||||
const playerListedPriceInRub = this.calculateRequirementsPriceInRub(offerRequest.requirements);
|
||||
|
||||
// Roll sale chance
|
||||
const sellChancePercent = this.ragfairSellHelper.calculateSellChance(
|
||||
singleItemPrice * stackCountTotal,
|
||||
playerListedPriceInRub,
|
||||
qualityMultiplier,
|
||||
);
|
||||
|
||||
// Create array of sell times for items listed + sell all at once as its a pack
|
||||
offer.sellResult = this.ragfairSellHelper.rollForSale(sellChancePercent, itemsToListCount, true);
|
||||
|
||||
// Subtract flea market fee from stash
|
||||
if (this.ragfairConfig.sell.fees)
|
||||
{
|
||||
const taxFeeChargeFailed = this.chargePlayerTaxFee(
|
||||
sessionID,
|
||||
newRootOfferItem,
|
||||
pmcData,
|
||||
playerListedPriceInRub,
|
||||
stackCountTotal,
|
||||
offerRequest,
|
||||
output,
|
||||
);
|
||||
@ -539,7 +757,7 @@ export class RagfairController
|
||||
* @param rootItem Base item being listed (used when client tax cost not found and must be done on server)
|
||||
* @param pmcData Player profile
|
||||
* @param requirementsPriceInRub Rouble cost player chose for listing (used when client tax cost not found and must be done on server)
|
||||
* @param itemStackCount How many items were listed in player (used when client tax cost not found and must be done on server)
|
||||
* @param itemStackCount How many items were listed by player (used when client tax cost not found and must be done on server)
|
||||
* @param offerRequest Add offer request object from client
|
||||
* @param output IItemEventRouterResponse
|
||||
* @returns True if charging tax to player failed
|
||||
@ -645,9 +863,9 @@ export class RagfairController
|
||||
protected getItemsToListOnFleaFromInventory(
|
||||
pmcData: IPmcData,
|
||||
itemIdsFromFleaOfferRequest: string[],
|
||||
): { items: Item[] | undefined, errorMessage: string | undefined }
|
||||
): { items: Item[][] | undefined, errorMessage: string | undefined }
|
||||
{
|
||||
const itemsToReturn = [];
|
||||
const itemsToReturn: Item[][] = [];
|
||||
let errorMessage: string | undefined = undefined;
|
||||
|
||||
// Count how many items are being sold and multiply the requested amount accordingly
|
||||
@ -665,7 +883,7 @@ export class RagfairController
|
||||
}
|
||||
|
||||
item = this.itemHelper.fixItemStackCount(item);
|
||||
itemsToReturn.push(...this.itemHelper.findAndReturnChildrenAsItems(pmcData.Inventory.items, itemId));
|
||||
itemsToReturn.push(this.itemHelper.findAndReturnChildrenAsItems(pmcData.Inventory.items, itemId));
|
||||
}
|
||||
|
||||
if (!itemsToReturn?.length)
|
||||
@ -689,12 +907,7 @@ export class RagfairController
|
||||
const loyalLevel = 1;
|
||||
const formattedItems: Item[] = items.map((item) =>
|
||||
{
|
||||
const isChild = items.some((it) => it._id === item.parentId);
|
||||
if (!isChild && !sellInOnePiece)
|
||||
{
|
||||
// Ensure offer with multiple of an item has its stack count reset
|
||||
item.upd.StackObjectsCount = 1;
|
||||
}
|
||||
const isChild = items.some((subItem) => subItem._id === item.parentId);
|
||||
|
||||
return {
|
||||
_id: item._id,
|
||||
|
@ -147,7 +147,7 @@ export class RagfairHelper
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges Root Items
|
||||
* Iterate over array of identical items and merge stack count
|
||||
* Ragfair allows abnormally large stacks.
|
||||
*/
|
||||
public mergeStackable(items: Item[]): Item[]
|
||||
|
@ -333,7 +333,8 @@ export class RagfairOfferHelper
|
||||
|
||||
for (const offer of profileOffers.values())
|
||||
{
|
||||
if (offer.sellResult && offer.sellResult.length > 0 && timestamp >= offer.sellResult[0].sellTime)
|
||||
if (offer.sellResult?.length > 0
|
||||
&& timestamp >= offer.sellResult[0].sellTime)
|
||||
{
|
||||
// Item sold
|
||||
let totalItemsCount = 1;
|
||||
@ -341,7 +342,8 @@ export class RagfairOfferHelper
|
||||
|
||||
if (!offer.sellInOnePiece)
|
||||
{
|
||||
totalItemsCount = offer.items.reduce((sum: number, item) => sum + item.upd.StackObjectsCount, 0);
|
||||
// offer.items.reduce((sum, item) => sum + item.upd?.StackObjectsCount ?? 0, 0);
|
||||
totalItemsCount = this.getTotalStackCountSize([offer.items]);
|
||||
boughtAmount = offer.sellResult[0].amount;
|
||||
}
|
||||
|
||||
@ -358,6 +360,28 @@ export class RagfairOfferHelper
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count up all rootitem StackObjectsCount properties of an array of items
|
||||
* @param itemsInInventoryToList items to sum up
|
||||
* @returns Total count
|
||||
*/
|
||||
public getTotalStackCountSize(itemsInInventoryToList: Item[][]): number
|
||||
{
|
||||
let total = 0;
|
||||
for (const itemAndChildren of itemsInInventoryToList)
|
||||
{
|
||||
for (const item of itemAndChildren)
|
||||
{
|
||||
if (item.slotId === "hideout")
|
||||
{
|
||||
total += item.upd?.StackObjectsCount ?? 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add amount to players ragfair rating
|
||||
* @param sessionId Profile to update
|
||||
@ -422,64 +446,23 @@ export class RagfairOfferHelper
|
||||
protected completeOffer(sessionID: string, offer: IRagfairOffer, boughtAmount: number): IItemEventRouterResponse
|
||||
{
|
||||
const itemTpl = offer.items[0]._tpl;
|
||||
let itemsToSend = [];
|
||||
let paymentItemsToSendToPlayer: Item[] = [];
|
||||
const offerStackCount = offer.items[0].upd.StackObjectsCount;
|
||||
|
||||
// Pack or ALL items of a multi-offer were bought - remove entire ofer
|
||||
if (offer.sellInOnePiece || boughtAmount === offerStackCount)
|
||||
{
|
||||
this.deleteOfferById(sessionID, offer._id);
|
||||
}
|
||||
else
|
||||
{
|
||||
offer.items[0].upd.StackObjectsCount -= boughtAmount;
|
||||
const rootItems = offer.items.filter((i) => i.parentId === "hideout");
|
||||
rootItems.splice(0, 1);
|
||||
const offerRootItem = offer.items[0];
|
||||
|
||||
let removeCount = boughtAmount;
|
||||
let idsToRemove: string[] = [];
|
||||
|
||||
while (removeCount > 0 && rootItems.length > 0)
|
||||
{
|
||||
const lastItem = rootItems[rootItems.length - 1];
|
||||
|
||||
if (lastItem.upd.StackObjectsCount > removeCount)
|
||||
{
|
||||
lastItem.upd.StackObjectsCount -= removeCount;
|
||||
removeCount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
removeCount -= lastItem.upd.StackObjectsCount;
|
||||
idsToRemove.push(lastItem._id);
|
||||
rootItems.splice(rootItems.length - 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
let foundNewItems = true;
|
||||
while (foundNewItems)
|
||||
{
|
||||
foundNewItems = false;
|
||||
|
||||
for (const id of idsToRemove)
|
||||
{
|
||||
const newIds = offer.items
|
||||
.filter((i) => !idsToRemove.includes(i._id) && idsToRemove.includes(i.parentId))
|
||||
.map((i) => i._id);
|
||||
if (newIds.length > 0)
|
||||
{
|
||||
foundNewItems = true;
|
||||
idsToRemove = [...idsToRemove, ...newIds];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (idsToRemove.length > 0)
|
||||
{
|
||||
offer.items = offer.items.filter((i) => !idsToRemove.includes(i._id));
|
||||
}
|
||||
// Reduce offer root items stack count
|
||||
offerRootItem.upd.StackObjectsCount -= boughtAmount;
|
||||
}
|
||||
|
||||
// Assemble the payment item(s)
|
||||
// Assemble payment to send to seller now offer was purchased
|
||||
for (const requirement of offer.requirements)
|
||||
{
|
||||
// Create an item template item
|
||||
@ -504,7 +487,7 @@ export class RagfairOfferHelper
|
||||
}
|
||||
}
|
||||
|
||||
itemsToSend = [...itemsToSend, ...outItems];
|
||||
paymentItemsToSendToPlayer = [...paymentItemsToSendToPlayer, ...outItems];
|
||||
}
|
||||
}
|
||||
|
||||
@ -519,7 +502,7 @@ export class RagfairOfferHelper
|
||||
this.traderHelper.getTraderById(Traders.RAGMAN),
|
||||
MessageType.FLEAMARKET_MESSAGE,
|
||||
this.getLocalisedOfferSoldMessage(itemTpl, boughtAmount),
|
||||
itemsToSend,
|
||||
paymentItemsToSendToPlayer,
|
||||
this.timeUtil.getHoursAsSeconds(
|
||||
this.questHelper.getMailItemRedeemTimeHoursForProfile(this.profileHelper.getPmcProfile(sessionID))),
|
||||
undefined,
|
||||
|
@ -65,9 +65,10 @@ export class RagfairSellHelper
|
||||
* Get array of item count and sell time (empty array = no sell)
|
||||
* @param sellChancePercent chance item will sell
|
||||
* @param itemSellCount count of items to sell
|
||||
* @param sellInOneGo All items listed get sold at once
|
||||
* @returns Array of purchases of item(s) listed
|
||||
*/
|
||||
public rollForSale(sellChancePercent: number, itemSellCount: number): SellResult[]
|
||||
public rollForSale(sellChancePercent: number, itemSellCount: number, sellInOneGo = false): SellResult[]
|
||||
{
|
||||
const startTime = this.timeUtil.getTimestamp();
|
||||
|
||||
@ -103,7 +104,9 @@ export class RagfairSellHelper
|
||||
|
||||
while (remainingCount > 0 && sellTime < endTime)
|
||||
{
|
||||
const boughtAmount = this.randomUtil.getInt(1, remainingCount);
|
||||
const boughtAmount = (sellInOneGo)
|
||||
? remainingCount
|
||||
: this.randomUtil.getInt(1, remainingCount);
|
||||
if (this.randomUtil.getChance100(effectiveSellChance))
|
||||
{
|
||||
// Passed roll check, item will be sold
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { ItemHelper } from "@spt/helpers/ItemHelper";
|
||||
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
||||
import { RagfairServerHelper } from "@spt/helpers/RagfairServerHelper";
|
||||
import { Item } from "@spt/models/eft/common/tables/IItem";
|
||||
@ -11,6 +12,7 @@ import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||
import { SaveServer } from "@spt/servers/SaveServer";
|
||||
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||
import { RagfairOfferHolder } from "@spt/utils/RagfairOfferHolder";
|
||||
import { TimeUtil } from "@spt/utils/TimeUtil";
|
||||
@ -31,11 +33,13 @@ export class RagfairOfferService
|
||||
@inject("DatabaseService") protected databaseService: DatabaseService,
|
||||
@inject("SaveServer") protected saveServer: SaveServer,
|
||||
@inject("RagfairServerHelper") protected ragfairServerHelper: RagfairServerHelper,
|
||||
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
||||
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
||||
@inject("EventOutputHolder") protected eventOutputHolder: EventOutputHolder,
|
||||
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
|
||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||
)
|
||||
{
|
||||
this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR);
|
||||
@ -276,7 +280,60 @@ export class RagfairOfferService
|
||||
this.ragfairOfferHandler.removeOffer(playerOffer);
|
||||
|
||||
// Send failed offer items to player in mail
|
||||
this.ragfairServerHelper.returnItems(profile.sessionId, playerOffer.items);
|
||||
const unstackedItems = this.unstackOfferItems(playerOffer.items);
|
||||
this.ragfairServerHelper.returnItems(profile.sessionId, unstackedItems);
|
||||
profile.RagfairInfo.offers.splice(offerinProfileIndex, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flea offer items are stacked up often beyond the StackMaxSize limit
|
||||
* Un stack the items into an array of root items and their children
|
||||
* Will create new items equal to the
|
||||
* @param items Offer items to unstack
|
||||
* @returns Unstacked array of items
|
||||
*/
|
||||
protected unstackOfferItems(items: Item[]): Item[]
|
||||
{
|
||||
const result: Item[] = [];
|
||||
const rootItem = items[0];
|
||||
const itemDetails = this.itemHelper.getItem(rootItem._tpl);
|
||||
const itemMaxStackSize = itemDetails[1]._props.StackMaxSize ?? 1;
|
||||
|
||||
const totalItemCount = rootItem.upd?.StackObjectsCount ?? 1;
|
||||
|
||||
// Items within stack tolerance, return existing data - no changes needed
|
||||
if (totalItemCount <= itemMaxStackSize)
|
||||
{
|
||||
return items;
|
||||
}
|
||||
|
||||
// Single item with no children e.g. ammo, use existing de-stacking code
|
||||
if (items.length === 1)
|
||||
{
|
||||
return this.itemHelper.splitStack(rootItem);
|
||||
}
|
||||
|
||||
// Item with children, needs special handling
|
||||
// Force new item to have stack size of 1
|
||||
for (let index = 0; index < totalItemCount; index++)
|
||||
{
|
||||
const itemAndChildrenClone = this.cloner.clone(items);
|
||||
|
||||
// Ensure upd object exits
|
||||
itemAndChildrenClone[0].upd ||= {};
|
||||
|
||||
// Force item to be singular
|
||||
itemAndChildrenClone[0].upd.StackObjectsCount = 1;
|
||||
|
||||
// Ensure items IDs are unique to prevent collisions when added to player inventory
|
||||
const reparentedItemAndChildren = this.itemHelper.reparentItemAndChildren(
|
||||
itemAndChildrenClone[0],
|
||||
itemAndChildrenClone);
|
||||
this.itemHelper.remapRootItemId(reparentedItemAndChildren);
|
||||
|
||||
result.push(...reparentedItemAndChildren);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user