A perk component named PerkSpear that charges over several shots and then spawns a piercing spear prefab. It tracks shots, displays cooldown/countdown text, computes spear damage per pierce from player max HP, clones and initializes a spear GameObject, network-spawns it, plays an SFX and resets the charge.
using System;
using Sandbox;
[Perk( Rarity.Epic, locked: true, minUnlocksReq: 5, alwaysOfferDebug: false )]
public class PerkSpear : Perk
{
private enum Mod { NumShotsRequired, HpPercentPerPierce, BulletDamage };
private int _shotCounter;
static PerkSpear()
{
Register<PerkSpear>(
name: "Spear Thrower",
imagePath: "textures/icons/vector/spear.png",
description: level => $"Every {GetValue( level, Mod.NumShotsRequired )} shots, launch a\npiercing spear that gains dmg equal to +{GetValue( level, Mod.HpPercentPerPierce, true ).ToString( "0.#" )}% of your max hp\neach pierce",
upgradeDescription: level => $"Every {GetValue( level - 1, Mod.NumShotsRequired )}→{GetValue( level, Mod.NumShotsRequired )} shots, launch a piercing spear that gains dmg equal to {GetValue( level - 1, Mod.HpPercentPerPierce, true ).ToString("0.#")}%→{GetValue( level, Mod.HpPercentPerPierce, true ).ToString("0.#")}% of your max hp each pierce",
descriptionLineHeight: 10f
);
}
public override void Start()
{
base.Start();
DisplayCooldownColor = new Color( 1f, 1f, 0.3f, 0.6f );
HighlightColor = new Color( 0.6f, 0.6f, 1f );
HighlightDuration = 0.25f;
HighlightOpacity = 2f;
}
public override void Refresh()
{
base.Refresh();
DisplayText = $"{GetValue( Level, Mod.NumShotsRequired ) - _shotCounter}";
DisplayCooldown = Utils.Map( _shotCounter, 0f, GetValue( Level, Mod.NumShotsRequired ), 0f, 1f );
//Player.Modify( this, PlayerStat.BulletDamage, -GetValue( Level, Mod.BulletDamage ), ModifierType.Add );
}
private static float GetValue( int level, Mod mod, bool isPercent = false )
{
switch ( mod )
{
case Mod.NumShotsRequired:
default:
return 42 - 8 * level;
case Mod.HpPercentPerPierce:
return isPercent ? 0.5f + 0.5f * level : 0.005f + 0.005f * level;
//case Mod.BulletDamage:
// return 0.5f + 0.5f * level;
}
}
public override void OnShoot()
{
base.OnShoot();
_shotCounter++;
if ( _shotCounter >= GetValue( Level, Mod.NumShotsRequired ) )
{
Shoot();
}
else
{
// todo: maybe add a "clink" sfx for the final few shots, to give feedback that you're charging up the spear?
}
DisplayText = $"{GetValue( Level, Mod.NumShotsRequired ) - _shotCounter}";
DisplayCooldown = Utils.Map( _shotCounter, 0f, GetValue( Level, Mod.NumShotsRequired ), 0f, 1f );
}
public void Shoot()
{
var dir = Player.FacingDir;
var pos = Player.Position2D + dir * Player.BULLET_SPAWN_OFFSET;
var zPos = Player.WorldPosition.z + 50f * Player.Stats[PlayerStat.Scale];
var spearGo = GameObject.Clone( "prefabs/spear.prefab", new CloneConfig { StartEnabled = true, Transform = new Transform( new Vector3( pos.x, pos.y, -100f ), Rotation.From( 0f, -Utils.GetAngleDegreesFromVector( dir ), 0f ) ) } );
var spear = spearGo.Components.Get<Spear>( true );
spear.BaseZPos = zPos;
spear.Velocity = dir * 180f;
spear.Shooter = Player;
spear.Damage = 1f;
spear.DamagePerPierce = Player.Stats[PlayerStat.MaxHp] * GetValue( Level, Mod.HpPercentPerPierce );
spear.Lifetime = 2.8f;
spear.Init();
spearGo.NetworkSpawn( Player.Network.Owner );
Manager.Instance.PlaySfxNearbyRpc( "spike.thrust", pos, pitch: Game.Random.Float( 0.94f, 0.98f ), volume: 0.7f, maxDist: 400f );
_shotCounter = 0;
Highlight();
Player.DodgeDuckRpc( dir, time: Game.Random.Float( 0.1f, 0.15f ) );
}
}