Conveyance/GoalZone.cs
using Sandbox;
/// <summary>
/// A delivery target. When a GameObject tagged "prop" enters the trigger volume,
/// the goal pad changes colour, plays a sound, and the prop is disabled.
///
/// NEW: When DeliveryCount reaches DeliveryThreshold, fires the same targets
/// as InteractButton — Bridge.Toggle(), CableCar.ToggleRunning(), or
/// ToggleTarget.Enabled = true. This means a bascule bridge wired to RewardBridge
/// will lower itself as a door the moment enough props are delivered, no extra code.
///
/// The reward fires ONCE when the threshold is first crossed. Set RewardRepeats = true
/// to fire again every time another full threshold worth of props arrives.
///
/// SETUP:
/// 1. BoxCollider (IsTrigger = true) + ModelRenderer on this GO as before.
/// 2. Set DeliveryThreshold to however many props unlock the reward (default 1).
/// 3. Drag the same targets you'd use on an InteractButton into the Reward group.
/// </summary>
public sealed class GoalZone : Component, Component.ITriggerListener
{
// -------------------------------------------------------------------------
// Existing properties (unchanged)
// -------------------------------------------------------------------------
[Property] public Gradient SuccessColor { get; set; }
[Property] public SoundEvent DeliverySound { get; set; }
[Property] public string AcceptedTag { get; set; } = "prop";
[Property] public int DeliveryCount { get; private set; }
// -------------------------------------------------------------------------
// Reward threshold
// -------------------------------------------------------------------------
/// <summary>How many props must be delivered before the reward fires.</summary>
[Property, Group( "Reward" )]
public int DeliveryThreshold { get; set; } = 1;
/// <summary>
/// If true the reward fires again every time another ThresholdCount props arrive
/// (e.g. every 3 props opens the bridge again after it has been closed).
/// If false (default) the reward fires only the first time the threshold is reached.
/// </summary>
[Property, Group( "Reward" )]
public bool RewardRepeats { get; set; } = false;
// -------------------------------------------------------------------------
// Reward targets — same pattern as InteractButton
// -------------------------------------------------------------------------
/// <summary>Calls Toggle() on a BasculeBridge — works as a door opening.</summary>
[Property, Group( "Reward" )] public BasculeBridge RewardBridge { get; set; }
/// <summary>Calls ToggleRunning() on a JointAnchorMover (cable car / platform).</summary>
[Property, Group( "Reward" )] public JointAnchorMover RewardCableCar { get; set; }
/// <summary>Sets Enabled = true on anything — turret, windmill, light, particle system.</summary>
[Property, Group( "Reward" )] public Component RewardToggleTarget { get; set; }
/// <summary>Optional sound played when the reward fires (separate from per-delivery sound).</summary>
[Property, Group( "Reward" )] public SoundEvent RewardSound { get; set; }
// -------------------------------------------------------------------------
// Private state
// -------------------------------------------------------------------------
private bool _rewardFired;
// -------------------------------------------------------------------------
// Trigger
// -------------------------------------------------------------------------
void Component.ITriggerListener.OnTriggerEnter( Collider other )
{
if ( !other.GameObject.Tags.Has( AcceptedTag ) ) return;
DeliveryCount++;
// Per-delivery visual + sound (unchanged behaviour)
foreach ( var r in Components.GetAll<ModelRenderer>( FindMode.EnabledInSelfAndDescendants ) )
r.Tint = SuccessColor.Evaluate( Game.Random.Float() );
if ( DeliverySound is not null )
Sound.Play( DeliverySound, WorldPosition );
other.GameObject.Enabled = false;
Log.Info( $"[GoalZone] Delivery #{DeliveryCount}: {other.GameObject.Name}" );
// Check whether we've hit the threshold
bool hitThreshold = RewardRepeats
? DeliveryCount % DeliveryThreshold == 0
: DeliveryCount == DeliveryThreshold;
if ( hitThreshold && (!_rewardFired || RewardRepeats) )
{
FireReward();
_rewardFired = true;
}
}
// -------------------------------------------------------------------------
// Reward
// -------------------------------------------------------------------------
private void FireReward()
{
Log.Info( $"[GoalZone] Threshold {DeliveryThreshold} reached — firing reward" );
RewardBridge?.Toggle();
RewardCableCar?.ToggleRunning();
if ( RewardToggleTarget.IsValid() )
RewardToggleTarget.Enabled = !RewardToggleTarget.Enabled;
if ( RewardSound is not null )
Sound.Play( RewardSound, WorldPosition );
}
}