An Epic rarity perk class called PerkNextShotDamageMult that accumulates a damage multiplier for the player's next shot every RECHARGE_TIME seconds. It increments a stored multiplier up to a cap, applies it to the player's ShotDamageMult stat, shows UI text and cooldown, plays a sound and a short visual scale/hold animation, and clears the multiplier when the player shoots.
using System;
using Sandbox;
[Perk( Rarity.Epic, locked: true, minUnlocksReq: 1, alwaysOfferDebug: false )]
public class PerkNextShotDamageMult : Perk
{
private enum Mod { DamageMult, MaxDamageMult };
private const float RECHARGE_TIME = 3f;
private float _timer;
private float _currentDamageMult;
private float _maxDamageMult;
static PerkNextShotDamageMult()
{
Register<PerkNextShotDamageMult>(
name: "Pumped Up",
imagePath: "textures/icons/vector/next_bullet_damage_mult.png",
description: level => $"Every {RECHARGE_TIME}s, your next shot\ngets +{GetValue( level, Mod.DamageMult, true )}% dmg\n(max: [n]+{(int)GetValue( level, Mod.MaxDamageMult, true )}%[/n])",
upgradeDescription: level => $"Every {RECHARGE_TIME}s, your next shot\ngets +{GetValue( level - 1, Mod.DamageMult, true )}%→+{GetValue( level, Mod.DamageMult, true )}% dmg\n(max: [n]+{(int)GetValue( level, Mod.MaxDamageMult, true )}%[/n])"
);
}
public override void Start()
{
base.Start();
ShouldUpdate = true;
HighlightColor = new Color( 0.6f, 0.6f, 1f );
HighlightDuration = 0.2f;
HighlightOpacity = 0.8f;
}
public override void Refresh()
{
base.Refresh();
_maxDamageMult = GetValue( Level, Mod.MaxDamageMult ) - 1f;
}
private static float GetValue( int level, Mod mod, bool isPercent = false )
{
switch ( mod )
{
case Mod.DamageMult:
default:
return isPercent
? 50 + 50 * level
: 1.5f + 0.5f * level;
case Mod.MaxDamageMult:
return isPercent
? 1000f
: 11f;
}
}
public override void Update( float dt )
{
base.Update( dt );
_timer += dt;
if ( _timer > RECHARGE_TIME )
{
_timer -= RECHARGE_TIME;
if ( _currentDamageMult < _maxDamageMult )
{
_currentDamageMult = Math.Min( _currentDamageMult + (GetValue( Level, Mod.DamageMult ) - 1f), _maxDamageMult );
Player.Modify( this, PlayerStat.ShotDamageMult, 1f + _currentDamageMult, ModifierType.Mult );
DisplayText = $"+{_currentDamageMult * 100f:0}%";
Manager.Instance.PlaySfxNearby( "pumped_up", Player.Position2D, pitch: Game.Random.Float( 0.9f, 1.1f ), volume: 1.8f, maxDist: 200f );
if ( !(Player.Stats[PlayerStat.PunchBullets] > 0f) )
Player.SetHoldType( Sandbox.Citizen.CitizenAnimationHelper.HoldTypes.Pistol );
Player.ScaleHeightRpc( amount: 1.5f, time: Game.Random.Float( 0.05f, 0.06f ) );
Highlight();
}
}
DisplayCooldown = _currentDamageMult < _maxDamageMult
? Utils.Map( _timer, 0f, RECHARGE_TIME, 0f, 1f )
: 1f;
}
public override void OnShoot()
{
base.OnShoot();
if ( _currentDamageMult <= 0f )
return;
DisplayText = " ";
Player.StopModifying( this, PlayerStat.ShotDamageMult );
_currentDamageMult = 0f;
// todo: bullet spawn position should be different
}
}