Effects/SnipeZone.cs
using System;

namespace PlanetMeat;

[Group( "Planet Meat - Effects" ), Title( "Snipe Zone" ), Icon( "location_searching" )]
public sealed class SnipeZone : Component
{
	public const int STAGE_INDEX_ARRIVING = 0;
	public const int STAGE_INDEX_SNIPING = 1;

	// RequireComponent doesn't make this stick... for some reason
	[RequireComponent] public AutoWeapon Weapon { get => field ??= Components.Get<AutoWeapon>(); set; }

	[RequireComponent] public Targeter Targeter { get; set; }
	[RequireComponent] public DelayedStages Delay { get; set; }
	[Property] float DriftSpeed { get; set; } = 250.0f;
	[Property] float DriftScatter { get; set; } = 0.25f;
	[Property] float DepatureTime { get; set; } = 2.5f;

	public float DriftAngle
	{
		get
		{
			if ( float.IsNaN( field ) )
				field = Game.Random.Float( 2.0f * MathF.PI );
			return field;
		}
		private set;
	} = float.NaN;

	[Property]
	public float Duration { get; set; } = 20.0f;

	private TimedRing Ring { get; set; }

	public void ApplyConfig( float delay, float durationMult, int dmgPerShot, float steadiness, float radius )
	{
		var drift = 1.0f - steadiness;
		DriftSpeed *= drift;
		DriftScatter *= drift;

		Weapon.BaseDamage = dmgPerShot;
		Targeter.BaseRange = (int)Math.Ceiling( radius );
		Delay.Delay = delay;
		Duration *= durationMult;
	}

	protected override void OnStart()
	{
		base.OnStart();
		Weapon.Enabled = false;
		Delay.DestroyAfter = DepatureTime;
		Delay.StageStarted += OnStageStarted;
		Ring = TimedRing.Create( WorldPosition, Weapon.Targeter.Range );
		Ring.StartBasicTimer( Delay.Delay, false );
	}

	protected override void OnUpdate()
	{
		base.OnUpdate();
		if ( Delay.StageIndex == STAGE_INDEX_SNIPING )
		{
			var spread = DriftScatter * MathF.PI * Time.Delta;
			DriftAngle += Game.Random.Float( -spread, spread );
			WorldPosition += new Vector3( Vector2.FromRadians( DriftAngle ) ) * DriftSpeed * Time.Delta;
			Ring.WorldPosition = WorldPosition;
		}
	}

	protected override void OnDestroy()
	{
		base.OnDestroy();
		if ( Ring?.GameObject is GameObject ringGo && ringGo.IsValid )
			ringGo.Destroy();
	}

	public void OnStageStarted( int stage )
	{
		Weapon.Enabled = stage == STAGE_INDEX_SNIPING;
		switch ( stage )
		{
			case STAGE_INDEX_SNIPING:
				Delay.Delay = Duration;
				Ring.StartBasicTimer( Duration, true );
				break;
			case DelayedStages.STAGE_INDEX_DESTROY:
				Ring?.GameObject.Destroy();
				Ring = null;
				break;
		}
	}
}