Code/GolfOutOfBounds.cs

A component that defines an out-of-bounds box trigger for a golf game. When a golf ball enters it the host treats it like a ball leaving the hole bounds and posts an OnBallOutOfBounds event; when a non-ball body enters it posts OnBodyOutOfBounds. It also draws a translucent red box gizmo in the editor using the BoxCollider scale.

Networking
using Sandbox;

/// <summary>
/// A placeable out-of-bounds zone: a box trigger (the Box Collider on the same object, Is Trigger
/// on) that a golf ball must not enter. When one does, the host treats it exactly as a ball that
/// left the Hole's bounds: back to where it was last shot from, the out-of-bounds penalty, the
/// announcement and the sound. A player who walks in is stood back next to their ball, or by the
/// tee if their ball hasn't been shot: no damage, no shot cost. Works on any hole and next to the
/// Hole's own bounds, which keep doing their job. Pickups and everything else pass through it
/// untouched. Drawn in the editor as a translucent red box, so it is easy to see and to resize
/// from the Box Collider's Scale.
/// </summary>
[Title( "Golf Out Of Bounds" ), Category( "Golf" ), Icon( "block" )]
public sealed class GolfOutOfBounds : Component, Component.ITriggerListener
{
	void Component.ITriggerListener.OnTriggerEnter( Collider other )
	{
		// Physics runs everywhere, but only the host decides what counts.
		if ( !Networking.IsHost ) return;
		if ( !other.IsValid() || !other.GameObject.IsValid() ) return;

		var ball = other.GameObject.GetComponentInParent<IGolfCourseBall>();
		if ( ball is not null )
		{
			if ( !ball.IsHoled ) IGolfCourseEvents.Post( x => x.OnBallOutOfBounds( this, ball ) );
			return;
		}

		// Anything else (a player's body, for one): the game resolves it.
		IGolfCourseEvents.Post( x => x.OnBodyOutOfBounds( this, other.GameObject ) );
	}

	protected override void DrawGizmos()
	{
		var box = GetComponent<BoxCollider>( true );
		var bounds = box is not null
			? BBox.FromPositionAndSize( box.Center, box.Scale )
			: BBox.FromPositionAndSize( new Vector3( 0f, 0f, 100f ), new Vector3( 200f, 200f, 200f ) );

		var alpha = Gizmo.IsSelected ? 0.35f : (Gizmo.IsHovered ? 0.25f : 0.12f);
		Gizmo.Draw.Color = Color.Red.WithAlpha( alpha );
		Gizmo.Draw.SolidBox( bounds );
		Gizmo.Draw.LineThickness = Gizmo.IsSelected ? 2f : 1f;
		Gizmo.Draw.Color = Color.Red.WithAlpha( Gizmo.IsSelected ? 0.9f : 0.5f );
		Gizmo.Draw.LineBBox( bounds );
	}
}