secondlife / secondlife/viewer

Game Control: semantic data coming from viewer does not update when viewer has custom game control mappings

Open
#6,302 0 comments 0 reactions 1 assignee View on GitHub

@AndrewMeadows is already working on this.

Since Sep 10, 2026.

bug triage
Dominant language
C++
Stars
299
Forks
146
Avg merge
1d 9h
Merged PRs (30d)
88

Description

Environment

Second Life Project Game-control 26.4.0.33903086239 (64bit)
Release Notes

You are at 166.5, 132.3, 23.0 in MTester Playground located at simhost-0f95d1f4c0da07fb8.aditi
SLURL: secondlife://Aditi/secondlife/MTester%20Playground/166/132/23
(global coordinates 678.5, 644.3, 23.0)
MercuryTest 2026-09-09.34397453303
Release Notes

CPU: Apple M1 Pro (2400 MHz)
Memory: 16384 MB
OS Version: macOS 26.6.2 Darwin 25.6.0 Darwin Kernel Version 25.6.0: Fri Jul 31 19:18:49 PDT 2026; root:xnu-12377.161.14~5/RELEASE_ARM64_T6000 arm64
Graphics Card Vendor: Apple
Graphics Card: Apple M1 Pro

OpenGL Version: 4.1 Metal - 90.5

Window size: 1230x805
Font Size Adjustment: 96pt
UI Scaling: 0.75
Draw distance: 216m
Bandwidth: 10000kbit/s
LOD factor: 1.375
Render quality: 3
Texture memory: 12124MB
Disk cache: Max size 2150.4 MB (99.9% used)
HiDPI display mode: false

J2C Decoder Version: KDU v8.4.1
Audio Driver Version: OpenAL, version 1.1 ALSOFT 1.24.2 / OpenAL Community / OpenAL Soft: OpenAL Soft
Dullahan: 1.26.0.202510161627
CEF: 139.0.40+g465474a+chromium-139.0.7258.139
Chromium: 139.0.7258.139
LibVLC Version: 3.0.21
Voice Server Version: Secondlife WebRTC Gateway 1.3.2-90f4ecf/estate

Packets Lost: 10/85819 (0.0%)
September 10 2026 15:05:48

Description

The semantic data sent by the viewer's game control event does not reflect custom button mappings set in viewer preferences. This is observed by llGetGameControlModeButtons() returning the same value as integer buttons in the game_control() event.

Reproduction steps
  1. Login with the game control viewer to a simulator which supports it.
  2. Open Preferences -> Game Control -> Server Data, and ensure that 'Send GameControl Data to server' is enabled.
  3. Open Preferences -> Game Control -> Action Mappings, Click 'Restore Defaults' then 'OK'
  4. Rez a box and add the 'game control events' script shown below to it
  5. Say 'perms' in chat, and accept the permissions request
  6. Enter the default 3rd person standing mode ( GAME_CONTROL_MODE_AVATAR )
  7. Tap the crouch button ('C' ) and note the output from the script
  8. Open Preferences -> Game Control -> Action Mappings, and change the Crouch button in 'When moving avatar' from the default 'East' to 'Misc1', then click 'OK'

Expected results:

  • In (7), pressing 'C' for the crouch event causes both integer buttons and llGetGameControlModeButtons() to return "EAST" (aka "B") - this is expected since the user has default mappings active. Here's an example of expected behavior:
game_control from Maestro Linden:
    buttons mask = 0x2 = ["B-EAST"]
    changed buttons = 0x2 = ["B-EAST"]
    axes = [0.000000,0.000000,0.000000,0.000000,-0.499969,0.000000]
    llGetGameControlModeButtons() = 0x2 = ["B-EAST"]
    changed llGetGameControlModeButtons = 0x2 = ["B-EAST"]
    llGetGameControlModeButtons vs buttons difference = 0x0 = []
    llGetGameControlModeAxes() = [0.000000,0.000000,0.000000,0.000000,-0.499969,0.000000,0.000000]
  • In (9), with the custom mapping, pressing 'C' for the crouch event should cause integer buttons to show "Misc1", but llGetGameControlModeButtons() should return "EAST" which is the default crouch button.
  • In general, it seems that llGetGameControlModeButtons() and integer buttons are always the same value

Actual results:

  • I see expected results in step (7)
  • In step (9), both integer buttons and llGetGameControlModeButtons() show "Misc1":
game_control from Maestro Linden:
    buttons mask = 0x8000 = ["Misc1"]
    changed buttons = 0x8000 = ["Misc1"]
    axes = [0.000000,0.000000,0.000000,0.000000,-0.499969,0.000000]
    llGetGameControlModeButtons() = 0x8000 = ["Misc1"]
    changed llGetGameControlModeButtons = 0x8000 = ["Misc1"]
    llGetGameControlModeButtons vs buttons difference = 0x0 = []
    llGetGameControlModeAxes() = [0.000000,0.000000,0.000000,0.000000,-0.499969,0.000000,0.000000]
game control events
// when quiet=TRUE, only print game_control events with button transitions
// when quiet=FALSE, game_control events with no button changes (which should generally be joystick movement events)
integer quiet = FALSE;
list seated_agents;
integer past_buttons;

integer past_get_buttons;

integer old_mode = -5; // placeholder

list button_labels = [
    GAME_CONTROL_BUTTON_SOUTH, "A-SOUTH",
    GAME_CONTROL_BUTTON_EAST, "B-EAST",
    GAME_CONTROL_BUTTON_WEST, "X-WEST",
    GAME_CONTROL_BUTTON_NORTH, "Y-NORTH",
    GAME_CONTROL_BUTTON_SELECT, "Select-Back", 
    GAME_CONTROL_BUTTON_HOME, "Home-Guide-Xbox",
    GAME_CONTROL_BUTTON_START, "Start/Menu",
    GAME_CONTROL_BUTTON_LEFTSTICK, "Left stick",
    GAME_CONTROL_BUTTON_RIGHTSTICK, "Right stick",
    GAME_CONTROL_BUTTON_LEFTSHOULDER, "Left shoulder",
    GAME_CONTROL_BUTTON_RIGHTSHOULDER, "Right shoulder",   
    GAME_CONTROL_BUTTON_DPAD_UP, "D-pad up",
    GAME_CONTROL_BUTTON_DPAD_DOWN, "D-pad down",
    GAME_CONTROL_BUTTON_DPAD_LEFT, "D-pad left",
    GAME_CONTROL_BUTTON_DPAD_RIGHT, "D-pad right",
    // Note: constants below this line are not testable on a default-configured Xbox controller
    GAME_CONTROL_BUTTON_MISC1, "Misc1",
    GAME_CONTROL_BUTTON_PADDLE1, "Paddle1",
    GAME_CONTROL_BUTTON_PADDLE2, "Paddle2",
    GAME_CONTROL_BUTTON_PADDLE3, "Paddle3",
    GAME_CONTROL_BUTTON_PADDLE4, "Paddle4",
    GAME_CONTROL_BUTTON_TOUCHPAD, "Touchpad"
];

string bits2nybbles(integer bits)
{
    integer lsn; // least significant nybble
    string nybbles = "";
    do
        nybbles = llGetSubString("0123456789ABCDEF", lsn = (bits & 0xF), lsn) + nybbles;
    while (bits = (0xfffFFFF & (bits >> 4)));
    return nybbles;
}
 
string bitmaskToButtonNames(integer mask)
{
    // Resolve a bitmask of buttons to their names
    // because the button_labels integer constants are densely packed, can simply bit-shift to resolve all buttons in the mask
    integer i;
    list buttons;
    while( mask )
    {
        if(mask & 0x1)
        {
            if(llGetListLength(button_labels) > i*2 + 1)
            {
                buttons += llList2String(button_labels, i*2 + 1);
            }
            else
            {
                buttons += "Undefined bit at position " + (string)i;
            }
            //llSay(0, "mask=" + (string)mask + " buttons: " + llList2Json(JSON_ARRAY, buttons));
        }
        mask = mask >> 1;
        i++;
    }
    return llList2Json(JSON_ARRAY, buttons);
}

string mode_name(integer mode)
{
    if (mode == GAME_CONTROL_MODE_AVATAR)    return "AVATAR";
    if (mode == GAME_CONTROL_MODE_MOUSELOOK) return "MOUSELOOK";
    if (mode == GAME_CONTROL_MODE_FLYCAM)    return "FLYCAM";
    if (mode == GAME_CONTROL_MODE_CAPTIVE)   return "CAPTIVE";
    if (mode == GAME_CONTROL_MODE_CURSOR)    return "CURSOR";
    if (mode == -1)                          return "NONE";
    return "UNKNOWN(" + (string)mode + ")";
}
 
default
{
    state_entry()
    {
        integer i = llGetNumberOfPrims();
        llSay(0, "Setting sit targets on " + (string)i + " prims. Say 'perms' to get permissions request");
        while(i--)
        {
            llLinkSitTarget(i, <0,0,1>, ZERO_ROTATION);
        }
        llListen(0, "", NULL_KEY, "perms"); // any agent can request perms
    }
    
    listen(integer channel, string name, key id, string msg)
    {
        if(llGetAgentSize(id))
        {
            llSay(0, "Requesting PERMISSION_GAME_CONTROL on " + name);
            llRequestPermissions(id, PERMISSION_GAME_CONTROL);
        }
    }
    
    run_time_permissions(integer p)
    {
        if(p & PERMISSION_GAME_CONTROL)
        {
            llSay(0, "Got PERMISSION_GAME_CONTROL from " + llKey2Name(llGetPermissionsKey()));
        }
        else
        {
            llSay(0, "Permissions request denied.");
        }
    }
        
    
    attach(key id)
    {
        if(id == NULL_KEY)
        {
            llSay(0, "Detached from agent.");
        }
        else
        {
            llSay(0, "Attached to agent " + (string)id);
        }
    }
    
    changed(integer change)
    {
        // announce when agents stand up or sit on the linkset
        if(change & CHANGED_LINK)
        {
            integer total_seated = llList2Integer(llGetObjectDetails(llGetKey(), [OBJECT_SIT_COUNT]), 0);
            integer i;
            list new_seated_agents;
            
            for(i = llGetNumberOfPrims() - total_seated + 1; i <= llGetNumberOfPrims(); i++)
            {
                key agent = llGetLinkKey(i);
                if(llListFindList(seated_agents, [agent]) == -1)
                {
                    llSay(0, "agent " + (string)agent + " has just sat down.");
                }
                new_seated_agents += agent;
            }
            
            for(i = 0; i < llGetListLength(seated_agents); i++)
            {
                key agent = llList2Key(seated_agents, i);
                if(llListFindList(new_seated_agents, [agent]) == -1)
                {
                    llSay(0, "agent " + (string)agent + " has just stood up.");
                }
            }
            
            seated_agents = new_seated_agents;
            
            llSay(0, "current seated agent list: " + llList2Json(JSON_ARRAY, seated_agents));
        }
    }
    
    game_control(key id, integer buttons, list axes)
    {
        integer mode = llGetGameControlMode(id);
        if (mode != old_mode)
        {
            llSay(0, "mode change: " + mode_name(old_mode) + " --> " + mode_name(mode));
            old_mode = mode;
        }
        
        string controls_message;
        
        if(buttons != past_buttons || !quiet)
        {
            string button_levels_hex = bits2nybbles(buttons);
            string button_edges_hex = bits2nybbles(buttons ^ past_buttons);
            
            controls_message += "game_control from " + llKey2Name(id)
                + ":\n\tbuttons mask = 0x" + button_levels_hex + " = " + bitmaskToButtonNames(buttons)
                + "\n\tchanged buttons = 0x" + button_edges_hex + " = " + bitmaskToButtonNames(buttons ^ past_buttons)
                + "\n\taxes = " + llList2Json(JSON_ARRAY, axes);
            past_buttons = buttons;
        }
        
        integer get_buttons = llGetGameControlModeButtons(id);
        if(get_buttons != past_get_buttons || !quiet)
        {
            string button_levels_hex = bits2nybbles(get_buttons);
            // difference between new llGetGameControlModeButtons() and last value
            string button_edges_hex = bits2nybbles(get_buttons ^ past_get_buttons);
            // difference between llGetGameControlModeButtons() and buttons returned in event
            string button_mode_hex = bits2nybbles(get_buttons ^ buttons);
            
            controls_message += "\n\t\llGetGameControlModeButtons() = 0x" + button_levels_hex + " = " + bitmaskToButtonNames(get_buttons)
                + "\n\tchanged llGetGameControlModeButtons = 0x" + button_edges_hex + " = " + bitmaskToButtonNames(get_buttons ^ past_get_buttons)
                + "\n\tllGetGameControlModeButtons vs buttons difference = 0x" + button_mode_hex + " = " + bitmaskToButtonNames(get_buttons ^ buttons)
                + "\n\tllGetGameControlModeAxes() = " + llList2Json(JSON_ARRAY, llGetGameControlModeAxes(id));
            past_buttons = buttons;
        }
        if(controls_message) llSay(0, controls_message);
    }
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.