Perk class that grants temporary attack and reload speed buffs on kills. It tracks kill timestamps in a queue, applies stacked multiplicative buffs up to a cap, updates display text/cooldown, and expires stacks after a duration.
using System;
[Perk( Rarity.Epic, locked: true, alwaysOfferDebug: false )]
public class PerkKillAttackSpeed : Perk
{
private Queue<float> _killTimes = new();
private enum Mod { AttackReloadSpeed, MaxBuff, Time };
static PerkKillAttackSpeed()
{
Register<PerkKillAttackSpeed>(
name: "Bloodlust",
imagePath: "textures/icons/vector/kill_attack_speed.png",
description: level => $"+{GetValue( level, Mod.AttackReloadSpeed, true ).ToString( "0.#" )}% attack/reload speed for\n{GetValue( level, Mod.Time ).ToString( "0.#" )}s on kill\n(max: {(int)GetValue( level, Mod.MaxBuff, true )}%)",
upgradeDescription: level => $"+{GetValue( level - 1, Mod.AttackReloadSpeed, true ).ToString( "0.#" )}%→{GetValue( level, Mod.AttackReloadSpeed, true ).ToString( "0.#" )}% attack/reload speed for\n{GetValue( level - 1, Mod.Time ).ToString( "0.#" )}→{GetValue( level, Mod.Time ).ToString( "0.#" )}s on kill\n(max: {(int)GetValue( level - 1, Mod.MaxBuff, true )}%→{(int)GetValue( level, Mod.MaxBuff, true )}%)"
);
}
public override void Start()
{
base.Start();
ShouldUpdate = true;
DisplayCooldownColor = new Color( 0.2f, 0.3f, 1f, 3f );
HighlightColor = new Color( 0.6f, 0.6f, 1f );
HighlightDuration = 0.15f;
HighlightOpacity = 1f;
}
public override void Refresh()
{
base.Refresh();
}
public override void Update( float dt )
{
base.Update( dt );
if ( _killTimes.Count > 0 )
{
if ( Time.Now > _killTimes.First() + GetValue( Level, Mod.Time ) )
{
_killTimes.Dequeue();
RefreshBuffs();
}
}
float maxPercent = GetValue( Level, Mod.MaxBuff, true );
float buffPercent = MathF.Min( _killTimes.Count * GetValue( Level, Mod.AttackReloadSpeed, true ), maxPercent );
DisplayText = _killTimes.Count > 0 ? $"{buffPercent}%" : " ";
DisplayCooldown = Utils.Map( buffPercent, 0f, maxPercent, 0f, 1f );
// todo: instead use displaycooldown to show time left on buff
//DebugOverlay.Text($"{Player.Stats[PlayerStat.AttackSpeed]}, {Player.Stats[PlayerStat.ReloadSpeed]}, {maxPercent}", Player.Position, 0f, float.MaxValue);
}
private static float GetValue( int level, Mod mod, bool isPercent = false )
{
switch ( mod )
{
case Mod.AttackReloadSpeed:
default:
return isPercent
? 1.5f + 0.5f * level
: 0.015f + 0.005f * level;
case Mod.MaxBuff:
return isPercent
? 10f + 20f * level
: 0.10f + 0.20f * level;
case Mod.Time:
return 1.5f + 0.5f * level;
}
}
public override void OnKill( Enemy enemy, DamageType damageType, bool countsAsKill )
{
base.OnKill( enemy, damageType, countsAsKill );
if ( !countsAsKill ) return;
_killTimes.Enqueue( Time.Now );
RefreshBuffs();
Highlight();
}
void RefreshBuffs()
{
float max = 1f + GetValue( Level, Mod.MaxBuff );
float speed = MathF.Min( 1f + _killTimes.Count * GetValue( Level, Mod.AttackReloadSpeed ), max );
Player.Modify( this, PlayerStat.AttackSpeed, speed, ModifierType.Mult );
Player.Modify( this, PlayerStat.ReloadSpeed, speed, ModifierType.Mult );
}
}