A legendary perk that modifies player bullet damage based on a timer digit and applies self-damage every minute. It updates each frame to read the seconds digit, adds that digit to BulletDamage while showing UI highlights, and deals configured HP loss on minute ticks.
using System;
using Sandbox;
[Perk( Rarity.Legendary, locked: true, minUnlocksReq: 6, alwaysOfferDebug: false, IncludedCategories = new[] { PerkCategory.SelfDmg })]
public class PerkTimerDigitDamage : Perk
{
private enum Mod { HpLost };
private int _currDigit;
static PerkTimerDigitDamage()
{
Register<PerkTimerDigitDamage>(
name: "On The Clock",
imagePath: "textures/icons/vector/digit_damage.png",
description: level => $"Increase bullet dmg by\nthe 2nd-last timer digit\n-{(int)GetValue( level, Mod.HpLost )} hp every new minute",
upgradeDescription: level => $"Increase bullet dmg by\nthe 2nd-last timer digit\n-{(int)GetValue( level - 1, Mod.HpLost )}→-{(int)GetValue( level, Mod.HpLost )} hp every new minute"
);
}
public override void Start()
{
base.Start();
ShouldUpdate = true;
_currDigit = -1;
}
public override void Refresh()
{
base.Refresh();
}
public override void Update( float dt )
{
base.Update( dt );
var manager = Manager.Instance;
TimeSpan time = manager.HasSpawnedBoss
? TimeSpan.FromSeconds( manager.ElapsedTime.Relative )
: TimeSpan.FromSeconds( manager.BossSpawnTime ) - TimeSpan.FromSeconds( manager.ElapsedTime.Relative );
string str = time.ToString( @"ss" );
int dmgDigit;
if ( int.TryParse( str.Substring( 0, 1 ), out dmgDigit ) )
{
if ( dmgDigit != _currDigit )
{
DisplayText = $"+{dmgDigit}";
Player.Modify( this, PlayerStat.BulletDamage, (float)dmgDigit, ModifierType.Add );
_currDigit = dmgDigit;
HighlightColor = new Color( 0.4f, 0.4f, 1f );
HighlightDuration = 0.25f;
HighlightOpacity = 1.25f;
Highlight();
}
DisplayCooldown = 1f - (dmgDigit / 6f);
DisplayCooldownColor = dmgDigit == (manager.HasSpawnedBoss ? 5 : 0) ? new Color( 1f, 0.3f, 0.3f, Utils.Map( Utils.FastSin( RealTime.Now * 24f ), -1f, 1f, 0f, 2f ) ) : new Color( 1f, 0.5f, 0.5f, Utils.Map( dmgDigit, 1, 4, 0.35f, 0.8f ) );
}
// todo: check if this works correctly after boss spawns
// todo: countdown sfx
}
private static float GetValue( int level, Mod mod, bool isPercent = false )
{
switch ( mod )
{
case Mod.HpLost:
default:
switch ( level )
{
case 1: default: return 50;
case 2: return 20;
}
}
}
public override void OnSetMinute( int minute )
{
base.OnSetMinute( minute );
if ( Player.IsDead )
return;
var damage = GetValue( Level, Mod.HpLost );
if ( damage > 0f )
{
var hitPos = Player.Position2D;
Player.Damage( damage, DamageType.Self, hitPos, Utils.GetRandomVector(), upwardAmount: 0f, force: 0f, ragdollForce: 1.5f, enemySource: null , enemyType: EnemyType.None );
//Manager.Instance.PlaySfxNearbyRpc( "zombie.attack.player", Player.Position2D, pitch: Utils.Map( Level, 1, 3, 1.4f, 1.2f ) * Game.Random.Float( 0.95f, 1.05f ), volume: 0.8f, maxDist: 200f );
// todo: needs louder sfx
HighlightColor = new Color( 1f, 0.2f, 0.5f );
HighlightDuration = 0.5f;
HighlightOpacity = 5f;
Highlight();
}
}
}