A Perk class for a game, named Deadly Accuracy. It tracks a bullet-hit streak and provides an added damage modifier per stacked shot, displays streak info and plays a fail SFX when a bullet icon hits the ground.
using System;
using Sandbox;
[Perk( Rarity.Epic, locked: true, alwaysOfferDebug: false )]
public class PerkBulletHitStreak : Perk
{
private enum Mod { BulletHitStreakDmg };
private const int MAX_STREAK = 100;
private TimeSince _timeSinceHitGroundSfx;
static PerkBulletHitStreak()
{
Register<PerkBulletHitStreak>(
name: "Deadly Accuracy",
imagePath: "textures/icons/vector/bullet_hit_streak.png",
description: level => $"+{GetValue( level, Mod.BulletHitStreakDmg ).ToString("0.###")} bullet dmg when you shoot\n(max: {MAX_STREAK} stacks)\nReset when bullet-icon hits the ground",
upgradeDescription: level => $"+{GetValue( level - 1, Mod.BulletHitStreakDmg ).ToString( "0.###" )}→{GetValue(level, Mod.BulletHitStreakDmg).ToString("0.###")} bullet dmg when\nyou shoot (max: {MAX_STREAK} stacks)\nReset when bullet-icon hits the ground"
);
}
public override void Start()
{
base.Start();
DisplayCooldownColor = new Color( 0.5f, 0.5f, 1f, 3f );
HighlightColor = new Color( 1f, 0f, 0f );
HighlightDuration = 0.25f;
HighlightOpacity = 2f;
}
public override void Refresh()
{
base.Refresh();
Player.Modify( this, PlayerStat.BulletHitStreakDmg, GetValue( Level, Mod.BulletHitStreakDmg ), ModifierType.Add );
}
public override void OnShoot()
{
RefreshDisplayText();
}
public override void OnBulletHitGround( Bullet bullet )
{
RefreshDisplayText();
// todo: halve stacks instead of canceling all buffs?
if( _timeSinceHitGroundSfx > 0.1f )
{
Manager.Instance.PlaySfxNearby( "bullet_hit_streak_fail", bullet.Position2D, pitch: Game.Random.Float( 1f, 1.2f ), volume: 1.3f, maxDist: 400f );
_timeSinceHitGroundSfx = 0f;
Highlight();
}
}
void RefreshDisplayText()
{
DisplayText = Player.Stats[PlayerStat.NumShotsWithoutBulletHitGround] > 0 ? string.Format( "{0:0.00}", MathF.Min( (int)Player.Stats[PlayerStat.NumShotsWithoutBulletHitGround], MAX_STREAK ) * GetValue( Level, Mod.BulletHitStreakDmg ) ) : " ";
DisplayCooldown = Utils.Map( Player.Stats[PlayerStat.NumShotsWithoutBulletHitGround], 0f, MAX_STREAK, 0f, 1f );
}
private static float GetValue( int level, Mod mod, bool isPercent = false )
{
switch ( mod )
{
case Mod.BulletHitStreakDmg:
default:
return 0.01f + level * 0.015f;
}
}
}