codeAPI Reference

BaseConstraintToolMode for Constraint Tools

calendar_today May 3, 2026 schedule ~1 min read person patrickjr verified 50

BaseConstraintToolMode Abstract Class

Base class for two-stage constraint tools (weld, rope, elastic, etc.) with selection point handling.

Class Overview

CSHARP
public abstract class BaseConstraintToolMode : ToolMode
{
    protected SelectionPoint Point1;
    protected SelectionPoint Point2;
    protected int Stage = 0;

    /// <summary>
    /// Can this tool constrain an object to itself.
    /// </summary>
    public virtual bool CanConstraintToSelf => false;

    /// <summary>
    /// Enable snap grid for precise placement.
    /// </summary>
    public override bool UseSnapGrid => true;

    /// <summary>
    /// Override to define constraint-specific removal logic.
    /// </summary>
    protected virtual IEnumerable<GameObject> FindConstraints(GameObject linked, GameObject target) => [];

    /// <summary>
    /// Create the constraint between two points. Called on host.
    /// </summary>
    protected abstract void CreateConstraint(SelectionPoint point1, SelectionPoint point2);

    /// <summary>
    /// Override for one-shot secondary placement.
    /// </summary>
    protected virtual SelectionPoint? GetSecondaryPoint(SelectionPoint select) => default;
}

Control Flow

CSHARP
public override void OnControl()
{
    base.OnControl();

    var select = TraceSelect();

    // Secondary action (attack2) - one-shot or cancel
    if (Input.Pressed("attack2"))
    {
        if (Stage == 0 && GetSecondaryPoint(select) is SelectionPoint point2)
        {
            if (!select.IsValid()) return;
            if (!UpdateValidity(select, point2)) return;
            if (!FireToolAction(ToolInput.Secondary)) return;

            Point1 = select;
            Point2 = point2;

            Create(Point1, Point2);
            ShootEffects(select);
            FirePostToolAction(ToolInput.Secondary);
            return;
        }

        Stage = 0; // Cancel
        IsValidState = false;
        return;
    }

    // Reload removes constraints
    if (Input.Pressed("reload"))
    {
        if (!FireToolAction(ToolInput.Reload)) return;
        var go = select.GameObject.Network.RootGameObject ?? select.GameObject;
        RemoveConstraints(go);
        ShootEffects(select);
        FirePostToolAction(ToolInput.Reload);
    }

    // Primary action (attack1) - two-stage selection
    IsValidState = true;

    if (Stage == 0)
    {
        IsValidState = UpdateValidity(select);
    }
    else if (Stage == 1)
    {
        IsValidState = UpdateValidity(Point1, select);
    }

    if (!IsValidState) return;

    if (Input.Pressed("attack1"))
    {
        if (Stage == 0)
        {
            Point1 = select;
            Stage++;
            ShootEffects(select);
        }
        else if (Stage == 1)
        {
            if (!FireToolAction(ToolInput.Primary))
            {
                Stage = 0;
                return;
            }

            Point2 = select;
            Create(Point1, Point2);
            ShootEffects(select);
            FirePostToolAction(ToolInput.Primary);
            Stage = 0;
        }
    }
}

Host RPC Creation

CSHARP
[Rpc.Host(NetFlags.OwnerOnly)]
private void Create(SelectionPoint point1, SelectionPoint point2)
{
    if (!UpdateValidity(point1, point2))
    {
        Log.Warning("Tried to create invalid constraint");
        return;
    }

    CreateConstraint(point1, point2);
    CheckContraptionStats(point1.GameObject);
}

Constraint Removal

CSHARP
[Rpc.Host(NetFlags.OwnerOnly)]
private void RemoveConstraints(GameObject go)
{
    var builder = new LinkedGameObjectBuilder();
    builder.AddConnected(go);

    var toRemove = new List<GameObject>();
    foreach (var linked in builder.Objects)
        toRemove.AddRange(FindConstraints(linked, go));

    foreach (var host in toRemove)
        host.Destroy();
}

Implementation Example

CSHARP
public class WeldTool : BaseConstraintToolMode
{
    protected override void CreateConstraint(SelectionPoint point1, SelectionPoint point2)
    {
        var weld = new GameObject();
        weld.Name = "Weld";
        
        var joint = weld.Components.Create<FixedJoint>();
        joint.Body = point1.GameObject.GetComponent<Rigidbody>();
        joint.Body2 = point2.GameObject.GetComponent<Rigidbody>();
        joint.Transform = point1.WorldTransform();
        
        // Add cleanup component for undo
        weld.Components.Create<ConstraintCleanup>();
    }

    protected override IEnumerable<GameObject> FindConstraints(GameObject linked, GameObject target)
    {
        return linked.GetComponentsInChildren<FixedJoint>()
            .Where(j => j.Body?.GameObject == target || j.Body2?.GameObject == target)
            .Select(j => j.GameObject);
    }
}

Key Behaviors

  • Two-stage selection: Primary fire selects first then second point
  • One-shot secondary: Some tools override GetSecondaryPoint() for instant placement
  • Validity checking: UpdateValidity() prevents self-constraint (unless allowed)
  • Constraint cleanup: Tools define how to find and remove their constraints
  • Stats tracking: Automatically calls CheckContraptionStats() on create
Was this helpful?