A game Item subclass representing a collectible coin. It manages value, scale, tint, physics behaviors like rotation, bounce, magnetization, combining with other coins, and awarding XP to players on pickup.
using System;
using System.Numerics;
using Sandbox;
using Sandbox.Diagnostics;
using Sandbox.Utility;
public class Coin : Item
{
[Property, Hide] public int Value { get; private set; }
public bool HasBeenUsed { get; set; }
private float _personalRotSpeed;
private float _personalBounceSpeed;
public override bool DontDisappear => true;
//public override Vector3 SpawnScale => new Vector3( 0.75f );
public override bool UseSpawnScale => false;
protected override float AttractRangeFactor => 1f;
protected override float AttractStrengthFactor => 1f;
protected override void OnStart()
{
base.OnStart();
ShouldCheckBounds = true;
PushStrength = 300f;
BaseZPos = 8f;
WorldPosition = WorldPosition.WithZ( BaseZPos );
RefreshTint( Value );
if ( IsProxy )
return;
Deceleration = 0.92f;
_personalRotSpeed = Game.Random.Float( -400f, -600f );
_personalBounceSpeed = Game.Random.Float( 10f, 20f );
}
protected override void OnUpdate()
{
base.OnUpdate();
//Gizmo.Draw.Color = Color.White;
//Gizmo.Draw.Text( $"{Value} - scale: {WorldScale}", new global::Transform( WorldPosition ) );
//if ( Manager.Instance.IsGameOver )
// return;
if ( IsProxy )
return;
LocalRotation = new Angles( 0f, LocalRotation.Yaw() + Time.Delta * _personalRotSpeed, 0f );
if ( !IsInTheAir )
WorldPosition = WorldPosition.WithZ( BaseZPos + 3f + Utils.FastSin( Time.Now * _personalBounceSpeed ) * 4f );
}
public override void Colliding( Thing other, float percent, float dt )
{
base.Colliding( other, percent, dt );
if ( HasBeenUsed )
return;
if ( other is Player player )
{
if ( CantBeCollected )
return;
if ( !player.IsDead )
{
if ( Manager.Instance.IsCommunismActive )
Manager.Instance.AddCommunismXp( Value, XpSource.Coin, spawnFloater: true, playSfx: true );
else
player.AddXpRpc( Value, XpSource.Coin );
HasBeenUsed = true;
player.ScaleHeightRpc( amount: Utils.Map( Value, 1, 10, 1.2f, 1.5f ), time: Utils.Map( Value, 1, 10, 0.03f, 0.08f ) * Game.Random.Float( 0.95f, 1.05f ) );
Remove();
}
}
else if ( other is Coin coin )
{
if ( IsInTheAir || (coin.IsMagnetized && !IsMagnetized) )
return;
var newValue = Value + coin.Value;
SetValue( newValue );
var sphereCollider = Collider as SphereCollider;
Radius = sphereCollider.Radius * WorldScale.x;
RefreshTint( newValue );
//SS2Game.CreateParticle( "particles/gameplay/coincombine/coincombine.vpcf", Position + Vector3.Up + Value * 7.5f );
// todo: bounce scale
coin.HasBeenUsed = true;
coin.Remove();
}
}
public void SetValue( int value )
{
Value = value;
var scale = 0.75f;
if ( value <= 3 )
scale = Utils.Map( value, 1, 3, 0.75f, 0.9f );
else if ( value <= 7 )
scale = Utils.Map( value, 4, 7, 0.9f, 1.1f );
else if ( value <= 12 )
scale = Utils.Map( value, 8, 12, 1.1f, 1.15f );
else if ( value <= 18 )
scale = Utils.Map( value, 13, 18, 1.15f, 1.2f );
else if ( value <= 24 )
scale = Utils.Map( value, 19, 24, 1.2f, 1.25f );
else if ( value <= 30 )
scale = Utils.Map( value, 25, 30, 1.25f, 1.3f );
else if ( value <= 35 )
scale = Utils.Map( value, 31, 35, 1.3f, 1.325f );
else if ( value <= 40 )
scale = Utils.Map( value, 36, 40, 1.325f, 1.35f );
else
scale = Utils.Map( value, 41, 100, 1.35f, 1.4f );
WorldScale = new Vector3( scale );
BaseZPos = Utils.Map( scale, 0.75f, 1.4f, 8.5f, 15f );
}
[Rpc.Broadcast]
public void RefreshTint( int value )
{
Color color;
if ( value <= 3 )
color = Color.Lerp( new Color( 0.3f, 0.3f, 1f ), new Color( 0f, 0f, 1f ), Utils.Map( value, 1, 3, 0f, 1f, EasingType.SineOut ) );
else if ( value <= 7 )
color = Color.Lerp( new Color( 1f, 0f, 1f ), new Color( 1f, 0.5f, 0.5f ), Utils.Map( value, 4, 7, 0f, 1f ) );
else if ( value <= 12 )
color = Color.Lerp( new Color( 1f, 0.3f, 0.3f ), new Color( 1f, 0f, 0f ), Utils.Map( value, 8, 12, 0f, 1f ) );
else if ( value <= 18 )
color = Color.Lerp( new Color( 1f, 0.2f, 0.1f ), new Color( 1f, 1f, 0.6f ), Utils.Map( value, 13, 18, 0f, 1f ) );
else if ( value <= 24 )
color = Color.Lerp( new Color( 1f, 1f, 0.4f ), new Color( 1f, 1f, 0f ), Utils.Map( value, 19, 24, 0f, 1f ) );
else if ( value <= 30 )
color = Color.Lerp( new Color( 0.8f, 1f, 0.2f ), new Color( 0.5f, 1f, 0.5f ), Utils.Map( value, 25, 30, 0f, 1f ) );
else if ( value <= 35 )
color = Color.Lerp( new Color( 0.4f, 1f, 0.4f ), new Color( 0f, 1f, 0f ), Utils.Map( value, 31, 35, 0f, 1f ) );
else if ( value <= 40 )
color = Color.Lerp( new Color( 0.2f, 0.95f, 0.2f ), new Color( 0.8f, 0.8f, 0.8f ), Utils.Map( value, 36, 40, 0f, 1f ) );
else
color = Color.Lerp( new Color( 0.83f, 0.83f, 0.83f ), new Color( 1f, 1f, 1f ), Utils.Map( value, 41, 100, 0f, 1f ) );
ModelRenderer.Tint = color.WithAlpha( ModelRenderer.Tint.a );
}
protected override 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 )
{
var strength = 500f;
if ( PlayerMagnetized.GetSyncStat( PlayerStat.CurseXpRepel ) > 0f && (PlayerMagnetized.Position2D - Position2D).LengthSquared < 200f * 200f )
strength = -200f;
Velocity += (PlayerMagnetized.Position2D - Position2D).Normal * strength * Utils.Map( MagnetizeTime, 0f, MAGNETIZE_DURATION, 1f, 0f, EasingType.QuadIn ) * Time.Delta;
}
}
}
}