Skip to content

Personal carts

A free cart is a wagon with two horses - or a two-wheeled cart with one - that stands where it was put until a player takes the reins at its bench. Then their game drives it - W trots, W with Shift held canters, S brakes and backs up, A / D steer - and everyone sees it move; a wall, a tree or another cart in the way stops the horses. Other players get on beside the driver or in the back with the game’s own prompt (“Get on”) and off with the game’s dismount key. The carts that drive a road by themselves are the other half of the carts: the routes guide.

The server does the driving, the seats and the looks; the game mode decides everything else - who may drive which cart, what it looks like, what it costs, and whether it is still there after the player’s next login.

A cart is of one of two kinds:

Kind What it is
wagon four wheels, two horses at the pole, a bench and a bed behind it - six seats
cart the game’s two-wheeled cart: one horse between its shafts, the driver on the bed’s front edge, a passenger on each side rail - three seats

An admin’s /cart (or /cart cart for a two-wheeler) puts a free cart 4.5 m ahead, across their way with the bench at their right hand; /cart remove takes away the free cart nearest them. A mode uses CreateCart - with the cart’s parts if it should not look like the default one - and DestroyEntity; GetEntityTemplate answers a cart’s kind:

-- a covered wagon full of sacks, 4 m ahead of the player, across their way
local x, y, z = GetPlayerPos(pid)
local r = math.rad(GetPlayerYaw(pid))
local cart = CreateCart("wagon", x - math.sin(r) * 4, y + math.cos(r) * 4, z, GetPlayerYaw(pid) + 90, nil,
{body = "b_covered", axle = "b_covered", load = "sacks"})
-- a peddler's two-wheeler with a market load behind a palomino
local peddler = CreateCart("cart", x + 6, y, z, 0, nil, {load = "market", horse1 = "palomino"})

How fast a player may drive is [carts] drive_speed (server settings), one cart’s own with SetCartSpeed.

Seat On a wagon On a two-wheeler
0 at the reins - the driver at the reins, on the bed’s front edge
1 the bench beside the driver the right side rail, behind the wheel
2, 3 the back of the bed, right and left 2: the left side rail
4, 5 the bed ahead of them, right and left -

The bench’s prompt gives the reins of a cart nobody drives, else the seat beside them (a side rail on a two-wheeler); the bed’s prompt a seat in the back on the player’s side. The mode sees and moves every seat: GetCartSeatPlayer says who sits where, SetPlayerCartSeat moves a seated player (to the reins too), SetCartSeatLocked keeps a seat from the prompt, and OnPlayerEnterCart may keep a player out or seat them elsewhere. The shipped modes give every player /seat (who sits where) and /seat <n> (move there).

A wagon is the game’s own parts, and the game builds every one of its wagons on the same chassis - so any body goes with any front axle, wheels and load. Each slot holds one part; a slot the mode never set shows the default (the first named below). A change shows on every screen at once, while the cart drives too. A wagon’s slots:

Slot Parts
body the game’s three wagons: a (the default), b (boards lying in its bed), b_plain (the same, its bed empty), c (long, with ladder sides); a_covered, b_covered, c_covered - under a canvas cover
axle b (the default), b_covered - the front axle with the pole
wheels b_covered (the default), b, polygonal (rough-hewn)
load none (the default), sacks, charcoal, carpets (cloth, rolls of fabric, crates), stones, hay_small, hay_medium, hay_large, goods (sacks, crates, baskets, a barrel), junk (hides, chains, shackles, rotten apples …), cabbage
horse1, horse2 the horses’ coats (left, right): black, bay_dun, chestnut, light_grey, buckskin, palomino, seal_brown, dark_grey, red_roan, brown - unset, each cart has a pair of its own

The two-wheeler is one body with its shafts, so it has fewer slots:

Slot Parts
body b - the only one
wheels b - the only one
load none (the default), goods (barrels, a basket, sacks), market (baskets and a barrel), sacks, cabbage (wicker sides full of cabbages)
horse1 the horse’s coat, the same names as a wagon’s

The functions: SetCartPart and SetCartParts change them, GetCartPart and GetCartParts read them - in any callback too - and GetCartPartSlots / GetCartPartNames list the catalogue of a kind for a shop’s menu (a slot the kind has not is refused). An admin’s /cart parts lists the nearest cart’s in the chat and /cart part <slot> <part> changes it.

A load is a look: the sacks and the hay take no room from the seats in the back. A mode that fills a cart with hay can keep the back seats from the prompt with SetCartSeatLocked.

A dealer sells carts, the buyer’s own from then on: nobody else takes its reins, anyone may ride along. The whole of it in Lua (the prices are the mode’s):

CART_OWNER = {} -- cart id -> the player who bought it
PLAYER_CART = {} -- player id -> their cart
local MODELS = {
peddler = {price = 150, kind = "cart", parts = {load = "market"}},
farmer = {price = 300, kind = "wagon", parts = {body = "c", wheels = "polygonal"}},
merchant = {price = 800, kind = "wagon", parts = {body = "b_covered", axle = "b_covered", wheels = "b"}},
}
-- /buycart peddler | farmer | merchant: the cart 4 m ahead, its reins kept for the buyer
function buyCart(pid, model)
local m = MODELS[model]
if not m then SendClientMessage(pid, COLOR_WHITE, "The dealer sells: peddler (150), farmer (300), merchant (800).") return end
if PLAYER_CART[pid] then SendClientMessage(pid, COLOR_RED, "You have a cart already.") return end
if GetPlayerMoney(pid) < m.price then SendClientMessage(pid, COLOR_RED, "That is " .. m.price .. " Groschen.") return end
local x, y, z = GetPlayerPos(pid)
local r = math.rad(GetPlayerYaw(pid))
local cart = CreateCart(m.kind, x - math.sin(r) * 4, y + math.cos(r) * 4, z, GetPlayerYaw(pid) + 90, nil, m.parts)
if not cart then return end
GivePlayerMoney(pid, -m.price)
SetCartSeatLocked(cart, 0) -- the bench's prompt offers a seat, not the reins ...
CART_OWNER[cart], PLAYER_CART[pid] = pid, cart
end
-- ... and the owner at the bench (at either side of a two-wheeler) is seated at the reins
function OnPlayerEnterCart(pid, id, seat)
local bench = seat == 1 or GetEntityTemplate(id) == "cart"
if CART_OWNER[id] == pid and bench and not GetCartSeatPlayer(id, 0) then return 0 end
return true
end

A cartwright does up the player’s own cart for a price: a covered body, other wheels, a load, other horses.

local WORK = {body = 150, axle = 40, wheels = 60, load = 20, horse1 = 100, horse2 = 100}
-- /cartwright <slot> <part>, next to your own cart
function cartwright(pid, slot, part)
local cart = PLAYER_CART[pid]
local cx, cy = GetEntityPos(cart or -1)
local px, py = GetPlayerPos(pid)
if not cx or (cx - px) ^ 2 + (cy - py) ^ 2 > 10 * 10 then SendClientMessage(pid, COLOR_RED, "Bring your cart here.") return end
local kind = GetEntityTemplate(cart)
local names = GetCartPartNames(slot, kind)
if not WORK[slot] or #names == 0 then SendClientMessage(pid, COLOR_WHITE, table.concat(GetCartPartSlots(kind), ", ")) return end
if not part then SendClientMessage(pid, COLOR_WHITE, table.concat(names, ", ")) return end
if GetCartPart(cart, slot) == part then return end
if GetPlayerMoney(pid) < WORK[slot] then SendClientMessage(pid, COLOR_RED, "That is " .. WORK[slot] .. " Groschen.") return end
if SetCartPart(cart, slot, part) then GivePlayerMoney(pid, -WORK[slot]) else SendClientMessage(pid, COLOR_RED, "No such " .. slot .. ".") end
end

The server forgets a cart when it stops, and a mode’s tables when it reloads - what a player owns is the mode’s to keep, in its database: the parts (GetCartParts(cart) is a plain table of slot names) and where the cart stood (GetEntityPos). At the player’s next login CreateCart with those parts brings it back; at their disconnect the mode saves it and takes it away (DestroyEntity) or leaves it standing - a cart left standing is anyone’s to drive unless its reins stay locked.