Learn SugarIntermediate

Item actions

Build an Item's custom actions in Sugar, from one simple message to configurable actions with variables and conditions.

An Item action is something a person can choose to do with an Item. Every custom action lives in the Item’s Code module beside its shared variables and functions.

Start with one small action

action inspect {
  text "@1 inspects the strange key.";
}

action begins the declaration. inspect is the action’s identifier, which your code uses to keep track of it. JOI displays Inspect automatically.

The braces contain everything that belongs to this action. A custom action needs at least one text declaration.

Choose the visible name

Use the display arrow -> when the action should display a different name:

action rollDice -> "Roll the dice" {
  text "@1 rolled a {}." with Rand::pick(1..=6);
}

rollDice remains its Sugar identifier. People see Roll the dice.

Configure participants and Item uses

Add a config block when the action needs something other than its defaults:

action dare -> "Give a dare" {
  config {
    description: "Chooses a dare for another participant.",
    participants: 2,
    consumes: 1,
  }

  text "@1 dares @2 to {}." with Rand::pick("sing a song", "share a secret", "send a selfie");
}
  • participants: 2 requires exactly two people.
  • consumes: 1 spends one Item use when the action succeeds.
  • description adds a short explanation below the selected action in chat.

Descriptions are optional static text and can contain at most 80 characters.

When the configuration is short, it can stay on one line:

config description => "Chooses a dare for another participant.", participants => 2, consumes => 1;

This is the same configuration as the block above, written in the compact mapping form introduced in Arrow syntax.

A participant range accepts several counts:

participants: 2..=4,

This action works with 2, 3, or 4 participants. 2..4 would include 2 and 3, but leave out 4.

Make an action available conditionally

Put when before the action body:

var hasKey = true;

action unlockDoor when hasKey {
  text "@1 unlocks the hidden door.";
}

The action appears only while hasKey is true.

The condition can read Item variables and linked App variables. It cannot change them.

Give an action its own variables

Declare variables inside the action:

action guessNumber -> "Guess the number" {
  config {
    consumes: 0,
  }

  var guess -> "Your guess": number = 1 with {
    input: true,
    required: true,
    min: 1,
    max: 10,
  };

  let secretNumber = Rand::pick(1..=10);
  let correct = guess == secretNumber;

  text if correct {
    "Correct! @1 found the secret number."
  } else {
    "Not this time. Try again!"
  }
}

guess, secretNumber, and correct exist only while this action runs. The configured var receives the person’s input; each let names a temporary calculation. text if chooses one complete result.

Variables declared at the top level are different. Every action on the Item can use them. Top-level functions are shared in the same way.

Use shared declarations with actions

Because the declarations share one module, you can read their relationship without switching editors:

var bonus = 5;

fn roll() {
  Rand::pick(1..=20) + bonus
}

action rollNumber -> "Roll a number" {
  text "@1 rolled a {}!" with roll();
}

bonus and roll() are available to every action in this Item. Their position does not change their scope, so the action could also appear above them.

Put shared behavior in the action

Code directly inside an action runs once before JOI chooses a text. This is the default place for calculations, assignments, and other behavior shared by every possible result:

action waterPlant -> "Water plant" {
  let wasWilted = hydration == 0;
  hydration = clamp(hydration + 25, 0, 100);

  text if wasWilted {
    "@1 waters {HOST.name}, helping it stand tall again."
  } else {
    "{HOST.name} happily drinks the water."
  }
}

Action let variables such as wasWilted remain available to its text conditions and bodies.

Put a program inside text { ... } only when that particular text needs behavior the other possible texts must not run. Because JOI randomly chooses among equally eligible text when declarations, code inside an unchosen text does not run.

Use text if ... else if ... else for one mutually exclusive set of messages. It requires a final else, so the action always has a result. Use separate text when declarations when each eligible declaration should remain an independent member of the text pool.

Keep the code readable

Comments are useful when an action grows:

var hasKey = true;

// Only appears after the player finds the key
action unlockDoor when hasKey {
  text "@1 unlocks the hidden door.";
}

Use blank lines between actions. JOI saves the source exactly as you wrote it, including comments and spacing.

The next lesson adds Memory variables so action changes can survive beyond one use. Later output and media lessons build on the same action body without changing its basic declaration shape.