Item entity class for the game, inherits Thing. Manages rendering, lifetime, fading, flying trajectories, magnetize behavior toward players, attraction forces, collision response and removal effects.
using System;
using System.Numerics;
using Sandbox;
public class Item : Thing
{
[Property] public ModelRenderer ModelRenderer { get; set; }
[Property] public HighlightOutline HighlightOutline { get; set; }
public bool IsVisible { get; private set; }
public float Lifetime { get; set; }
public float BaseZPos { get; set; }
private float _flyTimer;
private float _flyTime;
private Vector2 _flyStartPos;
private Vector2 _flyTargetPos;
private float _flyHeight;
public bool FlyHalfway { get; private set; }
public Player FlyTargetPlayer { get; private set; }
private bool _isFalling;
public bool DidFly { get; set; }
public int NumBounces { get; set; }
public virtual bool DontDisappear => false;
private TimeSince _timeSinceFlash;
public virtual float BlinkTimeRemainingStart => 5f;
private bool _hasFinishedFadingIn;
public const float FADE_IN_TIME = 0.06f;
public virtual bool CantBeCollected => IsInTheAir && NumBounces == 0;
protected virtual float AttractRangeFactor => 0f;
protected virtual float AttractStrengthFactor => 0f;
public bool IsMagnetized { get; private set; }
public Player PlayerMagnetized { get; private set; }
public TimeSince MagnetizeTime { get; private set; }
protected const float MAGNETIZE_DURATION = 12f;
protected override void OnAwake()
{
base.OnAwake();
ModelRenderer.Tint = Color.White.WithAlpha( 0f );
if ( IsProxy )
return;
}
protected override void OnStart()
{
base.OnStart();
if ( HighlightOutline == null )
{
HighlightOutline = Components.Create<HighlightOutline>();
HighlightOutline.Width = 0.2f;
HighlightOutline.Color = new Color( 1f, 1f, 1f, 0.3f );
HighlightOutline.Enabled = false;
}
if ( IsProxy )
return;
CollideWithTags.Add( "player" );
CollideWithTags.Add( "enemy" );
CollideWithTags.Add( "item" );
CollideWithTags.Add( "obstacle" );
}
protected override void OnUpdate()
{
base.OnUpdate();
if ( !_hasFinishedFadingIn )
{
if ( TimeSinceSpawn < FADE_IN_TIME )
{
ModelRenderer.Tint = ModelRenderer.Tint.WithAlpha( Utils.Map( TimeSinceSpawn, 0f, FADE_IN_TIME, 0f, 1f ) );
}
else
{
ModelRenderer.Tint = ModelRenderer.Tint.WithAlpha( 1f );
_hasFinishedFadingIn = true;
}
}
if ( !DontDisappear )
{
if ( TimeSinceSpawn > Lifetime - BlinkTimeRemainingStart )
{
float delay = Utils.Map( TimeSinceSpawn, Lifetime - BlinkTimeRemainingStart, Lifetime, 0.125f, 0.025f, EasingType.QuadIn );
if ( _timeSinceFlash > delay )
{
SetVisible( !IsVisible );
_timeSinceFlash = 0f;
}
//if ( TimeSinceSpawn > Lifetime )
// GameObject.Clone( "prefabs/effects/cloud.prefab", new CloneConfig { StartEnabled = true, Transform = new Transform( WorldPosition.WithZ( 10f ) ) } );
}
}
if ( IsProxy )
return;
UpdateMagnetize();
if ( IsInTheAir )
{
_flyTimer += Time.Delta;
if ( _flyTimer > _flyTime )
{
IsInTheAir = false;
var pos = FlyTargetPlayer.IsValid() ? FlyTargetPlayer.Position2D : _flyTargetPos;
WorldPosition = new Vector3( pos.x, pos.y, BaseZPos );
if ( NumBounces == 0 || (NumBounces < 3 && Game.Random.Float( 0f, 1f ) < 0.4f) && !FlyTargetPlayer.IsValid() )
{
Fly(
Position2D + (_flyTargetPos - _flyStartPos) * Game.Random.Float( 0.2f, 0.4f ),
_flyTime * Game.Random.Float( 0.2f, 0.35f ),
_flyHeight * Game.Random.Float( 0.1f, 0.35f ) * (_isFalling ? 0.25f : 1f)
);
NumBounces++;
// todo: sfx
//if ( NumBounces < 2 )
// Manager.Instance.PlaySfxNearbyRpc( "plop", Position2D, pitch: Game.Random.Float( 1.3f, 1.4f ) * Utils.Map( NumBounces, 0, 3, 1f, 0.8f ), volume: Utils.Map( NumBounces, 0, 3, 1f, 0.5f ), maxDist: 200f );
}
FlyTargetPlayer = null;
}
else
{
Vector2 targetPos = FlyTargetPlayer.IsValid() ? FlyTargetPlayer.Position2D : _flyTargetPos;
float progress = Utils.Map( _flyTimer, 0f, _flyTime, 0f, 1f );
var pos = Vector2.Lerp( _flyStartPos, targetPos, progress );
var zPos = _isFalling
? BaseZPos + Utils.Map( progress, 0f, 1f, 1f, 0f, EasingType.SineIn ) * _flyHeight
: BaseZPos + Utils.MapReturn( progress, 0f, 1f, 0f, 1f, EasingType.QuadOut ) * _flyHeight;
WorldPosition = new Vector3( pos.x, pos.y, zPos );
float flyProgress = _isFalling
? Utils.Map( progress, 0f, 1f, 0f, 1f )
: Utils.MapReturn( progress, 0f, 1f, 1f, 0.5f );
FlyHalfway = _flyTimer > _flyTime * 0.5f;
}
}
else
{
if ( AttractRangeFactor > 0f && AttractStrengthFactor > 0f )
{
foreach ( var player in Manager.Instance.AlivePlayers )
{
var dist_sqr = (Position2D - player.Position2D).LengthSquared;
var req_dist_sqr = MathF.Pow( player.GetSyncStat( PlayerStat.CoinAttractRange ) * AttractRangeFactor, 2f );
if ( dist_sqr < req_dist_sqr )
{
if ( (player.Position2D - Position2D).LengthSquared > Manager.TOUCH_DIST_REQUIRED_SQR )
{
var strength = player.GetSyncStat( PlayerStat.CoinAttractStrength );
if ( player.GetSyncStat( PlayerStat.CurseXpRepel ) > 0f && this is Coin )
strength *= -0.5f;
Velocity += (player.Position2D - Position2D).Normal * Utils.Map( dist_sqr, req_dist_sqr, 0f, 0f, 1f, EasingType.Linear ) * AttractStrengthFactor * strength * Time.Delta * Manager.Instance.GlobalMovespeedModifier;
}
}
}
}
if ( Manager.Instance.IsWindActive )
Velocity += Manager.Instance.GlobalWindForce * 0.25f * Time.Delta;
WorldPosition += (Vector3)Velocity * Time.Delta;
Velocity *= Math.Max( 1f - Time.Delta * Deceleration * Manager.Instance.GlobalFrictionModifier, 0f );
}
if ( !DontDisappear && TimeSinceSpawn > Lifetime )
{
Remove();
}
}
[Rpc.Broadcast]
public virtual void MagnetizeRpc( Player player )
{
HighlightOutline.Enabled = true;
if ( IsProxy )
return;
Magnetize( player );
}
public virtual void Magnetize( Player player )
{
PlayerMagnetized = player;
IsMagnetized = true;
MagnetizeTime = 0f;
}
[Rpc.Broadcast]
public virtual void StopMagnetizeRpc()
{
HighlightOutline.Enabled = false;
if ( IsProxy )
return;
IsMagnetized = false;
PlayerMagnetized = null;
}
protected virtual void UpdateMagnetize()
{
if ( !IsMagnetized )
return;
if ( MagnetizeTime > MAGNETIZE_DURATION || PlayerMagnetized == null || !PlayerMagnetized.IsValid || PlayerMagnetized.IsDead )
{
StopMagnetizeRpc();
}
else
{
if ( (PlayerMagnetized.Position2D - Position2D).LengthSquared > Manager.TOUCH_DIST_REQUIRED_SQR )
{
Velocity += (PlayerMagnetized.Position2D - Position2D).Normal * 500f * Utils.Map( MagnetizeTime, 0f, MAGNETIZE_DURATION, 1f, 0f, EasingType.QuadIn ) * Time.Delta;
}
}
}
public virtual void SetVisible( bool visible )
{
IsVisible = visible;
ModelRenderer.Enabled = visible;
}
public void Fly( Vector2 targetPos, float time, float height, bool isFalling = false )
{
targetPos = Manager.Instance.ClampPosToBounds( targetPos );
IsInTheAir = true;
_flyStartPos = Position2D;
_flyTargetPos = targetPos;
_flyTime = time;
_flyHeight = height;
_flyTimer = 0f;
DidFly = true;
_isFalling = isFalling;
FlyTargetPlayer = null;
if ( _isFalling )
{
WorldPosition = new Vector3( Position2D.x, Position2D.y, height );
}
}
public void FlyToPlayer( Player targetPlayer, float time, float height, bool isFalling = false )
{
Fly( Position2D, time, height, isFalling );
FlyTargetPlayer = targetPlayer;
}
public override void Colliding( Thing other, float percent, float dt )
{
base.Colliding( other, percent, dt );
if ( other is Enemy enemy )
{
if ( !Position2D.Equals( enemy.Position2D ) )
Velocity += (Position2D - enemy.Position2D).Normal * Math.Min( 300f, enemy.PushStrength ) * percent * enemy.SpawnProgress * dt; // * (enemy.Weight / Weight)
}
else if ( other is Item item )
{
if ( IsInTheAir )
return;
if ( !Position2D.Equals( other.Position2D ) )
Velocity += (Position2D - other.Position2D).Normal * other.PushStrength * percent * (other.Weight / Weight) * dt;
}
else
{
if ( IsInTheAir )
return;
// obstacle
if ( !Position2D.Equals( other.Position2D ) )
Velocity += (Position2D - other.Position2D).Normal * other.PushStrength * percent * (other.Weight / Weight) * dt;
var maxVel = 175f;
if ( Velocity.LengthSquared > maxVel * maxVel )
Velocity = Velocity.Normal * maxVel;
}
}
public virtual void Remove()
{
Manager.Instance.SpawnCloudRpc( WorldPosition.WithZ( Game.Random.Float( 10f, 13f ) ), velocity: Vector2.Zero, deceleration: 4f, bright: true );
GameObject.Destroy();
}
}