Perk component that damages the player when they move diagonally for a short accumulated time. It tracks analog movement, accumulates diagonal time, and applies self-damage and a visual highlight when a threshold is exceeded.
using System;
using Sandbox;
[Perk( Rarity.Unique, curse: true, alwaysOfferDebug: false, IncludedCategories = new[] { PerkCategory.SelfDmg, PerkCategory.OneHpLeft })]
public class CurseHurtMoveDiagonal : Perk
{
private enum Mod { HpLoss };
// Track diagonal movement over time
private float _accumulatedDiagonalTime;
private TimeSince _timeSinceHurt;
static CurseHurtMoveDiagonal()
{
Register<CurseHurtMoveDiagonal>(
name: "Angular Strain",
imagePath: "textures/icons/vector/curse_hurt_move_diagonal.png",
description: level => $"-{(int)GetValue( level, Mod.HpLoss )} hp while moving diagonally"
);
}
public override void Start()
{
base.Start();
ShouldUpdate = true;
_accumulatedDiagonalTime = 0f;
HighlightColor = new Color( 1f, 0.3f, 0.6f );
HighlightDuration = 0.75f;
HighlightOpacity = 4f;
}
public override void Refresh()
{
base.Refresh();
}
public override void Update( float dt )
{
base.Update( dt );
// Check if player is moving diagonally
var moveVector = Player.MoveVector;
var isMoving = moveVector.Length > 0.1f;
if ( isMoving )
{
// Normalize to check direction
var normalizedMove = Input.AnalogMove.Normal;
// Calculate how "diagonal" the movement is
// Pure cardinal directions (up/down/left/right) have abs(x) or abs(y) close to 1 and the other close to 0
// Diagonal movement has both abs(x) and abs(y) away from 0
var absX = MathF.Abs( normalizedMove.x );
var absY = MathF.Abs( normalizedMove.y );
// Diagonalness is high when both components are significant (away from pure cardinal)
// Using min of the two components - this is 0 for cardinal, ~0.707 for perfect diagonal
var diagonalness = MathF.Min( absX, absY );
// Consider it diagonal if the smaller component is > 0.3 (roughly 30+ degrees from cardinal)
var isDiagonal = diagonalness > 0.25f;
if ( isDiagonal )
{
_accumulatedDiagonalTime += dt;
}
else
{
// Decay when moving in cardinal directions
_accumulatedDiagonalTime *= (1f - dt * 2f);
}
}
else
{
// Decay when not moving
_accumulatedDiagonalTime *= (1f - dt * 2f);
}
var timeThreshold = 0.35f; // seconds of diagonal movement to trigger damage
if ( _accumulatedDiagonalTime > timeThreshold && _timeSinceHurt > 0.25f )
{
Player.Damage( GetValue( Level, Mod.HpLoss ), DamageType.Self, Player.Position2D, Player.MoveVector, upwardAmount: 0f, force: 0f, ragdollForce: 1f, enemySource: null, enemyType: EnemyType.None );
Highlight();
_accumulatedDiagonalTime -= timeThreshold;
_timeSinceHurt = 0f;
IconScale = Game.Random.Float( 1.2f, 1.3f );
IconAngleOffset = Game.Random.Float( 10f, 20f ) * (Game.Random.Int( 0, 1 ) == 0 ? -1f : 1f);
}
}
private static float GetValue( int level, Mod mod, bool isPercent = false )
{
switch ( mod )
{
case Mod.HpLoss:
default:
return 1f;
//switch( level )
//{
// case 1: default: return 1;
// case 2: return 5;
// case 3: return 10;
// case 4: return 25;
//}
}
}
}