An in-game Item subclass representing a perk pickup. It handles visual rotation/hover, configuration via RPC Setup, attraction to nearby players, collision collection logic that grants a perk (including curses) and plays SFX and chat messages.
using System;
using Sandbox;
public class PerkItem : Item
{
[Property] public PerkWorldPanel PerkWorldPanel { get; set; }
[Property, Hide] public int PerkTypeIdentity { get; set; }
[Property, Hide] public string IconPath { get; set; }
[Property, Hide] public Rarity PerkRarity { get; set; }
[Property, Hide] public bool Curse { get; set; }
private float _rotateTimeOffset;
private float _personalRotateSpeed;
[Sync] public bool ShouldHidePanel { get; set; }
public override Vector3 SpawnScale => new Vector3( 1.2f );
protected override float AttractRangeFactor => 1.25f;
protected override float AttractStrengthFactor => 0.5f;
public Player PlayerToTarget { get; set; }
protected override void OnStart()
{
base.OnStart();
Lifetime = 90f;
ShouldCheckBounds = true;
PushStrength = 500f;
BaseZPos = 13f;
WorldPosition = WorldPosition.WithZ( BaseZPos );
Manager.Instance.PlaySfxNearby( "scuffle", Position2D, pitch: Game.Random.Float( 1.4f, 1.45f ), volume: 1.1f, maxDist: 400f );
if ( IsProxy )
return;
Deceleration = 0.92f;
_rotateTimeOffset = Game.Random.Float( 0f, 10f );
_personalRotateSpeed = Game.Random.Float( 3f, 7f );
}
[Rpc.Broadcast]
public void Setup( int typeIdent )
{
PerkTypeIdentity = typeIdent;
var type = TypeLibrary.GetTypeByIdent( typeIdent );
IconPath = Perk.GetImagePath( type.TargetType );
var attrib = type.GetAttribute<PerkAttribute>();
PerkRarity = attrib.Rarity;
Curse = attrib.Curse;
}
protected override void OnUpdate()
{
base.OnUpdate();
//Gizmo.Draw.Color = Color.White;
//Gizmo.Draw.Text( $"{TypeLibrary.GetTypeByIdent( PerkTypeIdentity )}\n{PerkRarity}", new global::Transform( WorldPosition ) );
if ( Manager.Instance.IsGameOver )
return;
if ( IsProxy )
return;
if ( PlayerToTarget.IsValid() && !PlayerToTarget.IsDying && !IsInTheAir )
Velocity += (PlayerToTarget.Position2D - Position2D).Normal * 400f * Time.Delta;
WorldRotation = Rotation.From( new Angles( Utils.FastSin( _rotateTimeOffset + Time.Now * _personalRotateSpeed ) * 15f, 0f, -30f + Utils.FastSin( _rotateTimeOffset + Time.Now * _personalRotateSpeed * 0.75f ) * 5f ) );
if ( !IsInTheAir )
WorldPosition = WorldPosition.WithZ( BaseZPos + 4f + Utils.Map( Utils.FastSin( _rotateTimeOffset + Time.Now * _personalRotateSpeed * 2f ), -1f, 1f, -1f, 1f, EasingType.QuadIn ) * 4f );
}
public override void SetVisible( bool visible )
{
base.SetVisible( visible );
ShouldHidePanel = !visible;
}
public override void Colliding( Thing other, float percent, float dt )
{
base.Colliding( other, percent, dt );
if ( CantBeCollected )
return;
if ( other is Player player )
{
if ( !player.IsDead )
{
var type = PerkManager.IdentityToType( PerkTypeIdentity );
var attrib = type.GetAttribute<PerkAttribute>();
bool isCurse = attrib.Curse;
// check if already at max level before adding
bool wasAlreadyMaxLevel = false;
if( isCurse )
{
var existingLevel = player.GetPerkLevel( type );
var maxLevel = PerkManager.GetMaxLevelForRarity( attrib.Rarity );
wasAlreadyMaxLevel = existingLevel >= maxLevel;
}
player.AddPerkRpc( PerkTypeIdentity );
if( isCurse && !wasAlreadyMaxLevel )
{
player.AddLocalChatMessageRpc( "You got cursed!", from: "", endPerkIdents: new List<int>() { PerkTypeIdentity } );
}
// todo: different sfx for curses
Manager.Instance.PlaySfxNearbyRpc( "heal", player.Position2D, pitch: Game.Random.Float( 1.9f, 2f ), volume: 1.1f, maxDist: 350f );
player.CollectItemEffect();
Remove();
}
}
}
}