A mission¶
A mission for one or two players, started from a marker on Grove Street: escort a dealer to a meeting, everyone together, then fight off the Ballas who show up, before the time runs out. It uses most of the mission helper: a start marker, a mission ped to protect, a waiting point, a section with catch-up, a timer that fails it, a counter, and a reward with a cooldown.
mission.ts
import { Server, Weapon, defineMission, type Ped } from "@sagl/server";
const GROVE = { x: 2491, y: -1668, z: 13.3 };
const MEETING = { x: 2337, y: -1748, z: 13.5 };
const BALLAS_AT = [
{ x: 2320, y: -1760, z: 13.5 },
{ x: 2352, y: -1765, z: 13.5 },
{ x: 2330, y: -1730, z: 13.5 },
];
const theMeeting = defineMission({
name: "The Meeting",
maxPlayers: 2,
cooldown: 10 * 60_000, // once every ten minutes per player
minDuration: 30_000, // nobody walks it in under 30 s
requires: (p) => (p.wantedLevel > 0 ? "Lose the cops first." : undefined),
async run(m) {
// The dealer: a mission ped, seen by the party, who keeps to his orders.
const dealer = m.createPed(28, { x: 2495, y: -1672, z: 13.3 }, 90, { mission: true, weapon: Weapon.Pistol });
m.blip(dealer, { color: "blue" });
m.failWhen(dealer, "died", "The dealer died.");
m.objective("Walk the ~b~dealer~w~ to the ~y~meeting~w~.");
void dealer.follow(m.leader, { distance: 3 });
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 }); // a waiting point: the whole party
close.stop();
// Anyone who wandered off is brought here before the fight.
await m.section("fight", { at: [MEETING, { ...MEETING, x: MEETING.x + 2 }] });
await m.step(dealer.walkTo({ ...MEETING, y: MEETING.y + 4 })); // into the doorway
void dealer.cower();
m.bigText("AMBUSH!", 2000);
const ballas: Ped[] = BALLAS_AT.map((at) =>
m.createPed(102, at, 0, { mission: { hostileTo: [...m.players] }, weapon: Weapon.Pistol }));
for (const b of ballas) m.blip(b, { color: "red" });
const left = m.counter("Ballas", { value: ballas.length });
m.timer("Time", 90_000, { fail: "The dealer got away." });
m.objective("Kill the ~r~Ballas~w~.");
await m.until(() => {
left.value = ballas.filter((b) => b.exists && !b.dead).length;
return left.value === 0;
});
m.pass({ money: 1500 });
},
});
export function missions(server: Server) {
server.missions.marker(theMeeting, GROVE);
server.on("missionEvent", (e) => console.log(`[mission] ${e.type} ${e.name} ${e.player?.name ?? ""} ${e.reason}`));
}
Things to notice:
- Waits stop when the mission ends. If the dealer dies on the way,
m.reachrejects withMissionEndedand the script stops there; the helper catches it, removes the peds, blips and HUD, and shows "MISSION FAILED!" with the reason. keepClosetells a player who runs ahead to wait for the dealer (only them), and fails the mission if the whole party leaves him behind.- Ped orders never reject, so
m.step(dealer.walkTo(...))wraps one to make it stop with the mission too. - Late joiners (
m.addPlayer(friend)) get the objective, blips, counter and timer as they are, and see the mission's peds. - The reward is paid once, by
pass(), to each player in the party.