codeAPI Reference

ToolMode Base Class for Toolgun Modes

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

ToolMode Base Class

Abstract base class for creating toolgun modes in s&box. Handles input registration, action dispatch, and HUD rendering.

Class Overview

CSHARP
public abstract partial class ToolMode : Component, IToolInfo
{
    public Toolgun Toolgun => GetComponent<Toolgun>();
    public Player Player => GetComponentInParent<Player>();

    /// <summary>
    /// The mode should set this true or false in OnControl to indicate if the current state is valid.
    /// </summary>
    public bool IsValidState { get; protected set; } = true;

    /// <summary>
    /// When true, the toolgun absorbs mouse input so the camera doesn't move.
    /// </summary>
    public virtual bool AbsorbMouseInput => false;

    /// <summary>
    /// Display name for the tool, defaults to TypeDescription title.
    /// </summary>
    public virtual string Name => Game.Language.GetPhrase((TypeDescription?.Title ?? GetType().Name).TrimStart('#'));

    /// <summary>
    /// Tags that TraceSelect will ignore. Defaults to "player".
    /// </summary>
    public virtual IEnumerable<string> TraceIgnoreTags => ["player"];

    /// <summary>
    /// When true, TraceSelect will also hit hitboxes.
    /// </summary>
    public virtual bool TraceHitboxes => false;
}

Action Registration

CSHARP
protected void RegisterAction(ToolInput input, Func<string> name, Action callback, InputMode mode = InputMode.Pressed)
{
    if (IsProxy) return;
    _actions.Add(new ToolActionEntry(input, name, callback, mode));
}

// Example usage in a derived class:
protected override void OnStart()
{
    base.OnStart();
    
    RegisterAction(ToolInput.Primary, () => "Create", OnCreatePressed);
    RegisterAction(ToolInput.Secondary, () => "Remove", OnRemovePressed, InputMode.Down);
}

Object Tracking

CSHARP
/// <summary>
/// Track a GameObject created by this tool action for post-action events.
/// </summary>
protected void Track(params GameObject[] objects)
{
    foreach (var go in objects)
    {
        if (go.IsValid())
            _createdObjects.Add(go);
    }
}

// Usage:
void OnCreatePressed()
{
    var prop = Prefab.Clone(WorldTransform);
    Track(prop); // Tracked for IToolActionEvents
}

Screen Display (Toolgun Screen)

CSHARP
public virtual void DrawScreen(Rect rect, HudPainter paint)
{
    var title = Game.Language.GetPhrase(TypeDescription.Title.TrimStart('#'));
    var t = $"{TypeDescription.Icon} {title}";

    var text = new TextRendering.Scope(t, Color.White, 64);
    text.LineHeight = 0.75f;
    text.FontName = "Poppins";
    text.TextColor = Color.Orange;
    text.FontWeight = 700;

    // Auto-scroll (marquee) if text is too wide
    var measured = text.Measure();
    if (measured.x <= rect.Width)
    {
        paint.DrawText(text, rect, TextFlag.Center);
    }
    else
    {
        const float scrollSpeed = 80f;
        const float gap = 60f;
        float cycle = measured.x + gap;
        float offset = (Time.Now * scrollSpeed) % cycle;
        // Draw scrolling text...
    }
}

ToolModes automatically save/load settings via cookies:

CSHARP
protected override void OnEnabled()
{
    if (Network.IsOwner)
        this.LoadCookies(); // Loads saved settings
}

protected override void OnDisabled()
{
    if (Network.IsOwner)
        this.SaveCookies(); // Saves current settings
}

Stats Integration

CSHARP
[Rpc.Owner]
protected void CheckContraptionStats(GameObject anchor)
{
    var builder = new LinkedGameObjectBuilder();
    builder.AddConnected(anchor);

    var wheels = builder.Objects.Sum(o => o.GetComponentsInChildren<WheelEntity>().Count());
    var thrusters = builder.Objects.Sum(o => o.GetComponentsInChildren<ThrusterEntity>().Count());

    Sandbox.Services.Stats.SetValue("tool.contraption.wheel", wheels);
    Sandbox.Services.Stats.SetValue("tool.contraption.thruster", thrusters);
}
Was this helpful?