codeAPI Reference
BaseConstraintToolMode for Constraint Tools
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?