perks/PerkBulletBounceCopy.cs

A Perk class for a game that can clone a bullet on its first bounce with a chance that scales by level. It registers perk metadata, adjusts highlight visuals, and on bullet bounce may spawn a cloned bullet with similar stats, visuals, and sound effects.

NetworkingFile Access
using System;
using Sandbox;

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

	static PerkBulletBounceCopy()
	{
		Register<PerkBulletBounceCopy>(
			name: "Scatterbounce",
			imagePath: "textures/icons/vector/bullet_bounce_copy.png",
			description: level => $"{(int)GetValue( level, Mod.Chance, true )}% chance for bullet-icon\nto clone on first bounce",
			upgradeDescription: level => $"{(int)GetValue( level - 1, Mod.Chance, true )}%→{(int)GetValue( level, Mod.Chance, true )}% chance for bullet-icon\nto clone on first bounce"
		);
	}

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

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

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

	}

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

		if ( (int)bullet.Stats[BulletStat.NumBouncing] != bullet.StartingNumBounce - 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.Stats[BulletStat.NumPiercing];
		b.Stats[BulletStat.NumBouncing] = bullet.StartingNumBounce;
		b.Velocity = dir * bullet.Velocity.Length;

		if ( bullet.Stats[BulletStat.ArcHeight] > 0f )
			b.SetupArc( bullet.Stats[BulletStat.ArcHeight], 0f );

		b.Stats[BulletStat.NumBouncing] -= 1;
		b.ShowBounce = b.Stats[BulletStat.NumBouncing] > 0f;

		// todo: cloned bullet should have same fire/freeze etc instead of random

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

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

		Manager.Instance.PlaySfxNearbyRpc( "bounce_copy", bullet.Position2D, pitch: Game.Random.Float( 1f, 1.2f ), 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
					? 3f + 10f * level
					: 0.03f + 0.10f * level;
		}
	}
}