perks/PerkBulletPierceCopy.cs

A legendary perk class named Splintershot that hooks into bullet pierce events. When a bullet pierces for the first time it can spawn a cloned bullet at a random angled direction, copy some pierce/bounce stats, play SFX and spawn a ring effect. It also defines UI metadata (name, icon, descriptions) and some highlight visuals.

NetworkingFile Access
using System;
using Sandbox;

[Perk( Rarity.Legendary, includedAtStart: false, locked: true, alwaysOfferDebug: false )]
public class PerkBulletPierceCopy : Perk
{
	private enum Mod { Chance };

	static PerkBulletPierceCopy()
	{
		Register<PerkBulletPierceCopy>(
			name: "Splintershot",
			imagePath: "textures/icons/vector/bullet_pierce_copy.png",
			description: level => $"{(int)GetValue( level, Mod.Chance, true )}% chance for bullet-icon to\nclone on first pierce",
			upgradeDescription: level => $"{(int)GetValue( level - 1, Mod.Chance, true )}%→{(int)GetValue( level, Mod.Chance, true )}% chance for bullet-icon to\nclone on first pierce"
		);
	}

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

		HighlightColor = new Color( 0.6f, 0.6f, 0.8f );
		HighlightDuration = 0.1f;
		HighlightOpacity = 2f;
	}

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

	}

	public override void OnBulletPierce( Bullet bullet, Thing other )
	{
		base.OnBulletPierce( bullet, other );

		if ( (int)bullet.Stats[BulletStat.NumPiercing] != bullet.StartingNumPierce - 1 )
			return;

		if ( Game.Random.Float( 0f, 1f ) > GetValue( Level, Mod.Chance ) )
			return;

		var dmg = bullet.Stats[BulletStat.Damage];
		var dir = Utils.RotateVector( bullet.Velocity.Normal, Game.Random.Float( 15f, 40f ) * (Game.Random.Int( 0, 1 ) == 0 ? -1f : 1f) );
		var b = Player.SpawnBullet( bullet.Position2D, dir, dmg, isFromClip: false, bulletType: bullet.BulletType );
		b.Stats[BulletStat.NumPiercing] = bullet.StartingNumPierce;
		b.Stats[BulletStat.NumBouncing] = bullet.Stats[BulletStat.NumBouncing];

		b.Stats[BulletStat.NumPiercing] -= 1;
		b.ShowPierce = b.Stats[BulletStat.NumPiercing] > 0f;

		if ( other.IsValid() )
			b.HitThings.Add( other );

		Manager.Instance.SpawnRingRpc( bullet.Position2D, Game.Random.Float( 8f, 12f ), new Color( 0.5f, 0.5f, 1f, 0.5f ), lifetime: Game.Random.Float( 0.3f, 0.4f ), path: "ring_spiky" );

		Manager.Instance.PlaySfxNearbyRpc( "bounce_copy", bullet.Position2D, pitch: Game.Random.Float( 1.8f, 2.0f ), volume: 2.2f, maxDist: 350f );

		//Highlight();
	}

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