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.
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;
}
}