mirror of
https://github.com/sp-tarkov/server.git
synced 2025-02-13 06:10:44 -05:00
Fix flea selling issues (!374)
Co-authored-by: Dev <dev@dev.sp-tarkov.com> Reviewed-on: SPT/Server#374
This commit is contained in:
parent
77da49bb9e
commit
4fd113d00d
@ -414,8 +414,6 @@ export class RagfairController
|
|||||||
{
|
{
|
||||||
const output = this.eventOutputHolder.getOutput(sessionID);
|
const output = this.eventOutputHolder.getOutput(sessionID);
|
||||||
const fullProfile = this.saveServer.getProfile(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 = "";
|
const validationMessage = "";
|
||||||
if (!this.isValidPlayerOfferRequest(offerRequest, 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");
|
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
|
// 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);
|
= this.getItemsToListOnFleaFromInventory(pmcData, offerRequest.items);
|
||||||
if (!itemsInInventoryToList || itemsInInventoryError)
|
if (!itemsAndChildrenInInventoryToList || itemsInInventoryError)
|
||||||
{
|
{
|
||||||
this.httpResponse.appendErrorToOutput(output, 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
|
// Checks are done, create the offer
|
||||||
const playerListedPriceInRub = this.calculateRequirementsPriceInRub(offerRequest.requirements);
|
const playerListedPriceInRub = this.calculateRequirementsPriceInRub(offerRequest.requirements);
|
||||||
const offer = this.createPlayerOffer(
|
const offer = this.createPlayerOffer(
|
||||||
sessionID,
|
sessionID,
|
||||||
offerRequest.requirements,
|
offerRequest.requirements,
|
||||||
this.ragfairHelper.mergeStackable(itemsInInventoryToList),
|
itemsAndChildrenInInventoryToList[0],
|
||||||
sellAsPack,
|
false,
|
||||||
);
|
);
|
||||||
const rootItem = offer.items[0];
|
const rootItem = offer.items[0];
|
||||||
|
|
||||||
// Get average of items quality+children
|
// Get average of items quality+children
|
||||||
const qualityMultiplier = this.itemHelper.getItemQualityModifierForItems(offer.items, true);
|
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
|
// Check for and apply item price modifer if it exists in config
|
||||||
const itemPriceModifer = this.ragfairConfig.dynamic.itemPriceMultiplier[rootItem._tpl];
|
const itemPriceModifer = this.ragfairConfig.dynamic.itemPriceMultiplier[rootItem._tpl];
|
||||||
if (itemPriceModifer)
|
if (itemPriceModifer)
|
||||||
{
|
{
|
||||||
averageOfferPrice *= itemPriceModifer;
|
averageOfferPriceSingleItem *= itemPriceModifer;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multiply single item price by quality
|
// Multiply single item price by quality
|
||||||
averageOfferPrice *= qualityMultiplier;
|
averageOfferPriceSingleItem *= 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;
|
|
||||||
|
|
||||||
// Packs are reduced to the average price of a single item in the pack vs the averaged single price of an item
|
// 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(
|
const sellChancePercent = this.ragfairSellHelper.calculateSellChance(
|
||||||
averageSingleItemPrice,
|
averageOfferPriceSingleItem,
|
||||||
averagePlayerListedPriceInRub,
|
playerListedPriceInRub,
|
||||||
qualityMultiplier,
|
qualityMultiplier,
|
||||||
);
|
);
|
||||||
offer.sellResult = this.ragfairSellHelper.rollForSale(sellChancePercent, itemStackCount);
|
offer.sellResult = this.ragfairSellHelper.rollForSale(sellChancePercent, itemsToListCount);
|
||||||
|
|
||||||
// Subtract flea market fee from stash
|
// Subtract flea market fee from stash
|
||||||
if (this.ragfairConfig.sell.fees)
|
if (this.ragfairConfig.sell.fees)
|
||||||
@ -492,7 +500,217 @@ export class RagfairController
|
|||||||
rootItem,
|
rootItem,
|
||||||
pmcData,
|
pmcData,
|
||||||
playerListedPriceInRub,
|
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,
|
offerRequest,
|
||||||
output,
|
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 rootItem Base item being listed (used when client tax cost not found and must be done on server)
|
||||||
* @param pmcData Player profile
|
* @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 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 offerRequest Add offer request object from client
|
||||||
* @param output IItemEventRouterResponse
|
* @param output IItemEventRouterResponse
|
||||||
* @returns True if charging tax to player failed
|
* @returns True if charging tax to player failed
|
||||||
@ -645,9 +863,9 @@ export class RagfairController
|
|||||||
protected getItemsToListOnFleaFromInventory(
|
protected getItemsToListOnFleaFromInventory(
|
||||||
pmcData: IPmcData,
|
pmcData: IPmcData,
|
||||||
itemIdsFromFleaOfferRequest: string[],
|
itemIdsFromFleaOfferRequest: string[],
|
||||||
): { items: Item[] | undefined, errorMessage: string | undefined }
|
): { items: Item[][] | undefined, errorMessage: string | undefined }
|
||||||
{
|
{
|
||||||
const itemsToReturn = [];
|
const itemsToReturn: Item[][] = [];
|
||||||
let errorMessage: string | undefined = undefined;
|
let errorMessage: string | undefined = undefined;
|
||||||
|
|
||||||
// Count how many items are being sold and multiply the requested amount accordingly
|
// 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);
|
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)
|
if (!itemsToReturn?.length)
|
||||||
@ -689,12 +907,7 @@ export class RagfairController
|
|||||||
const loyalLevel = 1;
|
const loyalLevel = 1;
|
||||||
const formattedItems: Item[] = items.map((item) =>
|
const formattedItems: Item[] = items.map((item) =>
|
||||||
{
|
{
|
||||||
const isChild = items.some((it) => it._id === item.parentId);
|
const isChild = items.some((subItem) => subItem._id === item.parentId);
|
||||||
if (!isChild && !sellInOnePiece)
|
|
||||||
{
|
|
||||||
// Ensure offer with multiple of an item has its stack count reset
|
|
||||||
item.upd.StackObjectsCount = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_id: item._id,
|
_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.
|
* Ragfair allows abnormally large stacks.
|
||||||
*/
|
*/
|
||||||
public mergeStackable(items: Item[]): Item[]
|
public mergeStackable(items: Item[]): Item[]
|
||||||
|
@ -333,7 +333,8 @@ export class RagfairOfferHelper
|
|||||||
|
|
||||||
for (const offer of profileOffers.values())
|
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
|
// Item sold
|
||||||
let totalItemsCount = 1;
|
let totalItemsCount = 1;
|
||||||
@ -341,7 +342,8 @@ export class RagfairOfferHelper
|
|||||||
|
|
||||||
if (!offer.sellInOnePiece)
|
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;
|
boughtAmount = offer.sellResult[0].amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,6 +360,28 @@ export class RagfairOfferHelper
|
|||||||
return true;
|
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
|
* Add amount to players ragfair rating
|
||||||
* @param sessionId Profile to update
|
* @param sessionId Profile to update
|
||||||
@ -422,64 +446,23 @@ export class RagfairOfferHelper
|
|||||||
protected completeOffer(sessionID: string, offer: IRagfairOffer, boughtAmount: number): IItemEventRouterResponse
|
protected completeOffer(sessionID: string, offer: IRagfairOffer, boughtAmount: number): IItemEventRouterResponse
|
||||||
{
|
{
|
||||||
const itemTpl = offer.items[0]._tpl;
|
const itemTpl = offer.items[0]._tpl;
|
||||||
let itemsToSend = [];
|
let paymentItemsToSendToPlayer: Item[] = [];
|
||||||
const offerStackCount = offer.items[0].upd.StackObjectsCount;
|
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)
|
if (offer.sellInOnePiece || boughtAmount === offerStackCount)
|
||||||
{
|
{
|
||||||
this.deleteOfferById(sessionID, offer._id);
|
this.deleteOfferById(sessionID, offer._id);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
offer.items[0].upd.StackObjectsCount -= boughtAmount;
|
const offerRootItem = offer.items[0];
|
||||||
const rootItems = offer.items.filter((i) => i.parentId === "hideout");
|
|
||||||
rootItems.splice(0, 1);
|
|
||||||
|
|
||||||
let removeCount = boughtAmount;
|
// Reduce offer root items stack count
|
||||||
let idsToRemove: string[] = [];
|
offerRootItem.upd.StackObjectsCount -= boughtAmount;
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assemble the payment item(s)
|
// Assemble payment to send to seller now offer was purchased
|
||||||
for (const requirement of offer.requirements)
|
for (const requirement of offer.requirements)
|
||||||
{
|
{
|
||||||
// Create an item template item
|
// 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),
|
this.traderHelper.getTraderById(Traders.RAGMAN),
|
||||||
MessageType.FLEAMARKET_MESSAGE,
|
MessageType.FLEAMARKET_MESSAGE,
|
||||||
this.getLocalisedOfferSoldMessage(itemTpl, boughtAmount),
|
this.getLocalisedOfferSoldMessage(itemTpl, boughtAmount),
|
||||||
itemsToSend,
|
paymentItemsToSendToPlayer,
|
||||||
this.timeUtil.getHoursAsSeconds(
|
this.timeUtil.getHoursAsSeconds(
|
||||||
this.questHelper.getMailItemRedeemTimeHoursForProfile(this.profileHelper.getPmcProfile(sessionID))),
|
this.questHelper.getMailItemRedeemTimeHoursForProfile(this.profileHelper.getPmcProfile(sessionID))),
|
||||||
undefined,
|
undefined,
|
||||||
|
@ -65,9 +65,10 @@ export class RagfairSellHelper
|
|||||||
* Get array of item count and sell time (empty array = no sell)
|
* Get array of item count and sell time (empty array = no sell)
|
||||||
* @param sellChancePercent chance item will sell
|
* @param sellChancePercent chance item will sell
|
||||||
* @param itemSellCount count of items to 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
|
* @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();
|
const startTime = this.timeUtil.getTimestamp();
|
||||||
|
|
||||||
@ -103,7 +104,9 @@ export class RagfairSellHelper
|
|||||||
|
|
||||||
while (remainingCount > 0 && sellTime < endTime)
|
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))
|
if (this.randomUtil.getChance100(effectiveSellChance))
|
||||||
{
|
{
|
||||||
// Passed roll check, item will be sold
|
// Passed roll check, item will be sold
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { inject, injectable } from "tsyringe";
|
import { inject, injectable } from "tsyringe";
|
||||||
|
import { ItemHelper } from "@spt/helpers/ItemHelper";
|
||||||
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
||||||
import { RagfairServerHelper } from "@spt/helpers/RagfairServerHelper";
|
import { RagfairServerHelper } from "@spt/helpers/RagfairServerHelper";
|
||||||
import { Item } from "@spt/models/eft/common/tables/IItem";
|
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 { SaveServer } from "@spt/servers/SaveServer";
|
||||||
import { DatabaseService } from "@spt/services/DatabaseService";
|
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||||
|
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||||
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
import { HttpResponseUtil } from "@spt/utils/HttpResponseUtil";
|
||||||
import { RagfairOfferHolder } from "@spt/utils/RagfairOfferHolder";
|
import { RagfairOfferHolder } from "@spt/utils/RagfairOfferHolder";
|
||||||
import { TimeUtil } from "@spt/utils/TimeUtil";
|
import { TimeUtil } from "@spt/utils/TimeUtil";
|
||||||
@ -31,11 +33,13 @@ export class RagfairOfferService
|
|||||||
@inject("DatabaseService") protected databaseService: DatabaseService,
|
@inject("DatabaseService") protected databaseService: DatabaseService,
|
||||||
@inject("SaveServer") protected saveServer: SaveServer,
|
@inject("SaveServer") protected saveServer: SaveServer,
|
||||||
@inject("RagfairServerHelper") protected ragfairServerHelper: RagfairServerHelper,
|
@inject("RagfairServerHelper") protected ragfairServerHelper: RagfairServerHelper,
|
||||||
|
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
||||||
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
||||||
@inject("EventOutputHolder") protected eventOutputHolder: EventOutputHolder,
|
@inject("EventOutputHolder") protected eventOutputHolder: EventOutputHolder,
|
||||||
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
|
@inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil,
|
||||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||||
|
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR);
|
this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR);
|
||||||
@ -276,7 +280,60 @@ export class RagfairOfferService
|
|||||||
this.ragfairOfferHandler.removeOffer(playerOffer);
|
this.ragfairOfferHandler.removeOffer(playerOffer);
|
||||||
|
|
||||||
// Send failed offer items to player in mail
|
// 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);
|
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