things/AcidPuddle.cs

An AcidPuddle Thing component for the game. It manages a decal, visual tint/size over its Lifetime, tracks which Things it has damaged recently, and applies periodic acid damage or healing to Players and damage to Enemies on collision.

NetworkingFile Access
using System;
using Sandbox;

public class AcidPuddle : Thing
{
	[Property] public Decal Decal { get; set; }

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

	public Enemy EnemySource { get; set; }
	public EnemyType EnemyType { get; set; }

	public Player PlayerSource { get; set; }

	private Dictionary<Thing, float> _damageTimes;
	private const float DAMAGE_INTERVAL_PLAYER = 0.3f;
	private const float DAMAGE_INTERVAL_ENEMY = 0.5f;

	private float _colorTimeOffset;

	private Vector3 _modelScaleBase;

	public override bool UseSpawnScale => false;

	[Property, Hide] public Color ColorA { get; set; }
	[Property, Hide] public Color ColorB { get; set; }

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

		_colorTimeOffset = Game.Random.Float( 0f, 99f );
		_modelScaleBase = new Vector3( 0.2f, 0.15f, 0.2f );

		Decal.ColorTint = ColorA.WithAlpha( 0f );

		if ( IsProxy )
			return;

		//WorldPosition = WorldPosition.WithZ( 0f );

		_damageTimes = new();
	}

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

		var color = Color.Lerp( ColorA, ColorB, 0.5f + Utils.FastSin( Time.Now * 32f ) * 0.5f );
		Decal.ColorTint = color.WithAlpha( Utils.Map( TimeSinceSpawn, Lifetime - 0.5f, Lifetime, color.a, 0f ) );

		var decalSize = Radius * Utils.Map( WorldScale.x, 0.5f, 2f, 0.2f, 0.04f ) * 1.45f * Utils.Map( TimeSinceSpawn, 0f, 0.25f, 0f, 1f, EasingType.SineOut );
		Decal.Size = new Vector2( decalSize, decalSize );

		//var alpha = Utils.Map( TimeSinceSpawn, 0f, 0.5f, 0f, 1f, EasingType.QuadOut ) * Utils.Map( TimeSinceSpawn, Lifetime - 0.5f, Lifetime, 1f, 0f, EasingType.QuadOut );
		//ModelRenderer.Tint = Color.Lerp( ColorA, ColorB, 0.5f + Utils.FastSin( _colorTimeOffset + TimeSinceSpawn * 32f ) * 0.5f ).WithAlpha( alpha );

		if ( Manager.Instance.IsGameOver )
			return;

		//var scaleModifier = Utils.Map( TimeSinceSpawn, 0f, 0.5f, 0f, 1f, EasingType.QuadOut ) * Utils.Map( TimeSinceSpawn, Lifetime - 1f, Lifetime, 1f, 1.3f, EasingType.Linear );
		//ModelRenderer.LocalScale = new Vector3(_modelScaleBase.x, _modelScaleBase.y, _modelScaleBase.z) * scaleModifier;

		if ( IsProxy )
			return;

		var playerInterval = DAMAGE_INTERVAL_PLAYER * Utils.Select( Manager.Instance.Difficulty, 1.25f, 1f, 1f );

		for ( int i = _damageTimes.Count - 1; i >= 0; i-- )
		{
			var pair = _damageTimes.ElementAt( i );

			var interval = pair.Key is Player player 
				? playerInterval
				: DAMAGE_INTERVAL_ENEMY;

			if ( Time.Now > pair.Value + interval )
				_damageTimes.Remove( pair.Key );
		}

		//Gizmo.Draw.Color = Color.White;
		//Gizmo.Draw.Text( $"Radius: {Radius}\nTimeSinceSpawn: {TimeSinceSpawn}/{Lifetime}", new global::Transform( WorldPosition ) );
		//Gizmo.Draw.Text( $"{WorldScale.x}", new global::Transform( WorldPosition ) );

		//Gizmo.Draw.Color = Color.Blue;
		//Gizmo.Draw.LineSphere( WorldPosition.WithZ( 2f ), Radius);

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

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

		if ( TimeSinceSpawn < 0.15f || TimeSinceSpawn > Lifetime - 0.33f || _damageTimes.ContainsKey( other ) )
			return;

		if ( other is Player player )
		{
			if ( !player.IsDead && !player.IsInTheAir )
			{
				var acidHealPercent = player.GetSyncStat(PlayerStat.AcidHealHpPercent);

				if ( acidHealPercent > 0f && (player.Health / player.GetSyncStat( PlayerStat.MaxHp )) < acidHealPercent )
				{
					player.HealRpc( Damage, playSfx: true );

					player.HighlightPerkRpc( PerkManager.TypeToIdentity( TypeLibrary.GetType( typeof( PerkAcidHeal ) ) ) );
				}
				else
				{
					Vector2 dir = (Position2D - player.Position2D).LengthSquared > Manager.TOUCH_DIST_REQUIRED_SQR
						? (player.Position2D - Position2D).Normal
						: Utils.GetRandomVector();

					player.DamageRpc( Damage, DamageType.Acid, player.Position2D, dir, upwardAmount: Game.Random.Float( 0f, 0.2f ), force: 0f, ragdollForce: 0f, EnemySource, enemyType: EnemyType );
				}

				_damageTimes.Add( other, Time.Now );
			}
		}
		else if ( other is Enemy enemy )
		{
			if ( !enemy.IsDying && !enemy.IsInTheAir )
			{
				Vector2 dir = (Position2D - enemy.Position2D).LengthSquared > Manager.TOUCH_DIST_REQUIRED_SQR
						? (enemy.Position2D - Position2D).Normal
						: Utils.GetRandomVector();

				var hitPos = enemy.Position2D - dir * enemy.Radius;

				var shouldFlinch = Damage < enemy.MaxHealth * 0.05f ? false : true;
				enemy.DamageRpc( Damage, PlayerSource, DamageType.Acid, new Vector3( hitPos.x, hitPos.y, 10f ), force: Vector2.Zero, isCrit: false, shouldFlinch );

				_damageTimes.Add( other, Time.Now );
			}
		}
	}
}