A Perk component implementing an active reload mechanic. It spawns an active reload UI bar, tracks reload progress, allows early successful reloads with a timing window, applies reload speed modifier, and penalizes the player with health if mistimed.
using System;
using Sandbox;
[Perk( Rarity.Unique, locked: true, minUnlocksReq: 2, alwaysOfferDebug: false )]
public class PerkActiveReload : Perk
{
private enum Mod { HealthLost, ReloadSpeed };
private GameObject _activeReloadBar;
public bool IsVisible => IsReady && Player.IsReloading && !Player.IsChoosingLevelUpReward;
public float BarProgress { get; set; }
public float SectionStart { get; set; }
public float SectionWidth { get; set; }
public bool IsReady { get; set; }
public bool IsBarInSection => BarProgress >= SectionStart && BarProgress <= SectionStart + SectionWidth;
public float Duration { get; set; }
// todo: put UI on bottom when in 3rd person mode?
static PerkActiveReload()
{
Register<PerkActiveReload>(
name: "Active Reload",
imagePath: "textures/icons/vector/active_reload.png",
description: level => $"-{GetValue( level, Mod.ReloadSpeed, true )}% reload speed\nPress R to reload early\n-{GetValue( level, Mod.HealthLost )} hp on fail",
upgradeDescription: level => $"-{GetValue( level - 1, Mod.ReloadSpeed, true )}%→-{GetValue( level, Mod.ReloadSpeed, true )}% reload speed\nPress R to reload early\n-{GetValue( level - 1, Mod.HealthLost )}→-{GetValue( level, Mod.HealthLost )} hp on fail"
//description: level => $"Press R to reload early\n-{GetValue( level, Mod.HealthLost )} hp on fail",
//upgradeDescription: level => $"Press R to reload early\n-{GetValue( level - 1, Mod.HealthLost )}→-{GetValue( level, Mod.HealthLost )} hp on fail"
);
}
public override void Start()
{
base.Start();
_activeReloadBar = GameObject.Clone( "prefabs/active_reload_bar.prefab", new CloneConfig { StartEnabled = true, Parent = Player.GameObject } );
var arComponent = _activeReloadBar.GetComponent<ActiveReloadBar>();
arComponent.Player = Player;
arComponent.Perk = this;
ShouldUpdate = true;
}
public override void Refresh()
{
base.Refresh();
Player.Modify( this, PlayerStat.ReloadSpeed, GetValue( Level, Mod.ReloadSpeed ), ModifierType.Mult );
}
public override void Update( float dt )
{
base.Update( dt );
if ( !IsVisible )
return;
BarProgress = Player.ReloadProgress;
if( Input.Pressed("R") && !Manager.Instance.IsPaused )
{
float leniency = 0.008f;
if ( BarProgress > SectionStart - leniency && BarProgress < SectionStart + SectionWidth + leniency )
{
Player.ReloadInstantly();
HighlightColor = new Color( 0.7f, 0.7f, 1f );
HighlightDuration = 0.25f;
HighlightOpacity = 1.5f;
Highlight();
IconScale = Game.Random.Float( 1.1f, 1.2f );
IconAngleOffset = Game.Random.Float( 5f, 10f ) * (Game.Random.Int( 0, 1 ) == 0 ? -1f : 1f);
}
else
{
Manager.Instance.PlaySfxUI( "error2", pitch: 0.5f, volume: 0.95f );
Player.Damage( GetValue( Level, Mod.HealthLost ), DamageType.Self, Player.Position2D, Player.Velocity.Normal, upwardAmount: 0f, force: 0f, ragdollForce: 1f, enemySource: null, enemyType: EnemyType.None );
HighlightColor = new Color( 1f, 0.2f, 0.35f );
HighlightDuration = 0.35f;
HighlightOpacity = 3f;
Highlight();
IconScale = Game.Random.Float( 1.2f, 1.3f );
IconAngleOffset = Game.Random.Float( 10f, 20f ) * (Game.Random.Int( 0, 1 ) == 0 ? -1f : 1f);
}
IsReady = false;
}
}
public override void OnStartReload()
{
base.OnStartReload();
if ( Player.IsChoosingLevelUpReward )
return;
BarProgress = 0f;
SectionWidth = Game.Random.Float(0.035f, 0.195f);
SectionStart = Game.Random.Float( 0.1f, 1f - (SectionWidth + 0.1f) );
IsReady = true;
}
public override void OnFinishReload()
{
base.OnFinishReload();
IsReady = false;
}
public override void OnPlayerLevelUp()
{
base.OnPlayerLevelUp();
IsReady = false;
}
public override void Remove( bool restart = false )
{
base.Remove( restart );
_activeReloadBar.Destroy();
}
private static float GetValue( int level, Mod mod, bool isPercent = false )
{
switch ( mod )
{
case Mod.HealthLost:
default:
return 10f;
case Mod.ReloadSpeed:
return isPercent
? 15f * level
: 1f - (0.15f * level);
}
}
}