Missions¶
A mission is a script for a party of players, written top to bottom as in GTA's own missions: create a ped, set an objective, wait for the party to get somewhere, pass. The helper does the bookkeeping around it.
- The party shares the mission's peds, blips, objective text, counters and timers, including players who join later.
- Waits (
reach,enter,until, ...) say whether the first player completes the step for everyone, or every player must (a waiting point). - Sections set where the party respawns, and bring anyone left far behind to the start of the next part, faded out and back in.
- Fail conditions end it for everyone, stopping the script where it waits.
- Cleanup: when it ends, its blips and HUD go and its characters fade out, as in GTA: bodies lie where they fell until the cleanup takes them, and its vehicles stay until nobody is near.
- Exploit tooling: rewards only through
pass(), once; cooldowns; requirements checked at start and on pass; passes that are too fast are refused; every start, pass, fail and refusal is amissionEvent.
A mission lives as long as the gamemode: a server restart loses it, and a player who leaves (or reconnects) leaves it.
const delivery = defineMission({
name: "Delivery",
maxPlayers: 2,
cooldown: 10 * 60_000,
async run(m) {
const car = m.createVehicle(482, DEPOT, 90) // a Burrito;
m.failWhen(car, "wrecked", "The van was destroyed.");
m.objective("Get in the ~b~van~w~.");
m.blip(car, { color: "blue" });
await m.enter(car, { allPlayers: true });
m.objective("Drive to the ~y~drop-off~w~.");
m.timer("Time", 120_000, { fail: "Too late." });
await m.reach(DROP_OFF, { vehicle: car });
m.pass({ money: 2500 });
},
});
server.missions.marker(delivery, { x: 2491, y: -1670, z: 13.3 });
Defining a mission¶
defineMission(definition)¶
Returns the definition as it is; it gives the editor the types.
| Field | Type | Description |
|---|---|---|
name |
string |
For cooldowns and missionEvent. |
maxPlayers |
number |
Party size. Default 1. |
cooldown |
number |
After a pass, the same player can't start it again for this long (ms). Failing doesn't count. |
minDuration |
number |
A pass sooner than this after the start (ms) is refused, with no reward, and logged. |
requires |
(player) => string \| void |
Checked for each player when they start or join, and again on pass. Return a reason (shown to them) to refuse. |
deathFails |
boolean |
One player dying fails it. Default false: they respawn at the section start. |
catchUpDistance |
number |
How far (m) from a new section's start before a player is brought there. Default 150. |
run |
(m: Mission) => Promise<void> |
The script. If it returns without pass() or fail(), the mission passes. If it throws, the mission fails (reason "error") and the error is logged. |
steps |
MissionStep[] |
Or the script as named steps, run in order (see below). |
state |
() => S |
With steps: the state they share, made fresh for each run. |
Steps and checkpoints¶
A script as named steps reads top to bottom, and each step is a
checkpoint: the mission can be started from it, which runs its setup
first to put in place what the steps before would have left (a ped in a
car, the party somewhere). Each step's name is logged as it starts
(m.stage).
const job = defineMission<{ benny: Ped }>({
name: "Clean Hands",
state: () => ({}) as { benny: Ped },
steps: [
{ name: "call 1", run: async (m) => { await phoneCall(m); } },
{
name: "grab Benny",
setup: async (m, s) => { s.benny = m.createPed(46, CAB_STAND, 0); }, // only when starting here
run: async (m, s) => { await m.reach(s.benny.position, { radius: 4 }); },
},
],
});
server.missions.start(job, player, { from: "grab Benny" }); // testing the second half
Starting missions: server.missions¶
marker(definition, at, options?)¶
A start marker: a blip and the game's yellow marker for every player who
isn't on a mission. Walking into it on foot starts the mission, with
anyone else standing in it joining the party (up to maxPlayers).
| Option | Type | Description |
|---|---|---|
radius |
number |
Default 1.5 m. |
world |
number |
Virtual world. Default 0. |
blip |
BlipOptions | false |
The blip and marker; false for none. Default: the game's yellow. |
onRefused |
(player, reason) => void |
Default: the reason as an objective line for 3 s. |
Returns a MissionMarker; remove() takes it away.
start(definition, players, options?)¶
start(definition: MissionDefinition, players: Player | Player[], options?: { from?: string }): Mission
Starts a mission for these players; the first is the leader. from
starts a mission made of steps at that step (its setup first).
Throws MissionRefused (with reason) if any of them is on a mission,
cooling down, or doesn't meet requires, or there are more than
maxPlayers.
server.on("playerEnterVehicle", (player, vehicle) => {
if (vehicle.model !== 420 || player.seat !== 0) return; // a taxi, driving
try {
server.missions.start(taxiDriver, player);
} catch (e) {
if (e instanceof MissionRefused) player.hud.objective(e.reason, 3000);
}
});
of(player), running¶
The mission a player is on (or null), and every mission running.
Mission¶
What run gets, and what start returns.
| Member | Description |
|---|---|
name, definition |
|
players |
The party, leader first. |
leader |
Whoever started it, or the next in line once they leave. |
running |
False once it has ended. |
outcome |
How it ended (MissionOutcome), or null. |
done |
A promise of the MissionOutcome: resolves when it ends, however it ends. |
data |
Free-form storage for the script. |
startedAt |
Date.now() at the start. |
The party¶
addPlayer(player)¶
Adds a player (an invitation). They get its HUD as it is and see its
peds. Throws MissionRefused as start does, or if it's full.
removePlayer(player)¶
Takes them out; their HUD for it goes. The last player leaving abandons the mission (no screen is shown). Players leaving the server leave their mission.
Creating things¶
createPed(model, position, heading?, options?)¶
As server.createPed, plus onEnd. Seen by the party
(players joining later too), unless you give visibleTo. When the mission
ends it fades out, or if it's dead its body is
released: it lies there until it has been out of view a
minute. onEnd: "keep" (or keep: true) keeps it for good;
onEnd: "destroy" removes even a body at once.
createVehicle(model, position, heading?, options?)¶
As server.createVehicle, plus onEnd. When the mission ends
it's released: it stays until nobody is near (a
wreck, until it has been out of view 30 s). onEnd: "destroy" removes it
at once (a cab nobody should find afterwards); onEnd: "keep" (or keep)
leaves it for good.
createObject(model, position, rotation?, options?), createPickup(options)¶
As server.createObject and server.createPickup, seen by
the party unless visibleTo says otherwise. Destroyed and removed when
the mission ends.
HUD¶
Each shows for the whole party, and for players who join later.
| Method | Description |
|---|---|
objective(text, ms?) |
Objective text at the bottom (game codes like ~y~ work). |
clearObjective() |
|
bigText(text, ms?) |
Big text in the middle. |
blip(target, options?) |
A blip (and the game's marker) on a spot, ped, vehicle or player: see PlayerHud.blip. Returns a MissionBlip (set(), remove()). |
counter(label, options?) |
"Crates 2/6": { value, max, bar, color }. Returns a MissionCounter (value, label, remove()). |
timer(label, ms, options?) |
A countdown (countUp to count up). With fail: "reason", the mission fails when it reaches 0. value is the time left. |
const crates = m.counter("Crates", { max: 6 });
m.timer("Time", 90_000, { fail: "You ran out of time." });
crates.value++;
Waiting¶
Every wait rejects with MissionEnded if the mission ends first, which
stops the script there; the helper catches it, so let it go.
Steps say who has to do them:
| Option | |
|---|---|
{ anyPlayer: true } |
The first player does it for everyone (the default). |
{ allPlayers: true } |
A waiting point: every player must. |
reach(spot, options?)¶
Waits for the party to reach a spot, shown with a blip and the game's
yellow marker to those who haven't. With allPlayers, each counts once
they've been there and the rest are waited for. Resolves with the
player who completed it (the last one, for allPlayers).
| Option | Type | Description |
|---|---|---|
radius |
number |
Default 3 m. |
vehicle |
Vehicle |
They must be in it, driving or riding. |
marker |
boolean | BlipOptions |
The blip and marker. Default true. |
anyPlayer, allPlayers |
boolean |
See above. |
const first = await m.reach(ROOFTOP); // whoever gets there first
await m.reach(MEETING_POINT, { allPlayers: true }); // everyone, before the cutscene
await m.reach(GARAGE, { vehicle: van }); // in the van
enter(vehicle, options?)¶
enter(vehicle: Vehicle, options?: { anyPlayer?: boolean; allPlayers?: boolean; driver?: boolean }): Promise<Player>
Waits for the party to get in a vehicle (driver: in its driver's seat).
allPlayers waits until they're all in it at once.
untilPlayer(condition, options?)¶
Waits until condition holds for the first player, or for all at once.
until(condition, { timeout? })¶
Resolves with true once condition() is true (checked four times a
second); with timeout (ms), with false if that passes first. Give
waits in a scene a limit, so a blocked ped can't leave it stuck.
allDead(peds)¶
Resolves once every one of these peds is dead or removed.
wait(ms)¶
Resolves after ms.
step(promise)¶
Waits for any promise, such as a ped's order, but rejects with
MissionEnded if the mission ends first. With { timeout } (ms),
resolves with undefined if that passes first.
await m.step(ryder.walkTo(CAR));
await m.step(benny.walkTo(OUTSIDE, { pace: "run" }), { timeout: 4000 }); // knocked over: on anyway
Scenes and speech¶
cinematic(shot, scene, options?)¶
cinematic(shot: CameraShot, scene: (cut) => Promise<void>, options?: { players?: Player[]; freeze?: boolean; holster?: boolean; screenEffects?: "clear" | "keep" }): Promise<void>
A cinematic shot for the party (or players): black bars, the camera on
shot, everyone frozen and their weapons put away while scene runs.
Then the camera's back behind them, they can move and have their weapons
again, however the scene ends (the mission ending too). cut(shot)
changes the shot; cut(null) goes back to the first. freeze: false and
holster: false leave those alone.
Their screen effects are cleared for the scene and
come back as they were after (a snapshot), so a player's own depth of field
or grading doesn't spoil the shot; the scene can add its own meanwhile.
screenEffects: "keep" leaves them on.
await m.cinematic({ position: car.offset({ right: 3.2, ahead: 1.2, up: 0.9 }), lookAt: { vehicle: car } },
() => m.say("audio/benny-locked.mp3", { at: benny, subtitle: LINE }));
say(file, options?)¶
A recorded line (a client file) with its subtitles timed to it, shown to
the party; the radio ducks under it. Resolves when it's been said, and
stops if the mission ends. Takes server.audio.speak's
options: at a ped or vehicle to come from them (muffled from inside
it), left out at the party's ears (effects: "phone" for a caller).
await m.say("audio/voice-call2.mp3", { effects: "phone", subtitle: CALL2, prefix: "~y~" });
await m.say("audio/benny-my-car.mp3", { at: benny, subtitle: "My car! That's my car!" });
For as long as it runs¶
on(event, listener), every(ms, fn)¶
A server event listener, or a function called every ms,
for as long as the mission runs: removed when it ends however it ends (so
no try/finally), or earlier with the function each returns.
m.on("playerExitVehicle", (player, vehicle) => { if (vehicle === getaway) alarm(); });
const stop = m.every(1000, () => updateChase());
population(settings)¶
Changes the population of the leader's world (as
world.population) until the mission ends, or the function
it returns is called: then the settings it changed are as they were.
holdWhileWanted(message?)¶
While anyone in the party is wanted, the mission's timers stand still and
message (default "Lose the cops.") stays as the objective; objectives
set meanwhile show once they're clear.
stage(name), currentStage¶
Marks where the script is: logged, and a missionEvent of type
"stage". Steps do it themselves.
keepClose(target, options?)¶
keepClose(target: Ped | Vehicle | Player, options?: {
warnAt?: number; warning?: string; failAt?: number; fail?: string;
}): { stop(): void }
Keeps the party near someone: an escort, a ped to follow, a vehicle.
- A player further than
warnAt(default 25 m) getswarningin place of the objective (only them), until they're back in range, when the objective returns. - With
failAt, the mission fails (fail) once every player is further than that.
It lasts until the mission ends or you stop() it.
const close = m.keepClose(dealer, {
warnAt: 25, warning: "You're too far away. Wait for the ~b~dealer~w~ to catch up.",
failAt: 150, fail: "You lost the dealer.",
});
await m.reach(MEETING, { allPlayers: true });
close.stop();
cooldown(key, ms)¶
true if key was used in the last ms, else starts it again and
returns false. Throttles repeated actions:
Cutscenes¶
cutscene(name, options?)¶
Plays one of the game's cutscenes for the whole party together; resolves
when everyone's has finished. Skipping is a vote by default. The options
and result are server.playCutscene's.
If the mission ends meanwhile, the cutscene stops.
Sections¶
section(name, options)¶
section(name: string, options: { at: Vector3 | Vector3[]; heading?: number; catchUpDistance?: number }): Promise<void>
Starts a part of the mission:
- Respawning: a party member who dies (while others are alive) respawns, then is brought to the section's start.
- Catching up: anyone further than
catchUpDistancefrom it, or in another world, is faded out, moved there, and faded back in. Resolves once they're there.
at is one spot, or one per party member in party order so they don't
land on top of each other.
Ending¶
failWhen(...)¶
failWhen(ped: Ped, event: "died", reason: string): { stop() }
failWhen(vehicle: Vehicle, event: "wrecked", reason: string): { stop() }
failWhen(condition: () => boolean, reason: string): { stop() }
Fails the mission when the ped dies (or is removed), the vehicle is
wrecked (or removed), or condition() becomes true; until stop().
m.failWhen(ryder, "died", "Ryder died.");
m.failWhen(() => Math.hypot(ryder.position.x - m.leader.position.x, ryder.position.y - m.leader.position.y) > 150,
"You left Ryder behind.");
fail(reason?)¶
Fails it for everyone: "MISSION FAILED!" with reason under it.
pass(reward?)¶
Passes it: each player gets money, once, and "MISSION PASSED!" with
text under it (default: the money, $2500). Refused (no reward, reason
tooFast or requirements) if it's sooner than minDuration or a player
no longer meets requires.
The mission also fails:
- for everyone, when every party member is dead at once ("Everyone
died."), or when one dies and
deathFailsis set; - with reason
abandoned(no screen) when the party empties.
MissionOutcome¶
| Field | Type | Description |
|---|---|---|
passed |
boolean |
|
reason |
"passed" | "failed" | "abandoned" | "tooFast" | "requirements" | "error" |
|
text |
string |
The fail reason, or the pass subtitle. |
players |
Player[] |
Who was in it at the end. |
ms |
number |
How long it ran. |