Picking pockets
Players can pick each other’s pockets with the game’s own “Rob”: the thief holds E at another player’s body, the game’s charge starts - its roll can still get them caught - and then its wheel, the tile game of single player, opens over a few things the other player carries. That is one try. What the wheel takes moves from the victim’s bag to the thief’s. The thief’s game plays it all on their screen; the server makes the offer and moves what is taken. NPC actors can have pockets too.
Pickpocketing is off until a mode switches it on, and the rest is the mode’s: who may rob and be robbed, how much is on the wheel, and what a caught thief or a robbed player gets. The names are in the Pickpocketing group of the server API.
1. Switching it on
Section titled “1. Switching it on”SetPickpocketing(enabled, rules) switches it on or off - off by default.
When it is on, the game’s Rob prompt shows on every player’s body to every other player, and on the NPC actors that have
pockets (section 7). Every key of rules is optional; these are the defaults, but for deny:
function OnGameModeInit() SetPickpocketing(true, { range = 3, -- metres from the body (0.5 to 6) cooldown = 30, -- seconds before the same thief may rob the same player again (3600 at most, 0 = none) max_items = 8, -- stacks on the wheel, the purse counted (11 at most) max_stack = 5, -- the most of one kind of item on the wheel behind_only = false, -- true: only from within 70 degrees of straight behind min_seconds = 2.5, -- the shortest try that may take anything (60 at most) money = false, -- true: the victim's purse is one more entry on the wheel ... money_share = 0.1, -- ... holding this share of it (0 to 1) ... money_cap = 100, -- ... up to this many Groschen (6553.5 at most) actors = true, -- NPC actors with pockets can be robbed deny = {"Food", "dagger"}, -- item classes, names or categories never offered (none by default) })endA number that cannot be used, or a key left out, is the default; a call without rules keeps the earlier ones.
GetPickpocketing reads the switch and the rules back, and switching it off ends
the tries that run. range is what the server allows - the game’s own prompt reaches about 2.4 metres. A take that comes sooner
than min_seconds is refused and the try ends, so raise that one with care.
2. Who may rob and be robbed
Section titled “2. Who may rob and be robbed”While it is on, everyone may rob everyone.
SetPlayerPickpocketable(pid, false) takes a player’s pockets out of it:
the prompt does not show on their body, every ask is refused and a try on them ends.
SetPlayerCanPickpocket(pid, false) takes the right to rob from a player.
Both last the player’s session, through respawns, until the mode is reloaded - so set them where the mode knows a player’s role,
usually in OnPlayerSpawn:
-- admins and guards (team 1) cannot be robbed; only the thieves' guild (team 2) robsfunction OnPlayerSpawn(pid) SetPlayerPickpocketable(pid, not (IsPlayerAdmin(pid) or GetPlayerTeam(pid) == 1)) SetPlayerCanPickpocket(pid, GetPlayerTeam(pid) == 2)endIsPlayerPickpocketable and
CanPlayerPickpocket read them back. The server does not look at parties or
teams: members of one can rob each other unless the mode says no. A player who is dead, knocked out or carried cannot be robbed
(knocking a player out and carrying them).
3. What the server checks
Section titled “3. What the server checks”The thief’s game asks, and nothing plays until the server grants the try. A refusal reaches the thief alone, with a line in their chat saying why (each kind of line at most once in fifteen seconds). The server checks that:
- pickpocketing is on, the thief may rob, and both stand in the world, in one virtual world, with the victim’s body in the thief’s view;
- neither is busy: on a horse, in a cart, flying, in a dice match, at a shop, in a dialogue, searching a body, knocked out, carried or carrying someone, in a fall or a pull-down, or in a fight;
- the victim’s pockets may be picked, or the NPC actor has pockets and
actorsallows it; - the thief stands within
range, behind the victim whenbehind_onlyis set, and - with the level’s collision geometry and[validation] line_of_sighton - with no wall or closed door between them; - nobody else is robbing the victim, and the thief did not rob them in the last
cooldownseconds, counted from the start of that try whatever it ended in; - there is something to take: a victim who carries only what they wear has nothing on the wheel.
Then the mode has the last word. OnPlayerPickpocketStart(pid, victim, actor)
is called only when all of that has passed, and false refuses (a refusal starts no cooldown) - the place for a rule of the
mode’s own, such as a safe zone or a guard in sight. victim is the robbed player’s id and actor is -1, or the other way
round for an NPC actor.
4. What is on the wheel
Section titled “4. What is on the wheel”The server makes the offer when it grants the try, and the thief’s game plays over exactly that. For a player it is what they
carry - never what they wear or hold in a hand: one stack for each kind of item, at most max_stack of it, shuffled, and at
most max_items stacks. With money the victim’s purse is one more entry in the shuffle, so a full bag can leave it off the
wheel (deny does not reach it). The game’s quick-slot containers, item aliases, NPC tools and key rings are never in a pocket,
and deny keeps out whatever else you name: an item class, an item name or a category of
the item catalogue.
The mode cannot add to the wheel, and the odds of being caught are the game’s own roll. It can refuse the try, keep things off
with deny, or veto a stack afterwards.
5. What the thief takes
Section titled “5. What the thief takes”When the wheel ends, the thief’s game says which of the offered stacks it took. The server refuses a claim that names a stack the
offer did not hold, one twice, more than was offered, one sooner than min_seconds after the grant, or one after ninety seconds:
nothing moves and the try ends as "cancelled". Otherwise each stack goes to the mode first -
OnPlayerPickpocket(pid, victim, actor, class, amount) - and false leaves
that stack where it is:
-- outside the thieves' guild (team 2) a thief takes coin onlyfunction OnPlayerPickpocket(pid, victim, actor, class, amount) local info = GetItemInfo(class) if GetPlayerTeam(pid) ~= 2 and not (info and info.category == "Money") then return false endendamount is what would move: it is already cut to what the victim still holds, since they may have eaten, sold or put on
something since the offer. The purse is the Groschen class, and its amount is in tenths of a
Groschen - 25 is 2.5 - while money_cap and the money functions count Groschen. A veto says nothing by itself, so tell the
thief why. What is left moves: the victim’s game gives up those items and the thief gets exactly what it gave up. It is the
server’s own move, so the inventory audit explains both sides and nobody is flagged.
6. How a try ends
Section titled “6. How a try ends”OnPlayerPickpocketEnd(pid, victim, actor, outcome, reason, taken) hears
every ending. outcome is "took" (taken stacks moved), "nothing" (the wheel ended with nothing taken, or everything was
vetoed or gone), "caught" or "cancelled" (the try was broken off). A caught thief is one the game’s own roll caught in the
charge, or who failed the tile game: nothing moves, and what they face is the mode’s to decide.
reason says why. For the first three it is the game’s own. A caught thief usually says "detected" (the victim noticed) or
"seen" (someone saw); "lost" is the thief walking off or looking away, the game’s own break without a crime - so check the
reason before you punish. For "cancelled" it is "broken off" (the thief’s game), one of the server’s words - a death, a
departure, a teleport, a mount, a cart, flying, a knock-out, being carried or "carrying" someone, a fight, ninety seconds, a
refused claim; the callback’s page lists them - or what the
mode gave CancelPickpocket(pid, reason), "mode" when nothing. That call ends
the try a player is in, the thief or the one robbed, and the thief’s game ends its minigame at once.
GetPickpocketTarget(pid) answers whom a thief robs now: "player" or
"actor", and the id.
The server sends the victim no message. Telling them, fining the thief, calling the guards, or a fight with
StartFight (it needs [combat] pvp) are all the mode’s. A player in a fight can
neither rob nor be robbed, and a fight that begins ends their try:
function OnPlayerPickpocketEnd(pid, victim, actor, outcome, reason, taken) if victim < 0 then return end -- an NPC actor's pockets: nobody to tell if outcome == "took" then SendClientMessage(victim, COLOR_SERVER, "Your pockets feel lighter.") elseif outcome == "caught" and (reason == "detected" or reason == "seen") then local fine = math.min(20, GetPlayerMoney(pid)) -- the thief pays the victim, up to 20 Groschen if fine > 0 then GivePlayerMoney(pid, -fine) GivePlayerMoney(victim, fine) SendClientMessage(victim, COLOR_SERVER, GetPlayerName(pid) .. " was caught and paid you " .. fine .. " Groschen.") end endend7. NPC actors’ pockets
Section titled “7. NPC actors’ pockets”SetActorPocket(actorId, items, money) gives an NPC actor pockets: a list of
{class = ..., amount = ..., health = ...} (health 0 to 100, 100 when left out) and a purse in Groschen:
local merchant = CreateActor(nil, x, y, z, 180, "Merchant") -- x, y, z: where it standsSetActorPocket(merchant, { {class = "bread", amount = 3}, {class = "apple", amount = 5, health = 80},}, 12.5) -- and 12.5 Groschen in its purseThe prompt shows on an actor with pockets while pickpocketing is on. The offer is made from the list with the same max_items,
max_stack and deny, and the purse is offered whole, up to money_cap, whatever money says. With actors at false every
ask for an actor is refused. A take shrinks the pockets and the mode refills them:
GetActorPocket reads what is left, and in OnPlayerPickpocketEnd the actor comes
as actor, with victim at -1. An empty list is empty pockets (the thief is told there is nothing to take); nil takes them
away.
The example modes
Section titled “The example modes”freeroam.lua and basicrp.lua keep six of the rules above (range, cooldown, max_items, max_stack, behind_only,
money) in a table (POCKET in Freeroam, PICKPOCKET in BasicRP, both with enabled = false) and switch pickpocketing on
from it; both answer an admin’s /pickpocket on|off|status. There is no command to rob - the game’s prompt is the way.
Freeroam has nothing for a robbed player or a caught thief. BasicRP allows only robbing from behind (behind_only = true)
and adds admins_robbable, on_robbed ("none" by default, "notify" tells a robbed player) and on_caught (for a thief
the victim noticed: "notify", the default, tells both, "fight" starts a fight too, "none" says nothing); its
/pickpocket admins on|off lets admins be robbed. BasicRP ships with every player an admin (EVERYONE_ADMIN), so out of the
box nobody can be robbed there: turn that off, set admins_robbable, or use /pickpocket admins on. duel_arena.lua never
switches pickpocketing on.
What to know
Section titled “What to know”- A player’s game must be able to take part. A game from an older or unsupported build cannot rob: the server grants it nothing and says so in its chat. Women rob and are robbed like men.
- Export the game’s tables first. Without the game’s tables the server cannot tell a quick-slot container
or a key ring from any other item, and only class GUIDs work in
denyandSetActorPocket. - The prompt’s state keys are the server’s. The world state
pocket, a player’spocketandstealand an actor’spocketshow and hide it; do not set them withSetGlobalState,SetPlayerStateorSetEntityState. - A mode reload switches it off again. Pickpocketing goes back to off with the default rules, everyone can rob and be robbed,
the actors’ pockets are gone and a try that runs ends without a word to the mode. The new mode’s
OnGameModeInitsets what it wants, and it hearsOnPlayerConnectand, for players in the world,OnPlayerSpawnagain, so rights set there are set again. - Nothing is saved. Tries, rights, cooldowns and pockets are gone when the server stops.
