perks/PerkJumpLandDamage.cs

A Perk class for a game, named Seismic Stomp, that triggers when the player lands. It computes damage and stun values per level, spawns visual/sound effects, finds nearby enemy objects via scene sphere trace, and applies a jump effect and damage/stun RPCs to each valid enemy.

Networking
using System;
using Sandbox;

[Perk( Rarity.Mythic, includedAtStart: false, locked: true, minUnlocksReq: 7, alwaysOfferDebug: false )]
public class PerkJumpLandDamage : Perk
{
	private enum Mod { Damage, StunTime };

	public override float ImportanceMultiplier => 1.15f;

	static PerkJumpLandDamage()
	{
		Register<PerkJumpLandDamage>(
			name: "Seismic Stomp",
			imagePath: "textures/icons/vector/jump_land_damage.png",
			description: level => $"Do {GetValue( level, Mod.Damage )} dmg and stun enemies for {GetValue( level, Mod.StunTime ).ToString("0.#")}s when you land",
			upgradeDescription: level => $"Do {GetValue( level - 1, Mod.Damage )}→{GetValue( level, Mod.Damage )} dmg and stun enemies for {GetValue( level - 1, Mod.StunTime ).ToString( "0.#" )}→{GetValue( level, Mod.StunTime ).ToString( "0.#" )}s when you land"
		);
	}

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

	}

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

	}

	private static float GetValue( int level, Mod mod, bool isPercent = false )
	{
		switch ( mod )
		{
			case Mod.Damage:
			default:
				return 4f + 3f * level;
			case Mod.StunTime:
				return 0.3f + 0.2f * level;
		}
	}

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

		float radius = PerkDashToJump.LAND_RADIUS * Player.Stats[PlayerStat.RadiusMultiplier];

		if( !(Player.Stats[PlayerStat.JumpNotDash] > 0f) )
		{
			Manager.Instance.SpawnRingRpc( Player.Position2D, radius * 0.95f, new Color( 1f, 0.8f, 0.5f, 0.3f ), 0.2f );
			Manager.Instance.PlaySfxNearbyRpc( "slam", Player.Position2D, pitch: Game.Random.Float( 0.95f, 1.05f ), volume: 1f, maxDist: 400f );
		}

		var jumpDamage = GetValue( Level, Mod.Damage );
		var jumpStunTime = GetValue( Level, Mod.StunTime );
		var distance = Utils.Map( Level, 1, 3, 80f, 150f );
		var height = Utils.Map( Level, 1, 3, 110f, 160f );
		var lifetime = Utils.Map( Level, 1, 3, 0.7f, 1.2f );

		var traceResults = Player.Scene.Trace.Sphere( radius, Player.Position2D, Player.Position2D ).WithAnyTags( "enemy" ).HitTriggersOnly().RunAll().ToList();
		foreach ( var tr in traceResults )
		{
			var gameObject = tr.GameObject;
			var enemy = gameObject.GetComponent<Enemy>();
			if ( !enemy.IsValid() || enemy.IsDying || enemy.IsInTheAir || enemy.IsSpawning )
				continue;

			Vector2 dir = (enemy.Position2D - Player.Position2D).Normal;

			if ( jumpDamage > 0f )
			{
				//var distance = Utils.Map(jumpDamage, ) 
				enemy.JumpRpc( enemy.Position2D + dir * distance * Game.Random.Float( 0.75f, 1.2f ), height: height * Game.Random.Float( 0.75f, 1.25f ), lifetime: lifetime * Game.Random.Float( 0.8f, 1.2f ) );
				enemy.DamageOnJumpFinishRpc( jumpDamage, Player, jumpStunTime );
			}
		}
	}
}