Skip to main content

Event orchestration

Real GTA V missions do not just spawn enemies who immediately shoot. Think of the best moments in the story: two dealers argue in an alleyway, a third character steps out of the shadows, a phone rings, and only then does the deal go sideways.

Orchestrating multiple actors requires coordinating timelines: when actor A reaches a door, actor B must react, and actor C must flee.

The problem with hard-coded timers

Amateur scripters often try to coordinate scenes using raw delays:

// Fragile: what if actor A gets blocked by a car?
actorA.Task.GoTo(meetingPoint);
// 5 seconds later, assume actor A is there...
actorB.Task.PlayAnimation(...);

If actor A gets stuck behind a parked van or trips over a curb, actor B starts talking to empty air five seconds later. The scene falls apart.

Cue-based event choreography

Professional modding scenes are cue-based rather than time-based. Step N only begins when Step N-1 signals that its physical conditions have been met:

[ Step 1: Approach ]
Both actors walk to the meet point.
Wait until: Distance between actors < 2.0m.
| (signal: InPosition)
v
[ Step 2: Conversation ]
Actors play talking animations and subtitles show.
Wait until: Player draws weapon OR enters trigger zone.
| (signal: Provoked)
v
[ Step 3: Escalation ]
Buyer flees on foot. Dealer draws pistol and opens fire.

Implementing a scene coordinator in C#

You can coordinate this cleanly using a step counter or an explicit scene state:

public sealed class DealSceneCoordinator
{
private enum SceneStep { WalkingToMeet, Conversing, FleeingAndShooting, Finished }
private SceneStep _step = SceneStep.WalkingToMeet;

public void Update(Ped dealer, Ped buyer, Ped player)
{
switch (_step)
{
case SceneStep.WalkingToMeet:
if (dealer.Position.DistanceTo(buyer.Position) < 2.5f)
{
// Both arrived: trigger conversation
dealer.Task.ChatTo(buyer);
buyer.Task.ChatTo(dealer);
_step = SceneStep.Conversing;
}
break;

case SceneStep.Conversing:
// Interrupt if player intervenes
if (player.Position.DistanceTo(dealer.Position) < 6f || player.IsArmed(WeaponCheckFlags.All))
{
TriggerEscalation(dealer, buyer, player);
_step = SceneStep.FleeingAndShooting;
}
break;

case SceneStep.FleeingAndShooting:
// Check if scene has resolved
if (dealer.IsDead && buyer.Position.DistanceTo(player.Position) > 50f)
{
_step = SceneStep.Finished;
}
break;
}
}

private void TriggerEscalation(Ped dealer, Ped buyer, Ped player)
{
dealer.Task.ClearAll();
buyer.Task.ClearAll();

// One fights, the other flees
dealer.Weapons.Give(WeaponHash.MicroSMG, 100, true, true);
dealer.Task.Combat(player, TaskCombatFlags.None, TaskThreatResponseFlags.None);

buyer.Task.FleeFrom(player);
GTA.UI.Notification.PostTicker("The deal went sour!", false);
}
}

By decoupling the scene into observable conditions, the choreography adapts naturally whether the player watches silently from a rooftop with a sniper rifle or charges in with a shotgun.

In Project 06, we build this exact dynamic deal event.