Buying from and selling to a trader
Buy and Sell do one whole trade for the player, without the trade screen. They use the game’s own money, stock and price rules, so price factors and the Shops.Bought, Shops.Sold and Shops.PriceQuery events all apply. A trade is all or nothing: if it can’t be done in full, nothing changes.
Traders buys and sells for the player
Use Buy and Sell to make one trade for the player.
public static class Traders
{
public static bool Buy(string modId, string traderId, int itemId, int count, out TraderTradeRefusal refusal);
public static bool Sell(string modId, string traderId, int itemId, int count, out TraderTradeRefusal refusal);
}
| Method | Returns | What it does |
|---|---|---|
Buy(modId, traderId, itemId, count, out refusal) | bool | The player buys count of an item. Returns true when done. |
Sell(modId, traderId, itemId, count, out refusal) | bool | The player sells count of an item. Returns true when done. |
TraderTradeRefusal why a trade was refused
Buy and Sell give you this through out refusal. It is None when the trade went through, and otherwise says why it did not, such as OutOfStock. The game has no such list; the framework sets these values, with Failed when nothing more specific applies.
public enum TraderTradeRefusal
{
None,
NotReady,
BadInput,
SaveBusy,
UnknownTrader,
Closed,
OutOfStock,
NoRoom,
PlayerCannotAfford,
TraderCannotAfford,
NotEnoughItems,
NotAccepted,
Failed,
SpecialTrader,
LiquidItem,
TradeBusy
}
| Value | Meaning |
|---|---|
None | The trade was done. |
NotReady | The game is not loaded far enough yet.Trade once a save has loaded, for example from Events.WhenReady. Nothing was bought or sold. |
BadInput | Something you passed is not allowed.Check the mod id, a count of at least 1, and an item id the game knows. Nothing was traded. |
SaveBusy | A save is being written or loaded.Try the trade again once the save finishes. Nothing was traded. |
UnknownTrader | No trader has that shop id. |
Closed | The trader is closed. |
OutOfStock | The trader does not have that many.Ask for fewer, or read the stock first. Nothing was bought. |
NoRoom | There is no room for the items.Buying needs room in the player inventory, selling needs room on the trader. Free some space and try again. Nothing was traded. |
PlayerCannotAfford | The player cannot pay.Ask for a quote before buying. No money moved. |
TraderCannotAfford | The trader cannot pay.Traders carry limited money. Sell fewer units, or sell to another trader. Nothing was sold. |
NotEnoughItems | The player does not hold that many.Some units cannot be sold, for example owned or witnessed stock, so count what the trader will take rather than what the player holds. |
NotAccepted | The trader does not deal in that item.Each trader takes only certain categories. Check what it buys before offering. Nothing was sold. |
Failed | Something went wrong part way through.The framework undoes the steps it had taken and logs the error once, so the player keeps their money and items. |
SpecialTrader | The trader gives and takes certificates. The framework does not trade with it. |
LiquidItem | The item is a liquid, or holds one. |
TradeBusy | Another trade is already running.Do not trade from inside a price handler or from another trade. Wait for the first one to finish. |
Things to know
BuyandSellreturntruewhen the trade was done. Otherwise they returnfalseandrefusalsays why. A refusal before anything changed shows no message in the game. The log gets one line for each kind of trade and reason.Buygives the player the items first, then takes the units from the trader’s stock, then takes the money. The game’s ownTrade.Buyonly moves the money, so the framework moves the items too.Buyonly takes ordinary stock: units nobody owns, not stolen, and priced by the game as a new item. A stolen unit, or one the game prices differently (such as a worn one), stays on the shelf and is not counted. (units with no owner and no stolen mark, which the game prices as a new item)- A bought unit keeps the shelf unit’s owner and item data, such as its skin.
Selltakescountof the item from the player’s stacks that the trader accepts, one stack at a time. It skips units that the trader doesn’t accept: a stolen unit the trader doesn’t buy, or a unit the trader’s allowed and forbidden categories rule out. Those units stay with the player and are not sold.- Each stack is priced on its own, so its wear, quality and stolen mark count. A sold unit keeps its owner and item data in the trader’s stock, and the trader keeps it unless the trader destroys what it buys. (each stack is priced with its own owner and meta; a stolen unit sold to the trader is never one stolen from that trader, and it keeps its owner so the game’s stolen price factor applies to the sale)
- A trade is not a crime. Taking a unit that someone owns, or selling stolen goods through
Sell, does not commit a theft, the same as in the game’s own trade screen. - The trade screen does not need to be open. A trade does not check whether the trade screen is open for that trader.
- Events are held back until the trade is done. Handlers for
Shops.Bought,Shops.Sold,PlayerInventory.ItemAdded,PlayerInventory.ItemRemovedandCash.MoneyChangedrun after the trade is complete, so they never run in the middle of one. - A buy raises one
Shops.Bought. A sale raises oneShops.Soldfor each stack. Item events come once per item with the total moved. A bought item’s “New item” notice shows once with the total. A refused or undone trade raises none of these. - If a trade fails halfway, it is undone, and you get the reason. Only the money the trade moved goes back, to the player and to the trader.
- The trader’s stock and the player’s inventory (the main, backpack and character slots) are written back slot by slot, into the same slots, with the same amount, owner, quality, liquid and other item data. (the undo writes slots back directly, so the game’s take and drop callbacks do not run again, and the undo does not take back the game’s own callbacks that ran during a step)
- A finished trade is saved at the next save, like a trade on the trade screen. That is the trader’s money and trade balance, the trader’s stock, and the player’s inventory and money. The framework keeps no record of your trades, so keep your own in your own save data if you need one.
- The game’s price rules are not changed. Item value, stolen goods and containers are priced as the game does. Price factors only scale the result.
Limits
OutOfStockhappens when there are fewer thancountunits thatBuycan take.- A bought unit’s cold (refrigerated) state is not kept.
Sellrefuses and changes nothing when the accepted units are fewer thancount. You getNotAcceptedwhen the trader’s categories ruled out a unit the player holds, otherwiseNotEnoughItems. It also refuses when the trader can’t pay for the whole sale, or has no room for the items.- Liquids can’t be traded here. An item that is a liquid, or holds one such as a filled bottle, is refused with
LiquidItem, because the liquid can change when stacks merge and an undo could not restore that. - Special traders can’t be traded with. A trader that gives and takes certificates is refused with
SpecialTrader. (these areTradeSpecial) - A trade can’t start while another trade is running, or from inside a
Shops.PriceQueryhandler. The call refuses withTradeBusy. (while a trade runs, aShops.PriceQueryhandler can still read and set the price; but these writes are refused:Cashwrites (Give,TrySpend, and the banking and mail writes that check them),PlayerInventory.GiveandTake, andTraders.SetMoneyandRestock; other framework writes, such as shop stock and equipment, are not blocked) - The undo has limits. If a sale of a worn item (a hat, a backpack, a held item) fails after the item is taken, the item is back in its slot, but the game may not treat it as worn until the player moves it.
- In rare slot cases a purchase can put one unit on the floor, and the call then refuses and undoes the rest, leaving that unit on the floor. The player never loses money either way.