Conveyance/InteractButton.cs
using Sandbox;

/// <summary>
/// Place on any world object (a button prop, a lever, a sign — anything with a collider).
/// When a player looks at it and presses E, it calls Toggle() on the assigned targets.
///
/// SETUP — three steps:
///   1. Add this component to a GameObject that has a collider (so the raycast hits it).
///   2. Set PromptText to whatever the player should see ("Lower bridge", "Start cable car").
///   3. Drag target components into the relevant slots. Leave unused slots empty.
///
/// You can assign multiple targets — e.g. a button that starts the cable car AND
/// enables a turret at the same time. Just fill in as many slots as you need.
///
/// NETWORKING:
///   The RpcActivate broadcast means any player pressing E fires the action on
///   all clients simultaneously. Target components (BasculeBridge, JointAnchorMover
///   etc.) handle their own physics authority internally.
/// </summary>
public sealed class InteractButton : Component, IInteractable
{
	// -------------------------------------------------------------------------
	// What the player sees
	// -------------------------------------------------------------------------

	[Property] public string PromptText { get; set; } = "Press E";

	// -------------------------------------------------------------------------
	// Targets — fill in whichever apply, leave the rest empty
	// -------------------------------------------------------------------------

	/// <summary>Calls Toggle() — use for BasculeBridge.</summary>
	[Property, Group( "Targets" )] public BasculeBridge Bridge { get; set; }

	/// <summary>Calls ToggleRunning() — use for JointAnchorMover (cable car).</summary>
	[Property, Group( "Targets" )] public JointAnchorMover CableCar { get; set; }

	/// <summary>Flips Enabled on/off — works for DragonTurret, WindmillTuner, or anything else.</summary>
	[Property, Group( "Targets" )] public Component ToggleTarget { get; set; }

	// -------------------------------------------------------------------------
	// Optional visual feedback on a model renderer (e.g. a light or tint)
	// -------------------------------------------------------------------------

	[Property, Group( "Feedback" )] public ModelRenderer Indicator { get; set; }
	[Property, Group( "Feedback" )] public Color OnColor  { get; set; } = Color.Green;
	[Property, Group( "Feedback" )] public Color OffColor { get; set; } = Color.Red;

	// -------------------------------------------------------------------------
	// IInteractable
	// -------------------------------------------------------------------------

	string IInteractable.InteractionText => PromptText;

	void IInteractable.OnInteract( GameObject interactor ) => RpcActivate();

	// -------------------------------------------------------------------------
	// Networked activation
	// -------------------------------------------------------------------------

	private TimeSince _lastPress;
	private bool _isOn;

	[Rpc.Broadcast]
	private void RpcActivate()
	{
		if ( _lastPress < 0.5f ) return;
		_lastPress = 0;

		_isOn = !_isOn;

		Bridge?.Toggle();
		CableCar?.ToggleRunning();

		if ( ToggleTarget.IsValid() )
			ToggleTarget.Enabled = _isOn;

		if ( Indicator.IsValid() )
			Indicator.Tint = _isOn ? OnColor : OffColor;

		Log.Info( $"[InteractButton] '{GameObject.Name}' → {(_isOn ? "ON" : "OFF")}" );
	}

	// -------------------------------------------------------------------------
	// Editor gizmo
	// -------------------------------------------------------------------------

	protected override void DrawGizmos()
	{
		Gizmo.Draw.Color = Color.Cyan.WithAlpha( 0.3f );
		Gizmo.Draw.LineSphere( WorldPosition, 30f );
	}
}