control(key id, integer held, integer change)

Once a script has permission to take control inputs from the avatar with the key id (see llRequestPermissions), and has taken control of the desired set (see llTakeControls), this event will be used to pass the state of taken controls to the script.

The held parameter is a bitfield indicating which controls are held down. The change parameter is a bitfield indicating which controls changed their state since the last control event. The combination of these two parameters can be used to determine the various states of any taken controls. The easiest way to do this is by comparing the parameters to pre-defined control constants.

Here are the available control constants:

Constant name Value Hex Value Description
CONTROL_FWD 1 0x00000001 Tests for avatar forward control
CONTROL_BACK 2 0x00000002 Tests for avatar back control
CONTROL_LEFT 4 0x00000004 Tests for avatar move left control
CONTROL_RIGHT 8 0x00000008 Tests for avatar move right control
CONTROL_UP 16 0x00000010 Tests for avatar up control
CONTROL_DOWN 32 0x00000020 Tests for avatar down control
? 64 0x00000040 ?
? 128 0x00000080 ?
CONTROL_ROT_LEFT 256 0x00000100 Tests for avatar rotate left control
CONTROL_ROT_RIGHT 512 0x00000200 Tests for avatar rotate right control
CONTROL_LBUTTON 268435456 0x10000000 Tests for avatar left button control
CONTROL_ML_LBUTTON 1073741824 0x40000000 Tests for avatar left button control with the avatar in mouselook

The held and change parameters can each have information on multiple controls in any received event. If a control is pressed down the corresponding bit (matching the control constant) in the held parameter will be set to 1. If the state of the control just changed from up to down or down to up, the corresponding bit in the change parameter will be set to 1.

Since we tend to think more of presses, holds and releases, it’s useful to know how they relate. If the bit for a given control is unset (0) in both parameters then it hasn’t been touched and it is up. If the bit in both the held and change parameters is set (1), then the control was just pressed. If held is set and change is not, then the button is being held down. As long as the button is held down additional control events will be generated with the control’s held bit set. If the held bit is unset and the change bit is set, then the control was just released.

So if a ‘1’ indicates the presence of the desired control constant in the parameters (across the top) for each control state (down the side) then:

held change
button not held 0 0
button press 1 1
button held down 1 0
button release 0 1

Detecting Controls

There are a few ways you can test for controls in your code. Remembering your bitwise operators you’ll note that & is the intersection. So only bits that are set on both sides of the operator will remain set in the result. This is handy for seeing if the same bit is set in two values. Or(the ‘pipe’ operator |) is the union. All bits set in both arguments are set in the result. This can be useful for combining control constants to check for combinations. Be careful though, checking for multiple bits is tricky (see below).

The following code looks for an initial press of the forward button by and’ing held and change (if it’s held and changed that’s a press) and the desired control constant. Since only a single bit representing forward is set in the CONTROL_FWD constant, and’ing that constant with the two parameters will only result in a TRUE value if that bit is also set in both the held and change parameters (i.e. the button was pressed).

if (held & change & CONTROL_FWD) llOwnerSay("forward pressed");

This code looks for a button release:

if (~held & change & CONTROL_FWD) llOwnerSay("forward released");

The ~ operator is bitwise NOT, i.e. it flips all the bits from 0 to 1 or 1 to 0. If the CONTROL_FWD bit is set in ~held it was unset in the original held, which means it is up (not depressed).

This code looks for a button being held down (and will include the initial press since it’s true then as well)

if (held & CONTROL_FWD) llOwnerSay("forward held");

An alternative style that might be easier for some to understand maps more directly to the control state table above and looks like so:

if ((held & CONTROL_LBUTTON) && (change & CONTROL_LBUTTON)) llOwnerSay("mouse press");
if ((~held & CONTROL_LBUTTON) && (change & CONTROL_LBUTTON)) llOwnerSay("mouse release");

If there’s a 0 in the row you want for one of the parameters put a tilde ~ in front of it in the if statement. If it’s a 1, leave it normal.

Handling Multiple Controls

A single callback to this function can contain information on the status of multiple controls so they can be combined for more detailed actions. This is tricky because you may want to insure that all the bits you’re looking for are set if you’re looking for a “combo” move of more than one key. A statement like the following won’t work as expected:

if (held & change & (CONTROL_FWD | CONTROL_LEFT)) llOwnerSay("forward and/or left"); // Might not be what you want!

The problem is a case where only one of the specified controls is pressed, the forward button for example. Looking at the table at the top, forward has a value of 1. Left has a value of 4 (100 binary). So CONTROL_FWD | CONTROL_LEFT has a value of 5 (101 binary). If the forward key is pressed alone, both held and change will have a value of 1. In this case 1 & 1 & 101 evaluates to 1 because the first bit (1) is set in all 3 arguments and since one is TRUE the combo will be detected.

This is fine if you are looking for any combination of the specified controls, but if you want to make sure that all the desired controls are pressed, your code needs to work like the following:

integer secret_style = CONTROL_DOWN | CONTROL_UP | CONTROL_FWD;

if ((held & change & secret_style) == secret_style) {
    llOwnerSay("five point palm exploding heart technique");


This will only evaluate to TRUE if all the bits set in secret_style are present in both held and change. This method allows additional keys
beyond the ones you are looking for to be pressed and the combo will still be detected. If you want to be even more strict use code like this:

if ((held & change) == secret_style) llOwnerSay("very strict");

This will only evaluate to TRUE if exactly the specified controls are pressed and no others.

Asking someone to make sure they press 3 keys at the same instant to get them in one control event may be a bit much. An example that will make sure at least one was pressed and the correct 3 are down looks like:

if ((held & change) && (held == secret_style)) llOwnerSay("ouch");

This is saying, “If any key has been pressed and the keys that are down are exactly the right ones, then cause some pain.”

Parameter Conversion

Dealing with held and change can be hard to keep track of in complex scripts. If you’d rather use the ideas of pressed, down and released you can use code like this to figure out all the logic for you:

control(key id, integer held, integer change) {
        integer pressed = held & change;
        integer down = held & ~change;
        integer released = ~held & change;
        integer inactive = ~held & ~change;

        if (pressed & CONTROL_LBUTTON) llOwnerSay("click");


Keep in mind that down will not contain keys that have just been pressed even though they are “down”. If you don’t want that just use held.

Mouselook Mode vs. Normal Mode

In mouselook, the default movement keys act a bit different compared to “normal” mode. In ML mode the left mouse button triggers CONTROL_ML_LBUTTON instead of CONTROL_LBUTTON. The left/right keys are now used for strafing, so instead of CONTROL_ROT_LEFT and CONTROL_ROT_RIGHT(for turning), CONTROL_LEFT and CONTROL_RIGHT are triggered.

In “normal” mode CONTROL_LEFT and CONTROL_RIGHT can be triggered by holding down shift and using the left/right keys.


default {
    state_entry() {
        llRequestPermissions(llGetOwner(), PERMISSION_TAKE_CONTROLS); // get permission to take controls
    run_time_permissions(integer perm) { // permissions dialog answered
        if (perm & PERMISSION_TAKE_CONTROLS) { // we got a yes
            llTakeControls(CONTROL_UP | CONTROL_DOWN, TRUE, FALSE); // take up and down controls
    control(key id, integer held, integer change) { // something happened to one of our controls
        if (held & CONTROL_UP) { // the "fly up" key is held
            llSetPos(llGetPos() + <0, 0, 0.25>); // move up
        } else if (change & held & CONTROL_DOWN) { // the "fly down" key was pressed
            llSetPos(llGetPos() + <0, 0, -0.25>); // move down


Q: What are the keyboard keys for CONTROL_LEFT and CONTROL_RIGHT? Do they exist?
A: If you hold down the shift key and use left or right when NOT in mouselook, or if you move left or right in mouselook. (Shift-A/D when not in mouselook, A/D in mouselook)