things/HealingZone.cs

A HealingZone game entity. It spawns a particle emitter, tracks lifetime and periodically heals nearby Players and Enemies while playing a heal sound and visual ring effects.

NetworkingFile Access
using System;
using Sandbox;

public class HealingZone : Thing
{
	[Property] public ParticleEmitter Emitter { get; set; }

	public float Damage { get; set; }
	[Sync] public float Lifetime { get; set; }

	private Dictionary<Thing, float> _healTimes;
	private const float HEAL_INTERVAL = 0.75f;

	private bool _hasStoppedSpawning;

	private TimeSince _timeSinceHeal;

	public float HealAmount { get; set; }

	protected override void OnStart()
	{
		base.OnStart();

		_timeSinceHeal = 0f;

		Lifetime = 60f; // todo: should be able to overlap with other events?

		if ( IsProxy )
			return;

		CollideWithTags.Add( "player" );
		CollideWithTags.Add( "enemy" );

		_healTimes = new();

		HealAmount = 5f;
	}

	protected override void OnUpdate()
	{
		base.OnUpdate();

		bool shouldHeal = false;

		if( _timeSinceHeal > HEAL_INTERVAL && TimeSinceSpawn > 1f && TimeSinceSpawn < Lifetime - 0.5f )
		{
			Manager.Instance.SpawnRing( Position2D, Radius, new Color( 0.1f, 1f, 0.1f, 0.25f ), lifetime: HEAL_INTERVAL * 1.25f, path: "ring2" );

			shouldHeal = true;

			_timeSinceHeal = 0f;
		}

		if ( !_hasStoppedSpawning && TimeSinceSpawn > Lifetime - 5f )
		{
			Emitter.Loop = false;
			_hasStoppedSpawning = true;
		}

		if ( Manager.Instance.IsGameOver )
			return;

		if ( IsProxy )
			return;

		//Gizmo.Draw.Color = Color.White;
		//Gizmo.Draw.Text( $"{_damageTimes.Count}", new global::Transform( WorldPosition ) );

		for(int i = _healTimes.Count - 1; i >= 0; i-- )
		{
			var pair = _healTimes.ElementAt(i);
			if ( Time.Now > pair.Value + HEAL_INTERVAL )
				_healTimes.Remove( pair.Key );
		}

		if ( TimeSinceSpawn > Lifetime )
			GameObject.Destroy();

		var playSfx = false;

		if( shouldHeal )
		{
			foreach( var other in Touching )
			{
				if ( other is Enemy enemy && !enemy.IsSpawning && !enemy.IsDying && !enemy.IsInTheAir )
				{
					if( enemy.Health < enemy.MaxHealth )
						playSfx = true;

					enemy.Heal( HealAmount );
					
				}
				else if ( other is Player player && !player.IsDead && !player.IsInTheAir )
				{
					if ( player.Health < player.GetSyncStat( PlayerStat.MaxHp ) )
						playSfx = true;

					player.HealRpc( HealAmount );
				}
			}
		}

		if ( playSfx )
		{
			Manager.Instance.PlaySfxNearbyRpc( "heal", Position2D, pitch: Game.Random.Float(1.4f, 1.5f), volume: 0.4f, maxDist: 250f );
		}
	}

	public override void Colliding( Thing other, float percent, float dt )
	{
		base.Colliding( other, percent, dt );

		//if ( TimeSinceSpawn < 0.35f || TimeSinceSpawn > Lifetime - 0.33f || _healTimes.ContainsKey(other) )
		//	return;

		//if ( other is Unit unit && !unit.IsDying && !unit.IsInTheAir )
		//{
		//	if ( other is Enemy e && e.IsSpawning )
		//		return;

		//	//if ( unit is Enemy enemy )
		//	//{
		//	//	enemy.DamageRpc( Damage, PlayerSource, DamageType.Fire, new Vector3( hitPos.x, hitPos.y, 30f ), Vector2.Zero, isCrit: false, shouldFlinch: false );
		//	//}
		//	//else if ( unit is Player player )
		//	//{
		//	//	player.DamageRpc( Damage, damageType: DamageType.Fire, hitPos, dir, 0f, EnemySource );
		//	//}

		//	_healTimes.Add( other, Time.Now );
		//}
	}

	public void SetScale( float scale )
	{
		var sphereEmitter = GetComponent<ParticleSphereEmitter>();
		sphereEmitter.Radius *= scale;
		sphereEmitter.Rate = new ParticleFloat { Type = ParticleFloat.ValueType.Constant, ConstantValue = sphereEmitter.Rate.ConstantValue * scale };

		var sphereCollider = Collider as SphereCollider;
		sphereCollider.Radius *= scale;

		//var particleEffect = GetComponent<ParticleEffect>();
		//var range = new ParticleFloat { Type = ParticleFloat.ValueType.Range, ConstantA = 50f * scale, ConstantB = 70f * scale };
		//particleEffect.Scale = range;

		Radius = sphereCollider.Radius;
	}
}