perks/PerkShieldBreakAoe.cs

A rare AoE perk called Volatile Shield that triggers when the player's shield breaks. It spawns a visual ring and applies scaled area damage and knockback to nearby enemies, with damage value increasing by level.

NetworkingFile Access
using System;
using Sandbox;
using Sandbox.UI;

[Perk( Rarity.Rare, includedAtStart: false, locked: true, minUnlocksReq: 2, alwaysOfferDebug: false, IncludedCategories = new[] { PerkCategory.Aoe })]
public class PerkShieldBreakAoe : Perk
{
	private const float DAMAGE_RADIUS = 140f;

	private enum Mod { Damage };


	static PerkShieldBreakAoe()
	{
		Register<PerkShieldBreakAoe>(
			name: "Volatile Shield",
			imagePath: "textures/icons/vector/shield_break_damage.png",
			description: level => $"Do [+]{GetValue( level, Mod.Damage )}[/+] dmg to nearby enemies\nwhen your shield breaks",
			upgradeDescription: level => $"Do {GetValue( level - 1, Mod.Damage )}→{GetValue( level, Mod.Damage )} dmg to nearby enemies\nwhen your shield breaks"
		);
	}

	public override void Start()
	{
		base.Start();

		HighlightColor = new Color( 0.5f, 0.5f, 1f );
		HighlightDuration = 0.25f;
		HighlightOpacity = 3f;
	}

	public override void Refresh()
	{
		base.Refresh();

	}

	private static float GetValue( int level, Mod mod, bool isPercent = false )
	{
		switch ( mod )
		{
			case Mod.Damage:
			default:
				return 5f + 10f * level;
		}
	}

	public override void OnLoseShield()
	{
		float damage = GetValue( Level, Mod.Damage );
		float radius = DAMAGE_RADIUS * Player.Stats[PlayerStat.RadiusMultiplier];
		Vector2 pos = Player.Position2D;

		Manager.Instance.SpawnRingRpc( pos, radius, new Color( 1f, 1f, 0f, 0.6f ), lifetime: Game.Random.Float( 0.25f, 0.3f ), path: "ring_spiky_2" );

		// todo: replace with Manager.DamageInRadius or something
		foreach ( var enemy in Manager.Instance.GetNearbyEnemies( pos, radius ) )
		{
			if ( enemy.IsDying || enemy.IsInTheAir || (enemy.IsSpawning && !enemy.AlmostFinishedSpawning) )
				continue;

			Vector2 dir = (enemy.Position2D - pos).LengthSquared > Manager.TOUCH_DIST_REQUIRED_SQR
						? (enemy.Position2D - pos).Normal
						: Utils.GetRandomVector();

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

			var dist_sqr = (enemy.Position2D - pos).LengthSquared;
			var percent = Utils.Map( dist_sqr, 0f, MathF.Pow( radius, 2f ), 1f, 0.2f, EasingType.QuadIn );

			var shouldFlinch = percent > 0.7f;
			enemy.DamageRpc( damage * percent, Player, DamageType.Aoe, hitPos, dir * damage * 0.4f, isCrit: false, shouldFlinch );
		}

		Highlight();
	}
}