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