CODE3RESPONSE
Callouts SDK
A callout is one Lua file in c3r/callouts/. It says what the caller reported, where it can happen, and what really happened: a few hidden stories, one picked at random each time. Copy the example, change it, restart c3r, and it's in rotation.
How a callout works
- The report is what dispatch reads out. Every story shares the same reports, so the report never gives the story away.
- Locations are where it can happen: a street name, a scene point, and the exact spot for each person, car and prop.
- Stories are what really happened. Each story's
setupputs people, cars and evidence on scene, writes the objectives, and says what happens when officers do things. - People are real. Everyone you add gets a hidden truth like any driver: name checks find them, searches find their items, and they answer the usual questions plus their own.
- Several units at once. Objectives are split between the units on scene, so a team works it faster.
Your first callout
1. Copy the example
c3r/callouts/_example.lua is a complete, fully commented callout (a suspicious person by parked cars, with two stories). Copy it to a new file, e.g. callouts/loitering.lua, and change its id and name.
2. Write the reports
Callout({
id = 'loitering',
name = 'Suspicious person',
priority = 3, -- 1 most urgent
enabled = true,
reports = {
'Caller reports someone hanging around the parked cars, looking in the windows.',
'Suspicious male by the parked cars. Caller is watching from across the road.',
},
3. Name the points
points lists every spot a location needs, in the order /c3rplace walks you through them. A third value of 'car' means "sit in a car parked where it should be".
points = {
{ 'caller', 'The person who rang it in, waiting nearby on their phone.' },
{ 'subject', 'The person they called about.' },
{ 'parked_car', 'The car he was at. Sit in a car parked where it should be.', 'car' },
},
locations = {},
4. Place it in game
Stand at the scene and run /c3rplace loitering. It walks you through each point: go to the spot, face the way they should face, press E. Backspace goes back a point, X cancels. When you're done, the location is written into your file's locations and used straight away.
Run it again for more locations. /c3rplace loitering 2 replaces location 2. You need the command.c3rconfig ace.
To place points by hand instead, /c3rpos caller copies caller = vector4(x, y, z, heading), to your clipboard.
5. Write the stories
stories = {
thief = {
weight = 2, -- twice as likely as a weight-1 story
setup = function(c)
c:person('caller', { at = 'caller', label = 'Caller', scene = 'WORLD_HUMAN_STAND_MOBILE',
questions = { { id = 'c_what', label = 'What did you see?' } },
lines = { c_what = { { 'He went car to car, pulling the handles.', set = { c_saw = 'doors' } } } } })
c:person('subject', { at = 'subject', label = 'Person', secret = 'warrant', demeanor = 'nervous',
items = { 'Slim jim' } })
c:objective('caller', 'Talk to the caller', { blip = 'caller' })
c:objective('find', 'Find the person they called about', { blip = 'subject' })
c:objective('deal', 'Deal with them', { after = { 'find' }, who = 'all' })
c:on('act', function(who, action, arg)
if who == 'caller' and action == 'ask' and arg == 'c_what' then c:done('caller') end
end)
c:on('contact', function(who) if who == 'subject' then c:done('find') end end)
c:on('custody', function() c:finish('Suspect arrested for vehicle tampering.') end)
end,
},
lockout = { weight = 1, setup = function(c) --[[ he locked himself out ]] end },
},
})
6. Test it
Restart c3r, go on duty, and run /requestcall loitering. It puts your call out near you now.
Rules that keep a callout good
- Same reports, same starting objectives, for every story. Story-specific objectives start hidden and are revealed by something found. Otherwise players learn to read the card instead of doing police work.
- Start with objectives that can be done at once, so a second unit has real work.
- Every objective needs something in
c:on(...)that marks it done. - An error in your file cancels that one call and prints why in the server console. Nothing else breaks.
- Check scenario, animation and prop names against the game files. A wrong one just does nothing.
Reference
Callout fields
| Field | Type | What it is |
|---|---|---|
id | string | Unique id |
name | string | Shown on the offer card |
priority | number | 1 is the most urgent |
enabled | boolean | false keeps it as a template only |
reports | string[] | What the caller said; one is picked |
points | { name, description, 'car'? }[] | The spots each location needs |
locations | { street, at = vector3, points = { [name] = vector4 } }[] | Where it can happen |
stories | { [key] = { weight, setup = function(c) } } | What really happened |
People, cars and evidence
c:person(key, opts) Someone in the story, made when the first unit gets close.
| Option | Type | What it does |
|---|---|---|
at | string | vector3 | vector4 | A point name, or a position |
label | string | What the officer sees before they know a name |
model, male | string, boolean | Ped model, or just the sex |
secret | string | Their hidden truth: 'clean', 'nervous', 'drunk', 'drugs', 'warrant'... |
demeanor | string | 'polite', 'nervous', 'rude' or 'chatty' |
flags, warrant | string[], string | What their record shows |
items | string[] | What a search finds that matters (shown as evidence) |
pockets | string[] | Everyday things a search finds that aren't evidence |
age | { min, max } | How old their ID says they are |
home | true | string | They live at the call's address, or on the street of a point |
injured | boolean | On the ground hurt: E is first aid, paramedics take them |
scene | string | { dict, name, flag } | What they're doing: a scenario or an animation |
questions | { id, label, needs?, hide?, press?, stress? }[] | Their own questions under The call. Ids start c_ |
lines | { [question id] = lines } | Their answers. A line can give a fact away: { 'He went north.', set = { c_dir = 'north' } } |
lines can also replace their answer to any normal question (e.g. lines.where), and a line can be a function that returns the list, decided when it's asked. needs = { c_fact = value } shows a question only once they've said it; press = true gives a fresh answer each time it's asked; stress = true lets nerves show.
c:vehicle(key, opts) A parked car, on file.
| Option | Type | What it does |
|---|---|---|
at | string | vector4 | A point name, or a position |
model | string | Spawn name, e.g. 'asea' |
make, name, color | string | What the plate check says: 'Declasse', 'Asea', 'Silver' |
paint | number | GTA colour id |
owner | string | A person key it's registered to (else someone else) |
stolen | boolean | Comes back stolen |
locked | boolean | Defaults to true |
damage | 'front' | The front end smashed in |
c:clue(key, opts) Evidence. Up close, "E Inspect: label" shows text and puts it on every unit's MDT.
| Option | Type | What it does |
|---|---|---|
at / on | string | A point, or a vehicle key to put it on that car |
label, text | string | What it's called, and what you find |
prop | string | An object laid on the ground there; it glows until looked at |
hidden | boolean | Not there until c:reveal(key) |
Objectives
c:objective(id, text, opts) A line on the objective card.
| Option | What it does |
|---|---|
after = { ids } | Must be done first |
who = 'one' | 'all' | One unit takes it (default), or it's on everyone's card |
hint | Smaller text under it |
hidden | Off the card until c:reveal(id) |
blip | A person, vehicle or clue key (or a list) to mark on the map of whoever has it |
c:done(id) marks it done, c:reveal(id) shows a hidden one (and a clue with the same key), c:hide(id) takes it off again, c:is_done(id) checks.
Events
c:on(event, fn)
| Event | Arguments | When |
|---|---|---|
arrive | src | A unit got there |
contact | person, src | Someone started talking to a person |
act | person, action, arg, src | Anything done with them: 'ask' (arg = question id), 'licence', 'frisk', 'cuff', 'release', 'warn', 'cite', 'escort', 'seat'... |
fact | person, fact, value, src | Something they gave away (a line's set) |
contact_ended | person, outcome, src | 'released', 'arrested', 'cited', 'warning', 'cancelled' |
inspect | clue, src | Evidence looked at, the first time |
tick | seconds | Once a second |
gone | person | Taken away by a unit, or deleted |
custody | person, how, src | A prisoner taken in: 'booked' or 'transport' |
aid | person, what, src | First aid ('vitals', 'pressure', 'cpr') or paramedics ('hospital', 'treated') |
Helpers
| Call | Does |
|---|---|
c:custody(person) | They're under arrest: "Take them in" goes on the card until they're booked or transported |
c:talking() | Whether an officer is talking to anyone on the call right now |
c:questions(person) | After giving them a fact from elsewhere: refreshes what the officer talking to them sees |
c:who(person) | Their record on the call: .facts, .cuffed, .identified... |
c:name(person), c:plate(vehicle), c:street() | Real name, plate, the call's street |
c:owner(vehicle) | Who the car's registered to: { first, last, address } |
c:apart(a, b) | Out of each other's earshot (10 m, or either cuffed or in a car) |
c:task(person, task, point?) | 'go' (walk to a point), 'flee', 'surrender', 'wander', 'scene' |
c:say(person, text) | A line heard on scene |
c:dispatch(text) | A radio update to the units on it |
c:note(text) | A line on their MDT incident |
c:set(k, v), c:get(k) | The call's own memory |
c:finish(summary) | It's dealt with: completed, the summary on the MDT |
Sharing
Made a good one? Share it on Discord. Callouts are yours to share; remember the licence: free to share, never to sell.
Stuck? Ask on Discord
View as Markdown