Traders are how scavenged junk turns into supplies. Each one is an NPC with limited stock, prices that rise as the shelf empties, and a list of things they'll buy. You can add a trader in two ways:
- In config, in
en-shops/config/shared.lua. Good for the permanent traders your server is built around. - In game, from the Build tab of the admin panel. Good for placing and adjusting traders live, without a restart.
Option 1: add a trader in config
Open resources/[encore]/en-shops/config/shared.lua and add an entry to Config.traders:
Config.traders[#Config.traders + 1] = {
id = 'vinewood_fence',
label = 'Vinewood Fence',
name = 'Marta',
greeting = { 'Quick. What have you got?', 'No questions, no refunds.' },
ped = {
model = 'a_f_y_hipster_02',
coords = vec4(300.2, 180.4, 104.1, 70.0),
scenario = 'WORLD_HUMAN_SMOKING',
},
blip = { sprite = 52, colour = 47 },
sells = {
{ item = 'lockpick', max = 10 },
{ item = 'ammo_rifle', max = 120, barter = { { item = 'scrapmetal', count = 2 } }, requires = { level = 6 } },
{ item = 'gps', max = 2, barterOnly = true, barter = { { item = 'radio', count = 1 }, { item = 'duct_tape', count = 2 } } },
},
buys = { categories = { 'weapon', 'tool' }, items = { 'radio' }, rate = 0.5 },
}Use /coords from en-admin to copy your current position as a vec4 while standing where the trader should stand.
Trader fields
| Field | What it does |
|---|---|
id | Unique id. A trader placed in game with the same id replaces this one. |
label | Stall name shown on the trade page and blip |
name | The trader's name |
greeting | A line, or a list of lines picked at random when players talk to them |
ped.model | Ped model |
ped.coords | vec4 position and heading |
ped.scenario | Optional idle scenario |
blip | { sprite, colour }, or false for no map blip |
sells | Offers, see below |
buys | What they accept, see below |
What a trader sells
Each entry in sells:
| Field | What it does |
|---|---|
item | Item name |
max | Shelf capacity. Stock starts full. |
price | Optional fixed base price. Defaults to the item's value in Config.values. |
barter | Items taken per unit bought, as { { item, count }, ... } |
barterOnly | true means no cash option. Needs barter. |
requires | { level = n } for character level, or { skill = 'crafting', level = n } for a skill level |
What a trader buys
| Field | What it does |
|---|---|
categories | Item categories they accept, such as food, material, weapon |
items | Specific item names they accept |
rate | Share of an item's value they pay, overriding Config.pricing.sellRate. Clamped 0 to 2. |
Give every item a value
Config.values in the same file is the base cash value of each item. It is the default buy price, and it is what traders pay a share of when buying.
Config.values = {
-- ...
lockpick = 35,
radio = 150,
geiger_counter = 300,
}An item with no value can't be sold to anyone, even if a trader's buys list includes it.
Restart en-shops (or the server) to load the new trader.
Option 2: place a trader in game
- Press F10 to open the admin panel and go to the Build tab.
- Choose Traders. The list shows config traders and placed ones, with distance and a button to teleport to each.
- Choose New. A form opens with defaults filled in.
- Fill in Shop name and Trader name, and add greetings, one per line.
- Enter a Ped model and, optionally, a Scenario.
- Stand where the trader should be and press Use my position on the Position field. It records your heading too, and places the ped at your feet.
- Add Sells offers: item, max stock, optional price, barter cost, barter only, required level or skill.
- Fill in Buys categories, Buys items and Buy rate.
- Save. en-shops validates the data, and the trader appears for everyone straight away.
To change a config trader live, open it from the list, edit and save. The placed version overrides the config one with the same id. Delete the placed entry to go back to the config version.
Placed traders are stored in the encore_content table and survive restarts. See en-admin and Content registry.
How prices work
Prices are decided on the server, from stock and holdings.
Buying from a trader. The price rises as the shelf empties, by up to Config.pricing.scarcity (50%):
price = max(1, round(base * (1 + (1 - stock / max) * scarcity)))A lockpick worth 35 with 5 of 10 left costs round(35 * 1.25) = 44.
Selling to a trader. They pay sellRate (45%) of the value, less the more of that item they already hold, down by up to maxDrop (60%) at saturation (25) units:
price = floor(value * rate * (1 - min(maxDrop, holdings / saturation * maxDrop)))A radio worth 150 sells for 67 to a trader holding none, and for 51 once they hold 10.
Restocking. Every Config.restock.minutes (30), each shelf refills by 25% of its max, and 30% of holdings are cleared. Selling an item the trader also sells puts it back on their shelf.
| Key | Default | What it does |
|---|---|---|
Config.pricing.scarcity | 0.5 | Empty shelf charges up to +50% |
Config.pricing.sellRate | 0.45 | Share of value paid when buying from players |
Config.pricing.saturation | 25 | Holdings at which the price drop is at its largest |
Config.pricing.maxDrop | 0.6 | Largest reduction of the sell price |
Config.pricing.maxBatch | 50 | Most units per trade |
Config.restock.minutes | 30 | Minutes between restocks |
Config.restock.refill | 0.25 | Share of max refilled per restock |
Config.restock.clearance | 0.3 | Share of holdings cleared per restock |
Admins can refill every shelf and clear all holdings with /restockshops.
Scripting around traders
en-shops fires server events you can hook into, and exports current prices.
-- server: a small XP reward for bartering instead of paying cash
AddEventHandler('en-shops:server:bought', function(source, traderId, item, amount, method)
if method == 'barter' then
exports['en-core']:AddXP(source, 2 * amount, 'Bartered with a trader')
end
end)
-- server: log big sales
AddEventHandler('en-shops:server:sold', function(source, traderId, item, amount, totalCash)
if totalCash >= 1000 then
exports['en-core']:Log('default', ('%s sold %dx %s to %s for $%d'):format(
exports['en-core']:GetCharacterName(source), amount, item, traderId, totalCash))
end
end)
-- server: read a live price
local price = exports['en-shops']:GetBuyPrice('vinewood_fence', 'lockpick')Related
- en-shops: full reference.
- Add an item: create something for traders to sell.
- Building a zombie server: how traders fit the economy.