A Perk class for a game that grants a chance to reload 1 ammo when punching the same enemy repeatedly. It tracks the last hit enemy and consecutive punches, maps those to a chance between starting and final values, triggers a reload on success, and updates visual highlights and display text.
using System;
using Sandbox;
[Perk( Rarity.Uncommon, includedAtStart: false, locked: true, minUnlocksReq: 2, alwaysOfferDebug: false )]
public class PerkPunchReload : Perk
{
private enum Mod { StartingChance, FinalChance, NumStacks };
private Enemy _lastHitEnemy;
private int _numConsecutiveHits;
static PerkPunchReload()
{
Register<PerkPunchReload>(
name: "Relentless Fists",
imagePath: "textures/icons/vector/punch_reload.png",
description: level => $"{GetValue( level, Mod.StartingChance, true ).ToString( "0.#" )}% chance to reload 1 ammo on punching the same enemy\n(up to {(int)GetValue( level, Mod.FinalChance, true )}% chance\nat {(int)GetValue( level, Mod.NumStacks )} punches)",
upgradeDescription: level => $"{GetValue( level - 1, Mod.StartingChance, true ).ToString( "0.#" )}%→{GetValue( level, Mod.StartingChance, true ).ToString( "0.#" )}% chance to reload 1 ammo on punching the same enemy\n(up to {(int)GetValue( level - 1, Mod.FinalChance, true )}→{(int)GetValue( level, Mod.FinalChance, true )}% chance\nat {(int)GetValue( level - 1, Mod.NumStacks )}→{(int)GetValue( level, Mod.NumStacks )} punches)",
descriptionLineHeight: 9.5f
);
}
public override void Start()
{
base.Start();
DisplayCooldownColor = new Color( 0.4f, 0.4f, 1f, 0.5f );
}
public override void Refresh()
{
base.Refresh();
}
public override void OnDamageEnemy( Enemy enemy, float damage, DamageType damageType, Vector2 dir, bool isCrit )
{
base.OnDamageEnemy( enemy, damage, damageType, dir, isCrit );
if( damageType != DamageType.Punch )
return;
if( enemy != _lastHitEnemy )
{
_lastHitEnemy = enemy;
_numConsecutiveHits = 0;
HighlightColor = new Color( 1f, 0f, 0f );
HighlightDuration = 0.25f;
HighlightOpacity = 2f;
Highlight();
}
else
{
var maxStacks = (int)GetValue( Level, Mod.NumStacks );
_numConsecutiveHits = Math.Min( _numConsecutiveHits + 1, maxStacks );
var chance = Utils.Map( _numConsecutiveHits, 0, maxStacks, GetValue( Level, Mod.StartingChance ), GetValue( Level, Mod.FinalChance ) );
if ( Game.Random.Float( 0f, 1f ) < chance )
{
Player.ReloadAmmoAmount( 1 );
HighlightColor = new Color( 0.75f, 0.75f, 1f );
HighlightDuration = 0.15f;
HighlightOpacity = 2f;
Highlight();
}
}
RefreshText();
}
void RefreshText()
{
if ( _numConsecutiveHits == 0 )
DisplayText = " ";
else
DisplayText = $"{(int)Math.Round( Utils.Map( _numConsecutiveHits, 1, (int)GetValue( Level, Mod.NumStacks ), GetValue( Level, Mod.StartingChance, true ), GetValue( Level, Mod.FinalChance, true ) ) )}%";
DisplayCooldown = Utils.Map( _numConsecutiveHits, 0, (int)GetValue( Level, Mod.NumStacks ), 0f, 1f );
}
private static float GetValue( int level, Mod mod, bool isPercent = false )
{
switch ( mod )
{
case Mod.StartingChance:
default:
return isPercent
? 4f + 1f * level
: 0.04f + 0.01f * level;
case Mod.FinalChance:
return isPercent
? 20f + 10f * level
: 0.20f + 0.10f * level;
case Mod.NumStacks:
return 32 - level * 2;
}
}
}