Weapons/ToolGun/Modes/Hydraulic/HydraulicTool.cs
[Hide]
[Title( "#tool.name.hydraulic" )]
[Icon( "⚙️" )]
[ClassName( "HydraulicTool" )]
[Group( "#tool.group.building" )]
public sealed class HydraulicTool : BaseConstraintToolMode
{
public override string Description => Stage == 1 ? "#tool.hint.hydraulictool.stage1" : "#tool.hint.hydraulictool.stage0";
public override string PrimaryAction => Stage == 1 ? "#tool.hint.hydraulictool.finish" : "#tool.hint.hydraulictool.source";
public override string ReloadAction => "#tool.hint.hydraulictool.remove";
[Property, Sync]
public bool BallJoints { get; set; } = false;
protected override IEnumerable<GameObject> FindConstraints( GameObject linked, GameObject target )
{
foreach ( var cleanup in linked.GetComponentsInChildren<ConstraintCleanup>( true ) )
{
if ( linked != target && cleanup.Attachment?.Root != target ) continue;
if ( cleanup.GameObject.GetComponentInChildren<HydraulicEntity>() is not null )
yield return cleanup.GameObject;
}
}
protected override void CreateConstraint( SelectionPoint point1, SelectionPoint point2 )
{
DebugOverlay.Line( point1.WorldPosition(), point2.WorldPosition(), Color.Red, 5.0f );
if ( point1.GameObject == point2.GameObject )
return;
if ( BallJoints )
{
CreateBallJointHydraulic( point1, point2 );
return;
}
var line = point1.WorldPosition() - point2.WorldPosition();
var go1 = new GameObject( false, "hydraulic_a" );
go1.Parent = point1.GameObject;
go1.LocalTransform = point1.LocalTransform;
go1.WorldRotation = Rotation.LookAt( -line );
go1.Tags.Add( "constraint" );
var go2 = new GameObject( false, "hydraulic_b" );
go2.Parent = point2.GameObject;
go2.LocalTransform = point2.LocalTransform;
go2.WorldRotation = Rotation.LookAt( -line );
go2.Tags.Add( "constraint" );
var cleanup = go1.AddComponent<ConstraintCleanup>();
cleanup.Attachment = go2;
var len = (point1.WorldPosition() - point2.WorldPosition()).Length;
// End caps
var capA = new GameObject( go1, true, "hydraulic_cap_a" );
capA.LocalPosition = Vector3.Zero;
capA.WorldRotation = Rotation.LookAt( line ) * Rotation.FromPitch( -90f );
capA.AddComponent<ModelRenderer>().Model = Model.Load( "hydraulics/tool_hydraulic.vmdl" );
var capB = new GameObject( go2, true, "hydraulic_cap_b" );
capB.LocalPosition = Vector3.Zero;
capB.WorldRotation = Rotation.LookAt( -line ) * Rotation.FromPitch( -90f );
capB.AddComponent<ModelRenderer>().Model = Model.Load( "hydraulics/tool_hydraulic.vmdl" );
// Shaft, using line renderer
var lineRenderer = go1.AddComponent<LineRenderer>();
lineRenderer.Points = [go1, go2];
lineRenderer.Face = SceneLineObject.FaceMode.Cylinder;
lineRenderer.Texturing = lineRenderer.Texturing with { Material = Material.Load( "hydraulics/metal_tile_line.vmat" ), WorldSpace = true, UnitsPerTexture = 32 };
lineRenderer.Lighting = true;
lineRenderer.CastShadows = true;
lineRenderer.Width = 2f;
lineRenderer.Color = Color.White;
SliderJoint joint = default;
var jointGo = new GameObject( go1, true, "hydraulic" );
// Joint
{
joint = jointGo.AddComponent<SliderJoint>();
joint.Attachment = Joint.AttachmentMode.Auto;
joint.Body = go2;
joint.MinLength = len;
joint.MaxLength = len;
joint.EnableCollision = true;
}
//
// If it's ourself - we want to create the rope, but no joint between
//
var entity = jointGo.AddComponent<HydraulicEntity>();
entity.Length = 0.5f;
entity.MinLength = 5.0f;
entity.MaxLength = len * 2.0f;
entity.Joint = joint;
var capsule = jointGo.AddComponent<CapsuleCollider>();
go2.NetworkSpawn( true, null );
go1.NetworkSpawn( true, null );
jointGo.NetworkSpawn( true, null );
Track( go1, go2, jointGo );
var undo = Player.Undo.Create();
undo.Name = "Hydraulic";
undo.Add( go1 );
undo.Add( go2 );
undo.Add( jointGo );
}
private void CreateBallJointHydraulic( SelectionPoint point1, SelectionPoint point2 )
{
var p1 = point1.WorldPosition();
var p2 = point2.WorldPosition();
var len = p1.Distance( p2 );
var dir = (p2 - p1).Normal;
var up = MathF.Abs( Vector3.Dot( dir, Vector3.Up ) ) > 0.99f ? Vector3.Forward : Vector3.Up;
var axis = Rotation.LookAt( dir );
var surfaceRotA = point1.WorldTransform().Rotation;
var surfaceRotB = point2.WorldTransform().Rotation;
// Visual anchors — identity rotation for LineRenderer
var goA = new GameObject( false, "bs_hydraulic_a" );
goA.Parent = point1.GameObject;
goA.LocalTransform = point1.LocalTransform;
goA.LocalRotation = Rotation.Identity;
goA.Tags.Add( "constraint" );
var goB = new GameObject( false, "bs_hydraulic_b" );
goB.Parent = point2.GameObject;
goB.LocalTransform = point2.LocalTransform;
goB.LocalRotation = Rotation.Identity;
goB.Tags.Add( "constraint" );
var cleanup = goA.AddComponent<ConstraintCleanup>();
cleanup.Attachment = goB;
// Base mount visuals — surface normal aligned
var baseVisA = new GameObject( goA, true, "hydraulic_base_a" );
baseVisA.LocalPosition = Vector3.Zero;
baseVisA.WorldRotation = surfaceRotA * Rotation.FromPitch( 90f );
baseVisA.AddComponent<ModelRenderer>().Model = Model.Load( "hydraulics/tool_suspension_base.vmdl" );
var baseVisB = new GameObject( goB, true, "hydraulic_base_b" );
baseVisB.LocalPosition = Vector3.Zero;
baseVisB.WorldRotation = surfaceRotB * Rotation.FromPitch( 90f );
baseVisB.AddComponent<ModelRenderer>().Model = Model.Load( "hydraulics/tool_suspension_base.vmdl" );
// Ball joint visuals
var ballVisA = new GameObject( goA, true, "hydraulic_ball_a" );
ballVisA.WorldPosition = goA.WorldPosition + surfaceRotA.Forward * 4.644f;
ballVisA.WorldRotation = Rotation.LookAt( -dir, up ) * Rotation.FromPitch( -90f );
var skinA = ballVisA.AddComponent<SkinnedModelRenderer>();
skinA.Model = Model.Load( "hydraulics/tool_balljoint.vmdl" );
skinA.CreateBoneObjects = true;
var ballVisB = new GameObject( goB, true, "hydraulic_ball_b" );
ballVisB.WorldPosition = goB.WorldPosition + surfaceRotB.Forward * 4.644f;
ballVisB.WorldRotation = Rotation.LookAt( dir, up ) * Rotation.FromPitch( -90f );
var skinB = ballVisB.AddComponent<SkinnedModelRenderer>();
skinB.Model = Model.Load( "hydraulics/tool_balljoint.vmdl" );
skinB.CreateBoneObjects = true;
// Shaft
var lineRenderer = goA.AddComponent<LineRenderer>();
lineRenderer.Points = [ballVisA, ballVisB];
lineRenderer.Face = SceneLineObject.FaceMode.Cylinder;
lineRenderer.Texturing = lineRenderer.Texturing with { Material = Material.Load( "hydraulics/metal_tile_line.vmat" ), WorldSpace = true, UnitsPerTexture = 32 };
lineRenderer.Lighting = true;
lineRenderer.CastShadows = true;
lineRenderer.Width = 1.5f;
lineRenderer.Color = Color.White;
var aligner = goA.AddComponent<BallSocketPairEntity>();
aligner.BallModelA = skinA;
aligner.BallModelB = skinB;
aligner.ShaftRenderer = lineRenderer;
// Ball socket constraint
var ballTarget = new GameObject( point2.GameObject, false, "bs_target" );
ballTarget.LocalTransform = point2.LocalTransform;
var ballAnchor = new GameObject( point1.GameObject, false, "bs_anchor" );
ballAnchor.WorldTransform = ballTarget.WorldTransform;
var ballJoint = ballAnchor.AddComponent<BallJoint>();
ballJoint.Body = ballTarget;
ballJoint.Friction = 0.0f;
ballJoint.EnableCollision = false;
// Slider joint
var sliderA = new GameObject( false, "hydraulic_slider_a" );
sliderA.Parent = point1.GameObject;
sliderA.LocalTransform = point1.LocalTransform;
sliderA.WorldRotation = axis;
var sliderB = new GameObject( false, "hydraulic_slider_b" );
sliderB.Parent = point2.GameObject;
sliderB.LocalTransform = point2.LocalTransform;
sliderB.WorldRotation = axis;
var slider = sliderA.AddComponent<SliderJoint>();
slider.Body = sliderB;
slider.MinLength = len;
slider.MaxLength = len;
slider.EnableCollision = true;
var entity = sliderA.AddComponent<HydraulicEntity>();
entity.Length = 0.5f;
entity.MinLength = 5.0f;
entity.MaxLength = len * 2.0f;
entity.Joint = slider;
sliderA.AddComponent<CapsuleCollider>();
// TODO: my lord
goB.NetworkSpawn( true, null );
goA.NetworkSpawn( true, null );
ballTarget.NetworkSpawn( true, null );
ballAnchor.NetworkSpawn( true, null );
sliderB.NetworkSpawn( true, null );
sliderA.NetworkSpawn( true, null );
Track( goA, goB, ballAnchor, ballTarget, sliderA, sliderB );
var undo = Player.Undo.Create();
undo.Name = "Hydraulic (Ball Joints)";
undo.Add( goA, goB, ballAnchor, ballTarget, sliderA, sliderB );
}
}