Fix ragfair offers not saving upd properties

Create replacement for `addItem()`, only used for ragfair purchases currently - goal is to fully remove `addItem()`

Dont show warning when generating armor without a default plate on fence
This commit is contained in:
Dev 2024-01-14 10:09:43 +00:00
parent 743f222141
commit 8b03faca89
6 changed files with 232 additions and 15 deletions

View File

@ -115,7 +115,7 @@ export class TradeController
type: "buy_from_trader",
tid: (sellerIsTrader) ? fleaOffer.user.id : "ragfair",
// eslint-disable-next-line @typescript-eslint/naming-convention
item_id: fleaOffer.root,
item_id: fleaOffer._id, // Store ragfair offerId in buyRequestData.item_id
count: offer.count,
// eslint-disable-next-line @typescript-eslint/naming-convention
scheme_id: 0,

View File

@ -10,6 +10,7 @@ import { TraderAssortHelper } from "@spt-aki/helpers/TraderAssortHelper";
import { IPmcData } from "@spt-aki/models/eft/common/IPmcData";
import { Inventory } from "@spt-aki/models/eft/common/tables/IBotBase";
import { Item, Location, Upd } from "@spt-aki/models/eft/common/tables/IItem";
import { IAddItemDirectRequest } from "@spt-aki/models/eft/inventory/IAddItemDirectRequest";
import { AddItem, IAddItemRequestData } from "@spt-aki/models/eft/inventory/IAddItemRequestData";
import { IAddItemTempObject } from "@spt-aki/models/eft/inventory/IAddItemTempObject";
import { IInventoryMergeRequestData } from "@spt-aki/models/eft/inventory/IInventoryMergeRequestData";
@ -67,6 +68,90 @@ export class InventoryHelper
}
/**
* Add whatever is passed in `request.itemWithModsToAdd` into player inventory (if it fits)
* @param sessionId Session id
* @param request addItemDirect request
* @param pmcData Player profile
* @param output Client response object
* @returns IItemEventRouterResponse
*/
public addItemToInventory(sessionId: string, request: IAddItemDirectRequest, pmcData: IPmcData, output: IItemEventRouterResponse): IItemEventRouterResponse
{
const itemWithModsToAddClone = this.jsonUtil.clone(request.itemWithModsToAdd);
// get stash layouts ready for use
const stashFS2D = this.getStashSlotMap(pmcData, sessionId);
const sortingTableFS2D = this.getSortingTableSlotMap(pmcData);
// Find an empty slot in stash for item being added - adds 'location' property to root item
const errorOutput = this.placeItemInInventory(
stashFS2D,
sortingTableFS2D,
itemWithModsToAddClone,
pmcData.Inventory,
request.useSortingTable,
output,
);
if (errorOutput)
{
// Failed to place, error out
return errorOutput;
}
// Apply/remove FiR to item + mods
for (const item of itemWithModsToAddClone)
{
if (!item.upd)
{
item.upd = {};
}
if (request.foundInRaid)
{
item.upd.SpawnedInSession = request.foundInRaid;
}
else
{
if (delete item.upd.SpawnedInSession)
{
delete item.upd.SpawnedInSession;
}
}
}
// Remove trader properties from root item
this.removeTraderRagfairRelatedUpdProperties(itemWithModsToAddClone[0].upd);
// Run callback
try
{
if (typeof request.callback === "function")
{
request.callback();
}
}
catch (err)
{
// Callback failed
const message = typeof err === "string"
? err
: this.localisationService.getText("http-unknown_error");
return this.httpResponse.appendErrorToOutput(output, message);
}
// Add item + mods to output + profile inventory
output.profileChanges[sessionId].items.new.push(...itemWithModsToAddClone);
pmcData.Inventory.items.push(...itemWithModsToAddClone);
this.logger.debug(`Added item ${itemWithModsToAddClone[0]._tpl} with ${itemWithModsToAddClone.length - 1} mods to inventory`);
return output;
}
/**
* @deprecated - use addItemDirect()
*
* BUG: Passing the same item multiple times with a count of 1 will cause multiples of that item to be added (e.g. x3 separate objects of tar cola with count of 1 = 9 tarcolas being added to inventory)
* @param pmcData Profile to add items to
* @param request request data to add items
@ -175,7 +260,7 @@ export class InventoryHelper
{
// Update Items `location` properties
const itemWithChildren = this.itemHelper.findAndReturnChildrenAsItems(itemsToAddPool, itemToAdd.itemRef._id);
const errorOutput = this.placeItemInInventory(
const errorOutput = this.placeItemInInventoryLegacy(
itemToAdd,
stashFS2D,
sortingTableFS2D,
@ -252,7 +337,7 @@ export class InventoryHelper
}
// Remove invalid properties prior to adding to inventory
this.removeTraderRelatedUpdProperties(rootItemUpd);
this.removeTraderRagfairRelatedUpdProperties(rootItemUpd);
// Add root item to client return object
output.profileChanges[sessionID].items.new.push({
@ -381,10 +466,10 @@ export class InventoryHelper
}
/**
* Remove properties from a Upd object used by a trader
* Remove properties from a Upd object used by a trader/ragfair
* @param upd Object to update
*/
protected removeTraderRelatedUpdProperties(upd: Upd): void
protected removeTraderRagfairRelatedUpdProperties(upd: Upd): void
{
if (upd.UnlimitedCount !== undefined)
{
@ -402,6 +487,108 @@ export class InventoryHelper
}
}
protected placeItemInInventory(
stashFS2D: number[][],
sortingTableFS2D: number[][],
itemWithChildren: Item[],
playerInventory: Inventory,
useSortingTable: boolean,
output: IItemEventRouterResponse): IItemEventRouterResponse
{
const itemSize = this.getItemSize(itemWithChildren[0]._tpl, itemWithChildren[0]._id, itemWithChildren);
const findSlotResult = this.containerHelper.findSlotForItem(stashFS2D, itemSize[0], itemSize[1]);
if (findSlotResult.success)
{
/* Fill in the StashFS_2D with an imaginary item, to simulate it already being added
* so the next item to search for a free slot won't find the same one */
const itemSizeX = findSlotResult.rotation ? itemSize[1] : itemSize[0];
const itemSizeY = findSlotResult.rotation ? itemSize[0] : itemSize[1];
try
{
stashFS2D = this.containerHelper.fillContainerMapWithItem(
stashFS2D,
findSlotResult.x,
findSlotResult.y,
itemSizeX,
itemSizeY,
false,
); // TODO: rotation not passed in, bad?
}
catch (err)
{
const errorText = typeof err === "string" ? ` -> ${err}` : "";
this.logger.error(this.localisationService.getText("inventory-fill_container_failed", errorText));
return this.httpResponse.appendErrorToOutput(
output,
this.localisationService.getText("inventory-no_stash_space"),
);
}
// Store details for object, incuding container item will be placed in
itemWithChildren[0].parentId = playerInventory.stash;
itemWithChildren[0].location = {
x: findSlotResult.x,
y: findSlotResult.y,
r: findSlotResult.rotation ? 1 : 0,
rotation: findSlotResult.rotation,
};
// Success! exit
return;
}
// Space not found in main stash, use sorting table
if (useSortingTable)
{
const findSortingSlotResult = this.containerHelper.findSlotForItem(
sortingTableFS2D,
itemSize[0],
itemSize[1],
);
const itemSizeX = findSortingSlotResult.rotation ? itemSize[1] : itemSize[0];
const itemSizeY = findSortingSlotResult.rotation ? itemSize[0] : itemSize[1];
try
{
sortingTableFS2D = this.containerHelper.fillContainerMapWithItem(
sortingTableFS2D,
findSortingSlotResult.x,
findSortingSlotResult.y,
itemSizeX,
itemSizeY,
false,
); // TODO: rotation not passed in, bad?
}
catch (err)
{
const errorText = typeof err === "string" ? ` -> ${err}` : "";
this.logger.error(this.localisationService.getText("inventory-fill_container_failed", errorText));
return this.httpResponse.appendErrorToOutput(
output,
this.localisationService.getText("inventory-no_stash_space"),
);
}
// Store details for object, incuding container item will be placed in
itemWithChildren[0].parentId = playerInventory.sortingTable;
itemWithChildren[0].location = {
x: findSortingSlotResult.x,
y: findSortingSlotResult.y,
r: findSortingSlotResult.rotation ? 1 : 0,
rotation: findSortingSlotResult.rotation,
};
}
else
{
return this.httpResponse.appendErrorToOutput(
output,
this.localisationService.getText("inventory-no_stash_space"),
);
}
}
/**
* Take the given item, find a free slot in passed in inventory and place it there
* If no space in inventory, place in sorting table
@ -414,7 +601,7 @@ export class InventoryHelper
* @param output Client output object
* @returns Client error output if placing item failed
*/
protected placeItemInInventory(
protected placeItemInInventoryLegacy(
itemToAdd: IAddItemTempObject,
stashFS2D: number[][],
sortingTableFS2D: number[][],
@ -796,10 +983,10 @@ export class InventoryHelper
* inputs Item template ID, Item Id, InventoryItem (item from inventory having _id and _tpl)
* outputs [width, height]
*/
public getItemSize(itemTpl: string, itemID: string, inventoryItem: Item[]): number[]
public getItemSize(itemTpl: string, itemID: string, inventoryItems: Item[]): number[]
{
// -> Prepares item Width and height returns [sizeX, sizeY]
return this.getSizeByInventoryItemHash(itemTpl, itemID, this.getInventoryItemHash(inventoryItem));
return this.getSizeByInventoryItemHash(itemTpl, itemID, this.getInventoryItemHash(inventoryItems));
}
// note from 2027: there IS a thing i didn't explore and that is Merges With Children

View File

@ -5,6 +5,7 @@ import { ItemHelper } from "@spt-aki/helpers/ItemHelper";
import { TraderHelper } from "@spt-aki/helpers/TraderHelper";
import { IPmcData } from "@spt-aki/models/eft/common/IPmcData";
import { Item, Upd } from "@spt-aki/models/eft/common/tables/IItem";
import { IAddItemDirectRequest } from "@spt-aki/models/eft/inventory/IAddItemDirectRequest";
import { IItemEventRouterResponse } from "@spt-aki/models/eft/itemEvent/IItemEventRouterResponse";
import { IProcessBuyTradeRequestData } from "@spt-aki/models/eft/trade/IProcessBuyTradeRequestData";
import { IProcessSellTradeRequestData } from "@spt-aki/models/eft/trade/IProcessSellTradeRequestData";
@ -18,6 +19,7 @@ import { RagfairServer } from "@spt-aki/servers/RagfairServer";
import { FenceService } from "@spt-aki/services/FenceService";
import { PaymentService } from "@spt-aki/services/PaymentService";
import { HttpResponseUtil } from "@spt-aki/utils/HttpResponseUtil";
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
@injectable()
export class TradeHelper
@ -26,6 +28,7 @@ export class TradeHelper
constructor(
@inject("WinstonLogger") protected logger: ILogger,
@inject("JsonUtil") protected jsonUtil: JsonUtil,
@inject("EventOutputHolder") protected eventOutputHolder: EventOutputHolder,
@inject("TraderHelper") protected traderHelper: TraderHelper,
@inject("ItemHelper") protected itemHelper: ItemHelper,
@ -70,12 +73,14 @@ export class TradeHelper
const callback = () =>
{
// Update assort/flea item values
let itemPurchased: Item;
const isRagfair = buyRequestData.tid.toLocaleLowerCase() === "ragfair";
if (isRagfair)
{
const allOffers = this.ragfairServer.getOffers();
const offersWithItem = allOffers.find((x) => x.items[0]._id === buyRequestData.item_id);
// We store ragfair offerid in buyRequestData.item_id
const offersWithItem = allOffers.find((x) => x._id === buyRequestData.item_id);
itemPurchased = offersWithItem.items[0];
}
else
@ -119,10 +124,26 @@ export class TradeHelper
// Increment non-fence trader item buy count
this.incrementAssortBuyCount(itemPurchased, buyRequestData.count);
}
this.logger.debug(`Bought item: ${buyRequestData.item_id} from: ${Traders[buyRequestData.tid]}`);
};
if (buyRequestData.tid.toLocaleLowerCase() === "ragfair")
{
const allOffers = this.ragfairServer.getOffers();
const offersWithItem = allOffers.find((x) => x._id === buyRequestData.item_id);
const request: IAddItemDirectRequest = {
itemWithModsToAdd: offersWithItem.items,
foundInRaid: true,
callback: callback,
useSortingTable: true
}
return this.inventoryHelper.addItemToInventory(sessionID, request, pmcData, output);
}
// TODO - handle traders
// TODO - handle fence
return this.inventoryHelper.addItem(pmcData, newReq, output, sessionID, callback, foundInRaid, upd);
}

View File

@ -0,0 +1,10 @@
import { Item } from "../common/tables/IItem"
export interface IAddItemDirectRequest
{
/** Item and child mods to add to player inventory */
itemWithModsToAdd: Item[];
foundInRaid: boolean;
callback: () => void;
useSortingTable: boolean;
}

View File

@ -565,8 +565,7 @@ export class FenceService
const plateTpl = plateSlot._props.filters[0].Plate
if (!plateTpl)
{
this.logger.warning(`Fence generation: item: ${itemDbDetails._id} ${itemDbDetails._name} lacks a default plate for slot: ${plateSlot._name}, skipping`);
// Bsg data lacks a default plate, skip adding mod
continue;
}
const modItemDbDetails = this.itemHelper.getItem(plateTpl)[1];

View File

@ -61,10 +61,10 @@ export class RagfairOfferHolder
{
const trader = offer.user.id;
const offerId = offer._id;
const template = offer.items[0]._tpl;
const itemTpl = offer.items[0]._tpl;
this.offersById.set(offerId, offer);
this.addOfferByTrader(trader, offer);
this.addOfferByTemplates(template, offer);
this.addOfferByTemplates(itemTpl, offer);
}
public removeOffer(offer: IRagfairOffer): void