> For the complete documentation index, see [llms.txt](https://tools.continis.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tools.continis.io/hats/building-on-hats/adding-new-rules.md).

# Adding new rules

The set of rules is open: you can write your own and they'll show up in the workspace's rule list automatically, right next to the built-in ones. A rule is just a small C# class.

## How to create a new rule

A rule needs four things:

1. It must live in an **Editor** assembly (an `Editor` folder, or an asmdef that references `Hats.Editor`).
2. It must inherit from `RuleBase` and be marked `[System.Serializable]`.
3. It must carry a `[RuleInteractionType(...)]` attribute that says **when** it acts.
4. It must implement `RuleName`, `RuleDescription`, `OnBecameActive` and `OnBecameInactive`.

That's it. Public serializable fields appear in the Inspector on their own, and the rule is added to the **+** menu of Workspaces automatically.

An example of a simple custom Rule:

```csharp
using Hats.Editor;
using Hats.Editor.Attributes;
using Hats.Editor.Rules;

namespace MyGame.HatsRules
{
    [System.Serializable]
    [RuleInteractionType(RuleInteractionType.OnWorkspaceActivated)]
    public class MyRule : RuleBase
    {
        public override string RuleName => "My Rule";

        public override string RuleDescription =>
            "A short explanation of what this rule does. " +
            "It's shown in the (?) tooltip next to the rule in the Inspector.";

        public override bool OnBecameActive(Hats.ChangeReason reason)
        {
            // Do the thing here.
            // Return true if it worked, false if it couldn't run.
            return true;
        }

        public override bool OnBecameInactive() => true;
    }
}
```

#### Interaction type

The `[RuleInteractionType(...)]` attribute is required, and decides which submenu the rule lands in:

* `OnWorkspaceActivated` — the rule does something only when the workspace is turned on (**On Activation**).
* `OnWorkspaceDeactivated` — the rule does something only when the workspace is turned off (**On Deactivation**).
* `OnOff` — the rule does something on activation and undoes it on deactivation (**On & Off**).

{% hint style="warning" %}
Don't forget the attribute. A rule without `[RuleInteractionType(...)]` won't be picked up correctly by the **+** menu.
{% endhint %}

#### Return values

`OnBecameActive` and `OnBecameInactive` return a `bool`: return `true` when the rule applied successfully, and `false` when it couldn't (for example because a required field is empty or the Prefab it was trying to load has been destroyed, etc.). When returning `false`, it's good practice to log a warning prefixed with `(Hats)` so the user knows why nothing happened.

Also, the return is used to decide whether to execute following rules in the Workspace. This is to avoid that if a rule doesn't execute correctly, following rules fail too.

#### Handling recompiles

`OnBecameActive` is also called after a domain reload (a script recompile) for whichever workspace is active, with `reason == Hats.ChangeReason.Recompile`.

Many rules early-return in that case, because whatever they did is already in place, but rules can be set to act again after a recompile if that makes sense.

If you don't need a rule to act again on recompile, you can early exit using this line:

```csharp
if (reason == Hats.ChangeReason.Recompile) return true;
```

#### Remembering previous state

Since rules are not serialised by default, an `OnOff` rule that needs to restore something to a previous state might lose the original state if a recompile happens while it's active.

As such, a good idea is to store the state value in a `ScriptableSingleton`, so that the value will survive a recompile:

```csharp
[RuleInteractionType(RuleInteractionType.OnOff)]
[System.Serializable]
public class MyToggleRule : RuleBase
{
    public override string RuleName => "My Toggle Rule";
    public override string RuleDescription => "...";

    public override bool OnBecameActive(Hats.ChangeReason reason)
    {
        // On recompile, don't overwrite the value we already stored.
        if (reason != Hats.ChangeReason.Recompile)
        {
            MyRuleMemory.instance.previousValue = SomeSetting;
            MyRuleMemory.instance.hasStored = true;
        }

        SomeSetting = myDesiredValue;
        return true;
    }

    public override bool OnBecameInactive()
    {
        if (!MyRuleMemory.instance.hasStored) return true;

        SomeSetting = MyRuleMemory.instance.previousValue;
        MyRuleMemory.instance.hasStored = false;
        return true;
    }
}

public class MyRuleMemory : ScriptableSingleton<MyRuleMemory>
{
    public bool previousValue;
    public bool hasStored;
}
```

{% hint style="info" %}
Some Editor APIs aren't ready the instant a workspace switches (the Scene View, for example). If you hit this, defer the work with `EditorApplication.delayCall += () => { ... };`.
{% endhint %}

#### Renaming a rule later

If you rename a rule's class after it's already been used in some workspaces, add the `[MovedFrom]` attribute so existing references keep working:

```csharp
using UnityEngine.Scripting.APIUpdating;

[System.Serializable]
[MovedFrom(true, sourceClassName: "OldRuleName")]
[RuleInteractionType(RuleInteractionType.OnOff)]
public class NewRuleName : RuleBase { /* ... */ }
```

## Custom Inspector (optional)

By default the rule's fields are drawn with Unity's standard property field, and the `(?)` tooltip is filled from `RuleDescription`. If you want a custom layout (extra buttons, a different control), add a drawer that inherits from `RulePropertyDrawer` and override `CreateMainElement` (to replace the body) or `AddExtraElements` (to append to the foldout).

{% hint style="info" icon="lightbulb" %}
For instance, the **Scene Camera to Position** rule does this to add its **Record Camera Position** button.
{% endhint %}
