perks/PerkMoveToSpawnFire.cs

Perk component 'PerkMoveToSpawnFire'. It tracks player movement distance and when the player moves a threshold distance it spawns a ground fire, plays a nearby SFX, triggers a dodge/duck RPC on the player, and updates perk UI state (highlight, icon scale/angle, cooldown).

NetworkingFile Access
using System;
using Sandbox;

[Perk( Rarity.Legendary, locked: true, alwaysOfferDebug: false, IncludedCategories = new[] { PerkCategory.Aoe, PerkCategory.Fire })]
public class PerkMoveToSpawnFire : Perk
{
	private enum Mod { RequiredDist };

	private float _currDist;


	static PerkMoveToSpawnFire()
	{
		Register<PerkMoveToSpawnFire>(
			name: "Firewalker",
			imagePath: "textures/icons/vector/move_to_spawn_fire.png",
			description: level => $"Start a fire when you move {GetValue( level, Mod.RequiredDist, true ).ToString( "0.#" )}m",
			upgradeDescription: level => $"Start a fire when\nyou move {GetValue( level - 1, Mod.RequiredDist, true ).ToString("0.#")}→{GetValue( level, Mod.RequiredDist, true ).ToString("0.#")}m"
		);
	}

	public override void Start()
	{
		base.Start();

		ShouldUpdate = true;

		DisplayCooldownColor = new Color( 1f, 0.5f, 1f, 0.5f );

		HighlightColor = new Color( 0.8f, 0.8f, 1f );
		HighlightDuration = 0.2f;
		HighlightOpacity = 2f;
	}

	public override void Refresh()
	{
		base.Refresh();

	}

	private static float GetValue( int level, Mod mod, bool isPercent = false )
	{
		switch ( mod )
		{
			case Mod.RequiredDist:
			default:
				return (7.5f - 2.5f * level) * (isPercent ? 1f : Utils.Meter2Unit);
		}
	}

	public override void Update( float dt )
	{
		base.Update( dt );
		//DisplayText = $"{string.Format("{0:0.0}", GetValue(Level, Mod.RequiredDist) - _currDist)}";

		if ( Player.TotalVelocity.LengthSquared > 0f )
		{
			_currDist += Player.TotalVelocity.Length * dt;
			if ( _currDist > GetValue( Level, Mod.RequiredDist ) )
			{
				var dir = Player.TotalVelocity.Normal;
				var pos = Player.Position2D - dir * 20f;

				Manager.Instance.SpawnFireGroundRpc(
					pos,
					Player,
					enemySource: null,
					enemyType: EnemyType.None,
					damage: Player.Stats[PlayerStat.FireDamage],
					lifetime: Player.Stats[PlayerStat.FireLifetime],
					spreadChance: Player.Stats[PlayerStat.FireSpreadChance],
					canStack: Player.Stats[PlayerStat.FireDmgStack] > 0f,
					scale: Player.Stats[PlayerStat.RadiusMultiplier],
					colorA: Color.Red,
					colorB: Color.Yellow
				);

				Manager.Instance.PlaySfxNearbyRpc( "burn", Player.Position2D, pitch: Game.Random.Float( 0.95f, 1f ), volume: 0.9f, maxDist: 300f );

				_currDist = 0f;
				DisplayCooldown = 0f;

				Highlight();

				IconScale = Game.Random.Float( 1.2f, 1.3f );
				IconAngleOffset = Game.Random.Float( 10f, 20f ) * (Game.Random.Int( 0, 1 ) == 0 ? -1f : 1f);

				Player.DodgeDuckRpc( dir, time: Game.Random.Float( 0.15f, 0.25f ) );
			}
			else
			{
				DisplayCooldown = Utils.Map( _currDist, 0f, GetValue( Level, Mod.RequiredDist ), 0f, 1f );
			}
		}
	}
}