A player perk that grants temporary attack/reload/move speed buffs when the player rerolls, at the cost of some self-damage. It tracks queued reroll timestamps, applies multiplicative stat modifiers while active, shows cooldown/display text, and damages the player on each reroll.
using System;
using Sandbox;
[Perk( Rarity.Epic, locked: true, minUnlocksReq: 2, alwaysOfferDebug: false, IncludedCategories = new[] { PerkCategory.SelfDmg })]
public class PerkRerollAttackSpeed : Perk
{
private enum Mod { Speed, Time, LifeLoss };
private Queue<float> _rerollTimes = new();
private float _currStartTime;
static PerkRerollAttackSpeed()
{
Register<PerkRerollAttackSpeed>(
name: "Stim Injection",
imagePath: "textures/icons/vector/reroll_attack_speed.png",
description: level => $"-{(int)GetValue( level, Mod.LifeLoss )} hp and\n+{GetValue( level, Mod.Speed, true )}% attack/reload/move speed for {GetValue( level, Mod.Time ).ToString( "0.#" )}s\nwhen you reroll",
upgradeDescription: level => $"-{(int)GetValue( level - 1, Mod.LifeLoss )}→-{(int)GetValue( level, Mod.LifeLoss )} hp and +{GetValue( level - 1, Mod.Speed, true )}%→{GetValue( level, Mod.Speed, true )}% attack/reload/move speed for {GetValue( level - 1, Mod.Time ).ToString( "0.#" )}→{GetValue( level, Mod.Time ).ToString( "0.#" )}s when you reroll"
);
}
public override void Start()
{
base.Start();
ShouldUpdate = true;
DisplayCooldownColor = new Color( 0.6f, 0.6f, 1f, 3f );
HighlightColor = new Color( 0.6f, 0.6f, 1f );
HighlightDuration = 0.1f;
HighlightOpacity = 2f;
// todo: make this rarer and a bit more powerful
}
public override void Refresh()
{
base.Refresh();
Player.Modify( this, PlayerStat.RerollHealDisplay, -GetValue( Level, Mod.LifeLoss ), ModifierType.Add );
}
public override void Update( float dt )
{
base.Update( dt );
if ( _rerollTimes.Count > 0 )
{
var rerollTime = _rerollTimes.Peek();
if ( Time.Now > rerollTime + GetValue( Level, Mod.Time ) )
{
_rerollTimes.Dequeue();
RefreshBuffs();
_currStartTime = Time.Now;
DisplayCooldown = 0f;
}
else
{
DisplayCooldown = Utils.Map( Time.Now, _currStartTime, rerollTime + GetValue( Level, Mod.Time ), 1f, 0f );
}
}
float buffPercent = _rerollTimes.Count * GetValue( Level, Mod.Speed, true );
DisplayText = _rerollTimes.Count > 0 ? $"{buffPercent}%" : " ";
}
public override void OnRerollAfter()
{
base.OnRerollAfter();
if ( _rerollTimes.Count == 0 )
_currStartTime = Time.Now;
_rerollTimes.Enqueue( Time.Now );
RefreshBuffs();
// todo: different sfx?
Player.Damage( GetValue( Level, Mod.LifeLoss ), DamageType.Self, Player.Position2D, Utils.GetRandomVector(), upwardAmount: 0f, force: 0f, ragdollForce: 0.5f, enemySource: null, enemyType: EnemyType.None );
Highlight();
}
void RefreshBuffs()
{
float speed = 1f + _rerollTimes.Count * GetValue( Level, Mod.Speed );
Player.Modify( this, PlayerStat.AttackSpeed, speed, ModifierType.Mult );
Player.Modify( this, PlayerStat.ReloadSpeed, speed, ModifierType.Mult );
Player.Modify( this, PlayerStat.MoveSpeedMultiplier, speed, ModifierType.Mult );
//float dmgAdd = _rerollTimes.Count * GetValue( Level, Mod.BulletDmgAdd );
//Player.Modify( this, PlayerStat.BulletDamage, dmgAdd, ModifierType.Add );
}
private static float GetValue( int level, Mod mod, bool isPercent = false )
{
switch ( mod )
{
case Mod.Speed:
default:
return isPercent
? 10f + 5f * level
: 0.10f + 0.05f * level;
case Mod.Time:
return 3f + 1f * level;
//case Mod.BulletDmgAdd:
// return 0.5f + 0.3f * level;
case Mod.LifeLoss:
return 3f + 3f * level;
}
}
}