MissionStartTrigger.cs
using Sandbox;
using SWB.Shared;
// Put this on the object you already have in the map, alongside a
// trigger Collider sized to the area the player needs to stand in to
// interact with it. Walking into range and holding the Use button (E
// by default) for HoldDuration seconds kicks off the mission: cops
// start spawning and the survive timer starts counting.
public sealed class MissionStartTrigger : Component, Component.ITriggerListener, IInteractable
{
[Property]
public MissionSpawner Spawner { get; set; }
[Property]
public MissionTimer Timer { get; set; }
[Property]
public float HoldDuration { get; set; } = 3f;
bool _playerInRange = false;
float _holdProgress = 0f;
bool _missionStarted = false;
// Drives the on-screen prompt - see IInteractable. This one matters
// most of the three: it's a 3 second hold, so without a progress bar
// it just feels like nothing is happening.
public bool PlayerInRange => _playerInRange;
public float HoldFraction => HoldDuration <= 0f ? 0f : _holdProgress / HoldDuration;
public string PromptText => "Hold E to start the job";
public bool SuppressPrompt => _missionStarted;
public void OnTriggerEnter( Collider other )
{
if ( other.GameObject.Root.Tags.Has( "player" ) )
_playerInRange = true;
}
public void OnTriggerExit( Collider other )
{
if ( other.GameObject.Root.Tags.Has( "player" ) )
{
_playerInRange = false;
_holdProgress = 0f; // walking away mid-hold cancels progress
}
}
protected override void OnUpdate()
{
if ( _missionStarted || !_playerInRange )
return;
if ( Input.Down( InputButtonHelper.Use ) )
{
_holdProgress += Time.Delta;
if ( _holdProgress >= HoldDuration )
BeginMission();
}
else
{
_holdProgress = 0f;
}
}
void BeginMission()
{
// Local guard so a held key can't fire this twice before the
// broadcast lands back on us.
_missionStarted = true;
BeginMissionBroadcast();
}
// Input is read locally, so without broadcasting, whoever held E
// starts the job only on their own machine - everyone else sees
// nothing and has to start it themselves. This puts every client into
// the mission together off one person's interaction.
//
// Safe to run on all clients: MissionSpawner ignores StartSpawning on
// non-hosts, so only the host actually produces cops. The rest just
// need their timer and HUD running.
[Rpc.Broadcast]
void BeginMissionBroadcast()
{
_missionStarted = true;
Spawner?.StartSpawning();
Timer?.StartMission();
// The "go here to start the job" waypoint has done its job. Found
// on this same GameObject rather than wired in the editor, so
// adding the marker is the only step.
var marker = Components.Get<ObjectiveMarker>();
if ( marker is not null )
marker.Enabled = false;
}
}