A Legendary perk that heals the player while touching a tree. It tracks collisions with Tree objects, accumulates a touch timer, heals the player every DELAY seconds, updates display values and a VFX GameObject that visually links the player to the tree.
using System;
using Sandbox;
[Perk( Rarity.Legendary, alwaysOfferDebug: false )]
public class PerkTreehugger : Perk
{
private enum Mod { RegenAmount };
private const float DELAY = 1f;
private const string TreehuggerVfxPrefabPath = "prefabs/effects/laser_line_renderer.prefab";
private float _touchTimer;
private bool _touchingTreeThing;
private Tree _tree;
private bool _isRegenerating;
private GameObject _treehuggerVfxGo;
private PerkTreehuggerVfx _treehuggerVfx;
static PerkTreehugger()
{
Register<PerkTreehugger>(
name: "Treehugger",
imagePath: "textures/icons/vector/treehugger.png",
description: level => $"Heal {GetValue( level, Mod.RegenAmount ).ToString("0.#")} hp/s while\ntouching a tree",
upgradeDescription: level => $"Heal {GetValue( level - 1, Mod.RegenAmount ).ToString( "0.#" )}→{GetValue(level, Mod.RegenAmount).ToString("0.#")} hp/s while\ntouching a tree"
);
}
public override void Start()
{
base.Start();
ShouldUpdate = true;
EnsureTreehuggerVfx();
HighlightColor = new Color( 0.3f, 1f, 0.3f );
HighlightDuration = 0.2f;
HighlightOpacity = 1f;
}
public override void Refresh()
{
base.Refresh();
RefreshDisplay();
}
private static float GetValue( int level, Mod mod, bool isPercent = false )
{
switch ( mod )
{
case Mod.RegenAmount:
default:
return 0.5f + 1.0f * level;
}
}
public override void Update( float dt )
{
base.Update( dt );
if ( _touchingTreeThing )
{
if ( !_tree.IsValid() || (_tree.Position2D - Player.Position2D).LengthSquared > MathF.Pow( Player.Radius + _tree.Radius + 15f, 2f ) )
{
_touchingTreeThing = false;
_tree = null;
}
}
float BUFFER = Player.Radius * 1.2f;
var pos = Player.Position2D;
bool touchingBounds = false;
//(pos.x > Manager.Instance.BOUNDS_MAX.x - BUFFER) ||
//(pos.x < Manager.Instance.BOUNDS_MIN.x + BUFFER) ||
//(pos.y > Manager.Instance.BOUNDS_MAX.y - BUFFER) ||
//(pos.y < Manager.Instance.BOUNDS_MIN.x + BUFFER);
bool isRegenerating = touchingBounds || _touchingTreeThing;
if ( isRegenerating )
{
_touchTimer += dt;
if ( _touchTimer > DELAY )
{
Player.Heal( GetValue( Level, Mod.RegenAmount ) );
_touchTimer = 0f;
Highlight();
}
DisplayCooldown = Utils.Map( _touchTimer, 0f, DELAY, 0f, 1f );
}
DisplayCooldownColor = isRegenerating ? new Color( 0f, 0f, 0f, 5f ) : new Color( 0f, 0.25f, 0f, 1f );
if ( isRegenerating != _isRegenerating )
SetIsRegenerating( isRegenerating );
UpdateTreehuggerVfx( isRegenerating && _touchingTreeThing ? _tree : null );
//DebugOverlay.Text($"{touchingBounds}", Player.Position, 0f, float.MaxValue);
}
public override void Remove( bool restart = false )
{
base.Remove( restart );
if ( _treehuggerVfxGo.IsValid() )
_treehuggerVfxGo.Destroy();
}
void SetIsRegenerating( bool isRegenerating )
{
_isRegenerating = isRegenerating;
RefreshDisplay();
}
void RefreshDisplay()
{
Player.Modify( this, PlayerStat.HpRegenDisplay, _isRegenerating ? (GetValue( Level, Mod.RegenAmount ) / DELAY) : 0f, ModifierType.Add );
}
private void EnsureTreehuggerVfx()
{
if ( _treehuggerVfxGo.IsValid() )
return;
_treehuggerVfxGo = GameObject.Clone( TreehuggerVfxPrefabPath, new CloneConfig
{
StartEnabled = true,
Transform = new Transform( Player.WorldPosition ),
Parent = Player.GameObject,
} );
_treehuggerVfx = _treehuggerVfxGo.AddComponent<PerkTreehuggerVfx>();
_treehuggerVfx.LineRenderer = _treehuggerVfxGo.GetComponent<LineRenderer>( includeDisabled: true )
?? _treehuggerVfxGo.GetComponentInChildren<LineRenderer>( includeDisabled: true, includeSelf: true );
_treehuggerVfx.OwnerPlayer = Player;
_treehuggerVfxGo.NetworkSpawn();
}
private void UpdateTreehuggerVfx( Tree tree )
{
EnsureTreehuggerVfx();
if ( !_treehuggerVfx.IsValid() )
return;
_treehuggerVfx.OwnerPlayer = Player;
_treehuggerVfx.SourceTree = tree;
}
public override void Colliding( Thing other, float percent, float dt )
{
base.Colliding( other, percent, dt );
if ( _touchingTreeThing )
return;
if ( other is Tree tree )
{
_tree = tree;
_touchingTreeThing = true;
}
}
}