A unique perk class 'CurseChainGround' that periodically tethers the player to a nearby ground position. It tracks cooldown and time-since-last-tether, updates display values, and triggers visual/audio feedback and camera shake when the chain is applied.
using Sandbox;
using System;
using System.Numerics;
using System.Reflection;
[Perk( Rarity.Unique, curse: true, alwaysOfferDebug: false )]
public class CurseChainGround : Perk
{
private enum Mod { Time, Cooldown };
private TimeSince _timeSinceTether;
static CurseChainGround()
{
Register<CurseChainGround>(
name: "Tethered",
imagePath: "textures/icons/vector/curse_chain_ground.png",
description: level => $"Every [-]{GetValue( level, Mod.Cooldown )}s[/-], get\nchained for [-]{GetValue( level, Mod.Time )}s[/-]"
);
}
public override void Start()
{
base.Start();
DisplayCooldownColor = new Color( 1f, 0.5f, 0.4f, 0.6f );
ShouldUpdate = true;
_timeSinceTether = 0f;
}
public override void Refresh()
{
base.Refresh();
}
public override void Update( float dt )
{
base.Update( dt );
DisplayCooldown = Utils.Map( _timeSinceTether, 0f, GetValue( Level, Mod.Cooldown ), 0f, 1f );
DisplayText = $"{MathX.CeilToInt( GetValue( Level, Mod.Cooldown ) - _timeSinceTether )}";
if ( _timeSinceTether > GetValue( Level, Mod.Cooldown ) )
{
_timeSinceTether = 0f;
DisplayCooldown = 0f;
if ( !Player.IsDead )
{
var pos = Manager.Instance.ClampPosToBounds( Player.Position2D + Player.FacingDir * 25f );
Player.Chain( anchorUnit: null, chainPos: pos, chainLength: 150f, lifetime: GetValue( Level, Mod.Time ) );
HighlightColor = new Color( 1f, 0.25f, 0.3f );
HighlightDuration = 0.5f;
HighlightOpacity = 4f;
Highlight();
IconScale = Game.Random.Float( 1.2f, 1.3f );
IconAngleOffset = Game.Random.Float( 10f, 20f ) * (Game.Random.Int( 0, 1 ) == 0 ? -1f : 1f);
Manager.Instance.RequestTimeScale( startTimeScale: 0f, endTimeScale: 1f, duration: 0.15f, priority: 10 );
Player.ShakeRpc( startStrength: 1f, endStrength: 0f, time: 0.15f, easingType: EasingType.SineInOut );
}
}
}
private static float GetValue( int level, Mod mod, bool isPercent = false )
{
switch ( mod )
{
case Mod.Time:
default:
return 7;
case Mod.Cooldown:
return 30;
}
}
}