Weapons/ToolGun/Modes/Balloon/BalloonTool.cs
using Sandbox.UI;
[Icon( "🎈" )]
[Title( "#tool.name.balloon" )]
[ClassName( "balloon" )]
[Group( "#tool.group.building" )]
public sealed class BalloonTool : ToolMode
{
public override bool UseSnapGrid => true;
[Property, ResourceSelect( Extension = "bdef", AllowPackages = true ), Title( "Balloon" )]
public string Definition { get; set; } = "entities/balloon/basic.bdef";
[Range( 0, 500 )]
[Property, Sync]
public float Length { get; set; } = 50.0f;
[Range( -10, 10 )]
[Property, Sync]
public float Force { get; set; } = 1.0f;
[Property, Sync]
public bool Rigid { get; set; } = false;
[Property, Sync]
public Color Tint { get; set; } = Color.White;
public override string Description => "#tool.hint.balloon.description";
Color _previewTint = Color.Random;
protected override void OnStart()
{
base.OnStart();
RegisterAction( ToolInput.Primary, () => "#tool.hint.balloon.place_rope", OnPlaceWithRope );
RegisterAction( ToolInput.Secondary, () => "#tool.hint.balloon.place", OnPlaceWithoutRope );
}
protected override void OnEnabled()
{
base.OnEnabled();
_previewTint = Color.Random;
}
void OnPlaceWithRope()
{
var select = TraceSelect();
if ( !select.IsValid() ) return;
var thrusterDef = ResourceLibrary.Get<BalloonDefinition>( Definition );
if ( thrusterDef == null ) return;
var pos = select.WorldTransform();
var placementTx = new Transform( pos.Position );
Spawn( select, thrusterDef.Prefab, placementTx, true, _previewTint );
ShootEffects( select );
_previewTint = Color.Random;
}
void OnPlaceWithoutRope()
{
var select = TraceSelect();
if ( !select.IsValid() ) return;
var thrusterDef = ResourceLibrary.Get<BalloonDefinition>( Definition );
if ( thrusterDef == null ) return;
var pos = select.WorldTransform();
var placementTx = new Transform( pos.Position );
Spawn( select, thrusterDef.Prefab, placementTx, false, _previewTint );
ShootEffects( select );
_previewTint = Color.Random;
}
public override void OnControl()
{
base.OnControl();
var select = TraceSelect();
if ( !select.IsValid() ) return;
var thrusterDef = ResourceLibrary.Get<BalloonDefinition>( Definition );
if ( thrusterDef == null ) return;
var pos = select.WorldTransform();
var placementTx = new Transform( pos.Position );
var previewTint = Tint == Color.White ? _previewTint : Tint;
DebugOverlay.GameObject( thrusterDef.Prefab.GetScene(), transform: placementTx, castShadows: true, color: previewTint.WithAlpha( 0.9f ) );
}
[Rpc.Host]
public void Spawn( SelectionPoint point, PrefabFile thrusterPrefab, Transform tx, bool withRope, Color spawnTint )
{
var go = thrusterPrefab.GetScene().Clone( global::Transform.Zero, startEnabled: false );
go.Tags.Add( "removable" );
go.WorldTransform = Rigid && withRope ? tx.WithPosition( tx.Position + Vector3.Up * Length ) : tx;
var tint = Tint == Color.White ? spawnTint : Tint;
foreach ( var c in go.GetComponentsInChildren<Prop>( true ) )
{
c.Tint = tint;
}
if ( withRope )
{
var anchor = new GameObject( false, "anchor" );
anchor.Parent = point.GameObject;
anchor.LocalTransform = point.LocalTransform;
var joint = go.AddComponent<SpringJoint>();
joint.Body = anchor;
joint.MinLength = Rigid ? Length : 0;
joint.MaxLength = Length;
joint.RestLength = Length;
joint.Frequency = 0;
joint.Damping = 0;
joint.EnableCollision = true;
var cleanup = go.AddComponent<ConstraintCleanup>();
cleanup.Attachment = anchor;
const float ropeWidth = 0.4f;
var splineInterpolation = 0;
if ( !Rigid )
{
var vertletRope = go.AddComponent<VerletRope>();
vertletRope.Attachment = anchor;
const int maxSegmentCount = 48;
int segmentCount = Math.Min( maxSegmentCount, MathX.CeilToInt( Length / 16.0f ) );
vertletRope.SegmentCount = segmentCount;
vertletRope.Radius = ropeWidth;
splineInterpolation = segmentCount > maxSegmentCount ? 8 : 4;
}
var lineRenderer = go.AddComponent<LineRenderer>();
lineRenderer.Points = [go, anchor];
lineRenderer.Width = ropeWidth;
lineRenderer.Color = Color.White;
lineRenderer.Lighting = true;
lineRenderer.CastShadows = true;
lineRenderer.SplineInterpolation = splineInterpolation;
lineRenderer.Texturing = lineRenderer.Texturing with { Material = Material.Load( "materials/default/rope01.vmat" ), WorldSpace = true, UnitsPerTexture = 32 };
lineRenderer.Face = SceneLineObject.FaceMode.Cylinder;
anchor.NetworkSpawn( true, null );
}
ApplyPhysicsProperties( go );
go.NetworkSpawn( true, null );
foreach ( var c in go.GetComponentsInChildren<Rigidbody>( true ) )
{
c.GravityScale = Force;
}
var props = go.GetOrAddComponent<PhysicalProperties>();
props.GravityScale = Force;
Track( go );
var undo = Player.Undo.Create();
undo.Name = "Balloon";
undo.Add( go );
Player.PlayerData?.AddStat( "tool.balloon.place" );
}
}