A game perk class 'PerkBerserk' that gives the player a temporary berserk state after dashing. It modifies attack and reload speed, damage reduction, forces movement/aim toward nearest enemy, updates UI display and plays a nearby sound and overlay while active.
using Sandbox;
using System;
[Perk( Rarity.Mythic, locked: true, alwaysOfferDebug: false )]
public class PerkBerserk : Perk
{
private enum Mod { Speed, Time, DamageReduction };
private TimeSince _timeSinceStart;
private TimeSince _timeSinceCheck;
static PerkBerserk()
{
Register<PerkBerserk>(
name: "Berserker",
imagePath: "textures/icons/vector/berserk.png",
description: level => $"On dash, lose control for {GetValue( level, Mod.Time ).ToString("0.#")}s with +{GetValue( level, Mod.Speed, true )}% attack/reload speed\nand [+]-{GetValue( level, Mod.DamageReduction, true )}%[/+] dmg taken",
upgradeDescription: level => $"On dash, lose control for {GetValue( level - 1, Mod.Time ).ToString( "0.#" )}→{GetValue( level, Mod.Time ).ToString( "0.#" )}s with +{GetValue( level - 1, Mod.Speed, true )}→+{GetValue( level, Mod.Speed, true )}% attack/reload speed\nand [+]-{GetValue( level - 1, Mod.DamageReduction, true )}→-{GetValue( level, Mod.DamageReduction, true )}%[/+] dmg taken"
);
}
public override void Start()
{
base.Start();
ShouldUpdate = true;
DisplayCooldownColor = new Color( 1f, 0.5f, 0.4f, 3f );
HighlightColor = new Color( 1f, 1f, 1f );
HighlightDuration = 0.15f;
HighlightOpacity = 1f;
}
public override void Refresh()
{
base.Refresh();
}
public override void Update( float dt )
{
base.Update( dt );
DisplayCooldown = Utils.Map( _timeSinceStart, 0f, GetValue( Level, Mod.Time ), 1f, 0f );
// todo: vfx
//foreach ( var renderer in Renderers )
// renderer.Tint = Color.Lerp( Color.White, Color.Red, Utils.Map( Utils.FastSin( Time.Now * 30f ), -1f, 1f, 0f, 1f ) );
if ( _timeSinceStart > GetValue( Level, Mod.Time ) )
{
DisableBerserk();
}
else
{
if( _timeSinceCheck > 0.2f )
{
var closestEnemy = Manager.Instance.GetClosestEnemy( Player.Position2D );
var dir = closestEnemy.IsValid() ? ( closestEnemy.Position2D - Player.Position2D).Normal : Utils.GetRandomVector();
Player.MoveVector = dir;
Player.AimDir = dir;
_timeSinceCheck = 0f;
}
//Player.MoveInputPercent = 1f;
Player.MoveInputPercent = Input.AnalogMove.Length;
DisplayTextColor = Color.Lerp( Color.White, Color.Red, 0.5f + Utils.FastSin( _timeSinceStart * 32f ) * 0.5f );
IconScale = 1f + (Utils.FastSin( RealTime.Now * 26 ) * 0.05f);
IconAngleOffset = (Utils.FastSin( RealTime.Now * 32f ) * 2f);
}
}
public override void OnDashStarted( Vector2 dir )
{
base.OnDashStarted( dir );
ShouldUpdate = true;
_timeSinceStart = 0f;
EnableBerserk();
}
void EnableBerserk()
{
float speed = 1f + GetValue( Level, Mod.Speed );
Player.Modify( this, PlayerStat.AttackSpeed, speed, ModifierType.Mult );
Player.Modify( this, PlayerStat.ReloadSpeed, speed, ModifierType.Mult );
Player.Modify( this, PlayerStat.DamageReductionPercent, GetValue( Level, Mod.DamageReduction ), ModifierType.Add );
Player.Modify( this, PlayerStat.IsBerserk, 1f, ModifierType.Add );
DisplayText = $"+{GetValue( Level, Mod.Speed, true )}%";
Highlight();
Manager.Instance.PlaySfxNearbyRpc( "berserk", Player.Position2D, pitch: Game.Random.Float( 1.2f, 1.5f ), volume: 0.9f, maxDist: 320f );
Manager.Instance.OverlayEffects[OverlayEffectsType.Berserk].Enabled = true;
}
void DisableBerserk()
{
ShouldUpdate = false;
Player.StopModifying( this, PlayerStat.AttackSpeed );
Player.StopModifying( this, PlayerStat.ReloadSpeed );
Player.StopModifying( this, PlayerStat.DamageReductionPercent );
Player.StopModifying( this, PlayerStat.IsBerserk );
DisplayText = " ";
Manager.Instance.OverlayEffects[OverlayEffectsType.Berserk].Enabled = false;
}
private static float GetValue( int level, Mod mod, bool isPercent = false )
{
switch ( mod )
{
case Mod.Speed:
default:
return isPercent
? 60f + 20f * level
: 0.6f + 0.2f * level;
case Mod.Time:
return 1f + 1f * level;
// todo: might be too long of time, leading to perma-berserk and being unkillable?
case Mod.DamageReduction:
return isPercent
? 50f + 10f * level
: 0.50f + 0.10f * level;
}
}
public override void OnDie()
{
base.OnDie();
if ( Player.Stats[PlayerStat.IsBerserk] > 0f )
DisableBerserk();
Manager.Instance.OverlayEffects[OverlayEffectsType.Berserk].Enabled = false;
}
// todo: make sure other perks remove things that they need to
public override void Remove( bool restart = false )
{
base.Remove( restart );
if ( Player.Stats[PlayerStat.IsBerserk] > 0f )
DisableBerserk();
}
}