Difference between revisions of "HTML HUD Demo"

From Second Life Wiki
Jump to: navigation, search
(Added image (collage of 8 HUD screenshots). Resulting page layout may be suboptimal, but I couldn't do better.)
(added formatted copy, does the same thing)
Line 1: Line 1:
 
  Suggest a "Stop All Animations" button be added to Animations selection frame for convenience.
 
  Suggest a "Stop All Animations" button be added to Animations selection frame for convenience.
 +
 +
There are two versions of this script. The original by Kelly Linden above and a copy from Kireji Haiku below, who hasn't added anything to the script but reformatted it for better readability.
  
 
[[File:HTML HUD Demo.jpg|200px|thumb|top|screenshots]]
 
[[File:HTML HUD Demo.jpg|200px|thumb|top|screenshots]]
Line 470: Line 472:
 
     }
 
     }
 
}</lsl>
 
}</lsl>
 +
 +
<lsl>
 +
/*
 +
*  HTML-based, single script HUD
 +
*
 +
*  To use:
 +
*  - create a default prim (cube)
 +
*  - wear it as a HUD on top_left (script needs tweaking for other attachment points)
 +
*  - edit the cube while wearing
 +
*  - add animations you want to use
 +
*  - add notecards and objects you want to hand out
 +
*  - add this script
 +
*
 +
*  License:
 +
*    This script itself is free to share, modify and use without restriction.
 +
*    Any linked or referenced files are not included in this license and are licensed by their respective owners under their own respective copyright and other licenses.
 +
*    original by Kelly Linden and reformatted by Kireji Haiku, 2011.
 +
*/
 +
 +
key owner;
 +
string ownerName;
 +
 +
string url;
 +
 +
key currentRequestID;
 +
 +
integer responseStatus;
 +
string responseBody;
 +
 +
list lastPath;
 +
 +
string video_url;
 +
 +
string header;
 +
string footer;
 +
 +
string currentAnimation;
 +
 +
integer isVisible;
 +
 +
string exceptions;
 +
 +
/*
 +
*  user-function: init
 +
*  - does not return anything
 +
*  - sets initial variable values
 +
*  - sets object's name and textures
 +
*  - request a url to use the HUD
 +
*/
 +
 +
init()
 +
{
 +
    owner = llGetOwner();
 +
    ownerName = llKey2Name(owner);
 +
    llSetObjectName(ownerName+"'s HUD");
 +
 +
    video_url = "http://www.youtube.com/embed/m7p9IEpPu-c?rel=0";
 +
 +
    //header set in set_link_media(url)
 +
    //footer set in set_link_media(url)
 +
 +
    llSetLinkPrimitiveParamsFast(LINK_THIS, [
 +
        PRIM_TEXTURE, ALL_SIDES, TEXTURE_BLANK, <1.0, 1.0, 1.0>, ZERO_VECTOR, (float)FALSE,
 +
        PRIM_TEXTURE, 2, "0b815b79-c8f5-fc98-91fc-e77b53a468e2", <1.0, 1.0, 1.0>, ZERO_VECTOR, (float)FALSE]);
 +
 +
    toggle_visibility_of_HUD_button();
 +
    request_url();
 +
}
 +
 +
/*
 +
*  user-function: toggle_visibility_of_HUD_button
 +
*  - does not return anything
 +
*  - toggle the visibility of the prim
 +
*  - will rotate, position and scale the prim
 +
*/
 +
 +
toggle_visibility_of_HUD_button()
 +
{
 +
    if (isVisible)
 +
        llSetLinkPrimitiveParamsFast(LINK_THIS, [
 +
            PRIM_POS_LOCAL, <0.0, -0.13, -0.13>,
 +
            PRIM_ROT_LOCAL, <0.0, 0.0, 0.0, 1.0>,
 +
            PRIM_SIZE, <0.01, 0.25, 0.25>]);
 +
    else
 +
        llSetLinkPrimitiveParamsFast(LINK_THIS, [
 +
            PRIM_POS_LOCAL, <0.0, -0.04, -0.04>,
 +
            PRIM_ROT_LOCAL, <0.0, 0.0, -1.0, 0.0>,
 +
            PRIM_SIZE, <0.05, 0.05, 0.05>]);
 +
 +
    isVisible = !isVisible;
 +
}
 +
 +
/*
 +
*  user-function: request url
 +
*  - does not return anything
 +
*  - make sure we drop the old url before requesting a new one
 +
*/
 +
 +
request_url()
 +
{
 +
    llReleaseURL(url);
 +
    llRequestURL();
 +
}
 +
 +
/*
 +
*  user-function: set_link_media
 +
*  - does not return anything
 +
*  - set the values for the string variables 'header' and 'footer'
 +
*  - prepare face 4 for media on a prim
 +
*/
 +
 +
set_link_media(string scriptUrl)
 +
{
 +
    url = scriptUrl;
 +
 +
    header = "<html><head><link href='https://d1979ns0fqtj19.cloudfront.net/assets/common-103828347986224535963905120979424958961.css' media='all' rel='stylesheet' type='text/css' /><base href='" + scriptUrl + "/' /></head><body>";
 +
 +
    footer = "<div align='center' style='position:absolute;top:93%;left:8%;'><a href=''>Scan</a> |
 +
    <a href='anims'>Anims</a> | <a href='video'>Video</a> | <a href='config'>Config</a> | <a href='hide'>Hide</a></div><script src='https://d2mjw3k7q9u8rb.cloudfront.net/assets/common-170919042270376442559931151451605602726.js' type='text/javascript'></script></body></html>";
 +
 +
    llSetLinkMedia(LINK_THIS, 4, [
 +
                            PRIM_MEDIA_AUTO_PLAY, TRUE,
 +
                            PRIM_MEDIA_CURRENT_URL, url,
 +
                            PRIM_MEDIA_HOME_URL, url,
 +
                            PRIM_MEDIA_HEIGHT_PIXELS, 256,
 +
                            PRIM_MEDIA_WIDTH_PIXELS, 256,
 +
                            PRIM_MEDIA_PERMS_CONTROL, PRIM_MEDIA_PERM_NONE]);
 +
}
 +
 +
/*
 +
*  user-function:
 +
*  - does not return anything
 +
*  - used if there's not a request to the homepage of our website
 +
*  - check where on the site we are and prepare variables for the response
 +
*/
 +
 +
http_get(key requestID, list path)
 +
{
 +
    currentRequestID = requestID;
 +
 +
    integer numOfPathsParts = llGetListLength(path);
 +
    string firstPathPart = llList2String(path, 0);
 +
 +
    if (firstPathPart == "hide")
 +
    {
 +
        toggle_visibility_of_HUD_button();
 +
        http_get(requestID, lastPath);
 +
        return;
 +
    }
 +
 +
    lastPath = path;
 +
 +
    if (firstPathPart == "agent")
 +
    {
 +
        if (numOfPathsParts == 1)
 +
        {
 +
            prepare_profile_overview_page(requestID, owner);
 +
            return;
 +
        }
 +
        else if (numOfPathsParts == 2)
 +
        {
 +
            key id = (key)llList2String(path, 1);
 +
            prepare_profile_overview_page(requestID, id);
 +
            return;
 +
        }
 +
        else if (numOfPathsParts == 4)
 +
        {
 +
            if (llList2String(path, 2) != "give")
 +
                return;
 +
 +
            key id = (key)llList2String(path, 1);
 +
            string name = llKey2Name(id);
 +
            string itemName = llUnescapeURL(llList2String(path, 3));
 +
 +
            llOwnerSay("Giving '" + itemName + "' to '" + name + "'.");
 +
            llGiveInventory(id, itemName);
 +
            prepare_profile_overview_page(requestID, id);
 +
            return;
 +
        }
 +
    }
 +
    else if (firstPathPart == "anims")
 +
    {
 +
        if (numOfPathsParts == 1)
 +
        {
 +
            anims_page();
 +
            return;
 +
        }
 +
        else if (numOfPathsParts == 2)
 +
        {
 +
            play_anim(llList2String(path, 1));
 +
            anims_page();
 +
            return;
 +
        }
 +
    }
 +
    else if (firstPathPart == "video")
 +
    {
 +
        responseStatus = 200;
 +
        responseBody = header + "<br><iframe width='255' height='173' src='" + video_url + "' frameborder='0' allowfullscreen></iframe>" + footer;
 +
        return;
 +
    }
 +
    else if (firstPathPart == "config")
 +
    {
 +
        if (numOfPathsParts == 1)
 +
        {
 +
            config_page();
 +
            return;
 +
        }
 +
        else if (llList2String(path, 1) == "set")
 +
        {
 +
            string queryString = llGetHTTPHeader(requestID, "x-query-string");
 +
            list args = llParseString2List(queryString, ["="], ["&"]);
 +
            integer index = ~llGetListLength(args);
 +
            while (index)
 +
            {
 +
                string variable = llList2String(args, index);
 +
                string value = llUnescapeURL(llList2String(args, index + 1));
 +
                if (variable == "video")
 +
                    video_url = value;
 +
 +
                //because: var,val,&,var,val,&,...
 +
                index = index + 3;
 +
            }
 +
            config_page();
 +
        }
 +
    }
 +
 +
    responseStatus = 404;
 +
    responseBody = header + "<h1>404 Page Not Found.</h1>" + footer;
 +
    throw_exception("There has been a HTTP-request to a non-existant page on your HUD's website. Please check the path of the request for mistakes.");
 +
}
 +
 +
/*
 +
*  user-function: prepare_profile_overview_page
 +
*  - does not return anything
 +
*  - prepares a response for a page with information about a certain avatar
 +
*  - includes profile thumbnail, name, script info, give menu
 +
*/
 +
 +
prepare_profile_overview_page(key requestID, key id)
 +
{
 +
    list avatarDetails = llGetObjectDetails(id, [
 +
                                OBJECT_POS,
 +
                                OBJECT_TOTAL_SCRIPT_COUNT,
 +
                                OBJECT_SCRIPT_MEMORY,
 +
                                OBJECT_SCRIPT_TIME]);
 +
 +
    responseStatus = 200;
 +
    responseBody = header + "<table border='0' cellspacing='1' cellpadding='1'><tr><td colspan='2' style='white-space:nowrap'><div class='profile_title'><h1 id='display_name'>" + html_body_with_formatted_avatar_name(id) + "</h1><h2 id='username'>" + llGetUsername(id) + "</h2></div></td><td><div align='right'>" + html_body_with_links_for_interaction_with_certain_avatar(id) + "<br>" + html_body_with_inventory_overview_for_give_menu(id) + "</div></td></tr><tr><td width='80'>" + html_body_avatar_profile_pic_thumbnail(id) + "</td><td colspan='2'><ul style='list-style-type:circle'><li>Scripts:<ul style='list-style-type:disc;margin-left:10px'><li>" + (string)llList2Integer(avatarDetails, 1) + " total</li><li>" + bytes2str(llList2Integer(avatarDetails, 2)) + "</li><li>" + (string)((integer)(llList2Float(avatarDetails, 3) * 1000000.0)) + "us</li></ul></li></ul></td></tr></table>" + footer;
 +
}
 +
 +
/*
 +
*  user-function: html_body_with_formatted_avatar_name
 +
*  - returns the name of the avatar in html format
 +
*  - removes the lastname if it is Resident
 +
*  - adds a line-break for long names
 +
*/
 +
 +
string html_body_with_formatted_avatar_name(key id)
 +
{
 +
    string stringToReturn = llKey2Name(id);
 +
 +
    if (llGetSubString(stringToReturn, -9, -1) == " Resident")
 +
        stringToReturn = llDeleteSubString(stringToReturn, -9, -1);
 +
 +
    if (15 < llStringLength(stringToReturn))
 +
        stringToReturn = llDumpList2String(llParseString2List(stringToReturn,[" "], []), "<br>");
 +
 +
    return stringToReturn;
 +
}
 +
 +
/*
 +
*  user-function: html_body_with_links_for_interaction_with_certain_avatar
 +
*  - returns html text with links for an avatar to interact with who has a certain uuid
 +
*/
 +
 +
string html_body_with_links_for_interaction_with_certain_avatar(key id)
 +
{
 +
    return "<div class='menu_button'><a class='button call_to_action'>Options</a><ul style='list-style-type:none;text-align:left' class='menu'><li><a href='secondlife:///app/agent/" + (string)id + "/im'>IM</a></li><li><a href='secondlife:///app/agent/" + (string)id + "/offerteleport'>Offer Teleport</a></li><li><a href='secondlife:///app/maptrackavatar/" + (string)id + "'>Map</a></li><li><a href='secondlife:///app/sharewithavatar/" + (string)id + "'>Share</a></li><li><a href='secondlife:///app/agent/" + (string)id + "/pay'>Pay</a></li></ul></div>";
 +
}
 +
 +
/*
 +
*  user-function: html_body_with_inventory_overview_for_give_menu
 +
*  - returns html text with inventory item lists
 +
*/
 +
 +
string html_body_with_inventory_overview_for_give_menu(key id)
 +
{
 +
    string stringToReturn = "<div class='menu_button'><a class='button call_to_action'>Give</a><ul style='list-style-type:none;text-align:left;white-space:nowrap' class='menu'>";
 +
 +
    integer sizeOfStringBefore = llStringLength(stringToReturn);
 +
 +
    stringToReturn += inventory_list_in_html_format_for_give_menu(id, INVENTORY_NOTECARD);
 +
    stringToReturn += inventory_list_in_html_format_for_give_menu(id, INVENTORY_OBJECT);
 +
 +
    if (llStringLength(stringToReturn) == sizeOfStringBefore)
 +
        stringToReturn += "<li>(no objects or notecards found)</li>";
 +
 +
    stringToReturn += "</ul></div>";
 +
    return stringToReturn;
 +
}
 +
 +
/*
 +
*  user-function: bytes2str
 +
*  - returns a string with script memory info in readable format
 +
*/
 +
 +
string bytes2str(integer bytes)
 +
{
 +
    // 1024² = 1048576
 +
 +
    if (bytes < 1048576)
 +
        return (string)(bytes / 1024) + " KB";
 +
    //else
 +
        return (string)(bytes / 1048576) + " MB";
 +
}
 +
 +
/*
 +
*  user-function: html_body_avatar_profile_pic_thumbnail
 +
*  - returns html text with urls to someone's profile pic
 +
*/
 +
 +
string html_body_avatar_profile_pic_thumbnail(key id)
 +
{
 +
    return "<a href='secondlife:///app/agent/" + (string)id + "/about' class='avatar avatar_thumb' rel='#sl_image_zoom' title='Click to zoom'<img alt='Thumb_sl_image' src='https://my-secondlife.s3.amazonaws.com/users/" + llGetUsername(id) + "/sl_image.png' /></a>";
 +
}
 +
 +
/*
 +
*  user-function: inventory_list_in_html_format_for_give_menu
 +
*  - returns html text with inventory item list of given type
 +
*/
 +
 +
string inventory_list_in_html_format_for_give_menu(key id, integer type)
 +
{
 +
    string stringToReturn;
 +
 +
    integer index = llGetInventoryNumber(type);
 +
    while (index)
 +
    {
 +
        --index;
 +
 +
        string name = llGetInventoryName(type, index);
 +
 +
        stringToReturn += "<li><a href='agent/" + (string)id + "/give/" + name + "'>" + name + "</a></li>";
 +
    }
 +
 +
    return stringToReturn;
 +
}
 +
 +
/*
 +
*  user-function: anims_page
 +
*  - does not return anything
 +
*  - prepares an html text overview page of animations
 +
*/
 +
 +
anims_page()
 +
{
 +
    responseStatus = 200;
 +
    responseBody = header + "<h1>Animations</h1><h2>Choose an animation:</h2><div style='margin-left:40px'>" + html_body_animations_overview() + "</div><br><br>" + footer;
 +
}
 +
 +
/*
 +
*  user-function: html_body_animations_overview
 +
*  - returns html text with a list of included animations
 +
*/
 +
 +
string html_body_animations_overview()
 +
{
 +
    string stringToReturn = "<div class='menu_button' style='align:center'><a class='button call_to_action'>Animate<b class='actions_dropdown'>&nbsp;</b></a><ul style='list-style-type:none;text-align:left' class='menu'>";
 +
 +
    integer index = llGetInventoryNumber(INVENTORY_ANIMATION);
 +
 +
    if (!index)
 +
        stringToReturn += "<li>(no animations found)</li>";
 +
 +
    else while (index)
 +
    {
 +
        --index;
 +
 +
        string name = llGetInventoryName(INVENTORY_ANIMATION, index);
 +
 +
        stringToReturn += "<li><a href='anims/" + name + "'>" + name + "</a></li>";
 +
    }
 +
 +
    stringToReturn += "</ul></div>";
 +
 +
    return stringToReturn;
 +
}
 +
 +
/*
 +
*  user-function: play_anim
 +
*  - does not return anything
 +
*  - prompts a perms request to animate owner
 +
*/
 +
 +
play_anim(string anim)
 +
{
 +
    llRequestPermissions(owner, PERMISSION_TRIGGER_ANIMATION);
 +
    currentAnimation = llUnescapeURL(anim);
 +
}
 +
 +
/*
 +
*  user-function: config_page
 +
*  - does not return anything
 +
*  - prepares page to configure youtube video link
 +
*/
 +
 +
config_page()
 +
{
 +
    responseStatus = 200;
 +
    responseBody = header + "<h1>Options:</h1><form action='config/set' method='get'>Video URL: <input type='text' name='video' value='" + video_url + "' /><input type='submit' value='Set' /></form>" + footer;
 +
}
 +
 +
/*
 +
*  user-function: throw_exception
 +
*  - does not return anything
 +
*  - logs errors into cache for later viewing and debugging
 +
*/
 +
 +
throw_exception(string inputString)
 +
{
 +
    if (exceptions == "")
 +
        exceptions = "The following un-handled exception(s) occurred that are preventing this device's operation:\n";
 +
 +
    exceptions += "\t"+inputString+"\n";
 +
}
 +
 +
default
 +
{
 +
    on_rez(integer start_param)
 +
    {
 +
        llReleaseURL(url);
 +
        llResetScript();
 +
    }
 +
 +
    changed(integer change)
 +
    {
 +
        if (change & (CHANGED_OWNER | CHANGED_INVENTORY))
 +
        {
 +
            llReleaseURL(url);
 +
            llResetScript();
 +
        }
 +
 +
        if (change & (CHANGED_REGION | CHANGED_REGION_START | CHANGED_TELEPORT))
 +
            request_url();
 +
    }
 +
 +
    state_entry()
 +
    {
 +
        init();
 +
    }
 +
 +
    touch_start(integer num_detected)
 +
    {
 +
        toggle_visibility_of_HUD_button();
 +
    }
 +
 +
    http_request(key id, string method, string body)
 +
    {
 +
        integer sendResponseNow = TRUE;
 +
 +
        responseStatus = 400;
 +
        responseBody = "Unsupported method";
 +
 +
        if (method == URL_REQUEST_GRANTED)
 +
        {
 +
            responseStatus = 200;
 +
            responseBody = "OK";
 +
 +
            set_link_media(body);
 +
        }
 +
        else if (method == URL_REQUEST_DENIED)
 +
        {
 +
            responseStatus = 400;
 +
            responseBody = "Bad request";
 +
 +
            throw_exception("The following error occurred while attempting to get a free URL for this device:\n \n" + body);
 +
        }
 +
        else if (method == "GET")
 +
        {
 +
            responseStatus = 200;
 +
            responseBody = "GET";
 +
 +
            string pathInfoHeader = llGetHTTPHeader(id, "x-path-info");
 +
            list path = llParseString2List(pathInfoHeader, ["/"], []);
 +
 +
            if (path == [])
 +
            {
 +
                sendResponseNow = FALSE;
 +
                llSensor("", NULL_KEY, AGENT_BY_LEGACY_NAME, 96.0, PI);
 +
                lastPath = [];
 +
            }
 +
            else
 +
                http_get(id, path);
 +
        }
 +
 +
/*
 +
*  check if doing a sensor sweep and if so don't send a response
 +
*  but send the response in 'sensor event' or 'no_sensor event'
 +
*
 +
*  (time-out 30.0 seconds for response)
 +
*/
 +
 +
        if (sendResponseNow)
 +
        {
 +
            llSetContentType(id, CONTENT_TYPE_HTML);
 +
            llHTTPResponse(id, responseStatus, responseBody);
 +
        }
 +
 +
        if (exceptions != "")
 +
            state error;
 +
    }
 +
 +
    run_time_permissions(integer perm)
 +
    {
 +
        if (perm & PERMISSION_TRIGGER_ANIMATION)
 +
        {
 +
            llStartAnimation(currentAnimation);
 +
        }
 +
        else
 +
            throw_exception("This HUD has tried to animate your avatar WITHOUT having the permissions to do so. You must grant this HUD permissions to animate your avatar for this feature to work.");
 +
 +
        if (exceptions != "")
 +
            state error;
 +
    }
 +
 +
    sensor(integer num_detected)
 +
    {
 +
        responseStatus = 200;
 +
        responseBody = header + "<h2>Scan Results:</h2><ul style='list-style-type:circle;margin-left:20px'>";
 +
 +
        if (14 < num_detected)
 +
            num_detected = 14;
 +
 +
        while (num_detected)
 +
        {
 +
            --num_detected;
 +
 +
            key id = llDetectedKey(num_detected);
 +
 +
            responseBody += "<li><a href='agent/" + (string)id + "'>" + html_body_with_formatted_avatar_name(id) + "</a></li>";
 +
        }
 +
 +
        responseBody += "</ul>" + footer;
 +
 +
        llSetContentType(currentRequestID, CONTENT_TYPE_HTML);
 +
        llHTTPResponse(currentRequestID, responseStatus, responseBody);
 +
    }
 +
 +
    no_sensor()
 +
    {
 +
        responseStatus = 200;
 +
        responseBody = header + "<h2>Scan Results:</h2><ul style='list-style-type:circle;margin-left:20px'><li>No one near by.</li><li>Owner: <a href='agent/" + (string)owner + "'>" + ownerName + "</a></li></ul>" + footer;
 +
 +
        llSetContentType(currentRequestID, CONTENT_TYPE_HTML);
 +
        llHTTPResponse(currentRequestID, responseStatus, responseBody);
 +
    }
 +
 +
    state_exit()
 +
    {
 +
        llReleaseURL(url);
 +
    }
 +
}
 +
 +
state error
 +
{
 +
    on_rez(integer start_param)
 +
    {
 +
        llResetScript();
 +
    }
 +
 +
    changed(integer change)
 +
    {
 +
        if (change & (CHANGED_OWNER | CHANGED_INVENTORY))
 +
            llResetScript();
 +
    }
 +
 +
    state_entry()
 +
    {
 +
        llOwnerSay("========== ERROR REPORT START ==========");
 +
        llOwnerSay(exceptions);
 +
        llOwnerSay("========== ERROR REPORT END ==========");
 +
        llOwnerSay("Resetting now...");
 +
        llResetScript();
 +
    }
 +
}
 +
</lsl>

Revision as of 06:40, 31 October 2011

Suggest a "Stop All Animations" button be added to Animations selection frame for convenience.

There are two versions of this script. The original by Kelly Linden above and a copy from Kireji Haiku below, who hasn't added anything to the script but reformatted it for better readability.

screenshots

<lsl>// HTML Based, Single Script HUD // This is intended to be a tech demo of what html on a hud allows. // * Interaction with SL world (give items, play animations, scan nearby avatars) // * Simple, low overhead (1 scripts, 1 prim) // * Rich, dynamic interface (images, menus, flash) // Updates and improvements are welcome at http://wiki.secondlife.com/wiki/HTML_HUD_Demo

// To Use: // * Create a cube and wear it as a hud on the top-left. (script will need tweaking for other spots) // * Edit the cube while wearing it and add this script to it. // * Add any animations you want to play to the object // * Add any notecards or objects you want to give away to the object.

// License: // This script itself is free to share, modify and use without restriction. // Any linked or referenced files are not included in this license and are licensed by their respective owners under their own respective copyright and other licenses. // Created by Kelly Linden, 2011.


// CONFIG PARAMS string video_url = "http://www.youtube.com/embed/m7p9IEpPu-c?rel=0";

integer display_face = 4; // The face to display HTML on integer button_face = 2; // The face that shows when hiding the HTML integer link = LINK_THIS; // The prim with media and button.

// Prim params for when media is shown. Rotates and scales to show the media. list visible = [PRIM_POS_LOCAL,<0,-0.13,-0.13>,PRIM_SIZE,<0.01,0.25,0.25>,PRIM_ROT_LOCAL,<0,0,0,1>]; // Prim params for when media is hidden. Rotates and scales to show the button. list button = [PRIM_POS_LOCAL,<0,-0.04,-0.04>,PRIM_SIZE,<0.05,0.05,0.05>,PRIM_ROT_LOCAL,<0,0,-1,0>]; // Initial setup: sets the button texture. list init = [PRIM_TEXTURE,ALL_SIDES,TEXTURE_BLANK,<1,1,1>,<1,1,1>,0,PRIM_TEXTURE,button_face,"0b815b79-c8f5-fc98-91fc-e77b53a468e2",<1,1,1>,<1,1,1>,0];

// INTERNAL VARS // These variables are set by the program. integer is_visible = FALSE; // Whether or not the hud is showing HTML side. list last_path; // Last visited string footer; // Footer: set in set_url string header; // Header: set in set_url key current_request; // HTTP request ID for the current request.

// Toggle between button and HTML toggle_show() {

   if (is_visible)
       llSetLinkPrimitiveParamsFast(link,visible);
   else
       llSetLinkPrimitiveParamsFast(link,button);
   is_visible = !is_visible;

}

// Build an image url from an avatar's key. string av_image(key id) {

   return "https://my-secondlife.s3.amazonaws.com/users/" + llGetUsername(id) + "/thumb_sl_image.png";

}

// Build an url that opens the profile for an avatar string app_profile_url(key id) {

   return "secondlife:///app/agent/" + (string)id + "/about";

}

// Setup required after getting an url. set_url(string url) {

   // Build the common header:
   // * link to the css file
   // * set a base url
   // * html boilerplate code.
   // TODO: Remove dependency on this css file.
   header = "<html><head>"
           + "<link href='https://d1979ns0fqtj19.cloudfront.net/assets/common-103828347986224535963905120979424958961.css' media='all' rel='stylesheet' type='text/css' />"
           + "<base href='" + url + "/' />"
           + "</head><body>";
   // Build the common footer
   // * Navigation menu
   // * JS helper script (for fancy buttons)
   // * html boilerplate code
   // TODO: Remove dependency on the js file
footer = "
"
       + "<a href=>Scan</a> | "
       + "<a href='anims'>Anims</a> | "
       + "<a href='video'>Video</a> | "
       + "<a href='config'>Config</a> | "
+ "<a href='hide'>Hide</a>
"
       + "<script src='https://d2mjw3k7q9u8rb.cloudfront.net/assets/common-170919042270376442559931151451605602726.js' type='text/javascript'></script></body></html>";
   llSetLinkMedia(link, display_face,              // Side to display the media on.
           [PRIM_MEDIA_AUTO_PLAY,TRUE,     // Show this page immediately
            PRIM_MEDIA_CURRENT_URL,url,    // The url if they hit 'home'
            PRIM_MEDIA_HOME_URL,url,       // The url currently showing
            PRIM_MEDIA_HEIGHT_PIXELS,256,  // Height/width of media texture will be
            PRIM_MEDIA_WIDTH_PIXELS,256,   //   rounded up to nearest power of 2.
            PRIM_MEDIA_PERMS_CONTROL, PRIM_MEDIA_PERM_NONE]);

}

// Turn a number of bytes (script size) into human readable format. string bytes2str(integer b) {

   if (b < 1024 * 1024) return (string)(b / 1024) + "KB";
   return (string)(b / (1024 * 1024)) + "MB";

}

// Build the html for the profile thumbnail image that links to their profile. string profile_thumb_html(key agent) {

   return "<a href='" + app_profile_url(agent) + "' class='avatar avatar_thumb' rel='#sl_image_zoom' title='Click to zoom'>"
       + "<img alt='Thumb_sl_image' src='https://my-secondlife.s3.amazonaws.com/users/" + llGetUsername(agent) + "/sl_image.png' /></a>";

}

// Build the agent options menu string agent_menu_html(key agentid) {

return "";

}

// Build an html list of items of type for the Give menu string give_list(key agentid, integer type) {

   string resp;
   integer n = llGetInventoryNumber(type);
   integer i;
   for (i=0;i<n;++i)
   {
resp += "
  • <a href='agent/" + (string)agentid + "/give/" + llGetInventoryName(type,i) + "'>" + llGetInventoryName(type,i) + "</a>
  • ";
       }
       return resp;
    

    }

    // Build the html for the Give menu and fill it with notecards and objects. string give_menu_html(key agentid) {

    string resp = "";
       return resp;
    

    }

    // Strip the "Resident" last name, break long names into 2 lines if possible. string agent_name(key id) {

       string name = llList2String(llParseString2List(llKey2Name(id),[" Resident"],[]),0); 
       if (llStringLength(name) > 15)
       {
           name = llDumpList2String(llParseString2List(name,[" "],[]),"
    "); } return name;

    }

    // Build the Agent Details page. agent_details(key request, key agent) {

       // Get the details we want.
       list r = llGetObjectDetails(agent,[OBJECT_POS,OBJECT_TOTAL_SCRIPT_COUNT,OBJECT_SCRIPT_MEMORY,OBJECT_SCRIPT_TIME]);
    
       // Build the html.
    
    string resp = header + "" + "" + "
    " + agent_menu_html(agent) + "
    " + give_menu_html(agent) + "
    " + profile_thumb_html(agent) + "" + "
      " + "
    • Scripts:" + "
        " + "
      • " + (string)llList2Integer(r,1) + " total
      • " + "
      • " + bytes2str(llList2Integer(r,2)) + "
      • " + "
      • " + (string)((integer)(llList2Float(r,3) * 1000000.0)) + "us
      • " + "
    " + footer;
       // Send the response.
       llSetContentType(current_request, CONTENT_TYPE_HTML);
       llHTTPResponse(current_request,200,resp);
    
       // Uncomment this to debug memory usage.
       // Done here as this is the largest, most complex page.
       // llOwnerSay("Memory used: " + (string)llGetUsedMemory() + ", response size: " + (string)llStringLength(resp));
    

    }

    // Build up an animation menu with any animations in the object. string anim_menu_html() {

    string resp = "";
       return resp;
    

    }

    // Play an animation given the name. play_anim(string anim) {

       llRequestPermissions(llGetOwner(),PERMISSION_TRIGGER_ANIMATION);
       llStartAnimation(llUnescapeURL(anim));
    

    }

    // Build the anims page. anims_page() {

    string resp = header + "

    Animations

    Choose an animation:

    " + "
    " + anim_menu_html() + "


    " + footer;
       llSetContentType(current_request, CONTENT_TYPE_HTML);
       llHTTPResponse(current_request,200,resp);
    

    }

    // Build the configuration page. config_page() {

    string resp = header + "

    Options:

    "
           + "<form action='config/set' method='get'>"
           + "Video URL: <input type='text' name='video' value='" + video_url + "' />"
           + "<input type='submit' value='Set' /></form>"
           + footer;
       llSetContentType(current_request, CONTENT_TYPE_HTML);
       llHTTPResponse(current_request,200,resp);
    

    }

    // Process a get request with a path. GET(key request, list path) {

       current_request = request;
       integer path_segments = llGetListLength(path);
       if (path_segments == 0)
       {
           // Home page is handled in the sensor response.
           llSensor("",NULL_KEY,AGENT,96,PI);
           
           // Set the last_path incase the next request is a hide
           last_path = [];
           return;
       }
    
       string p0 = llList2String(path,0);
       if (p0 == "hide")
       {
           toggle_show();
           
           // We need to send back a page to show otherwise the
           // the next time the hud is shown there will be an error.
           // So we loop back in here with our last path.
           // Effectively this just refreshes the page we were already on.
           GET(request,last_path);
           
           // Do *not* set the last_path here to avoid any infinite loops.
           return;
       }
       
       // Set the last path here, after the recursive hide call.
       last_path = path;
       
       if (p0 == "agent")
       {
           if (path_segments == 1)
           {
               // <url>/agent
               // Show owner details.
               agent_details(request,llGetOwner());
               return;
           }
    
           // p1 should be an agent id
           key agent = (key)llList2String(path,1);
    
           if (path_segments == 2)
           {
               // <url>/agent/<id>
               // Show the details for the agent.
               agent_details(request,agent);
               return;
           }
           
           if (path_segments == 4)
           {
               // <url>/agent/<id>/<cmd>/<option>
               string cmd = llList2String(path,2);
               if (cmd == "give")
               {
                   string object_name = llUnescapeURL(llList2String(path,3));
                   llOwnerSay("Giving " + object_name + " to " + llKey2Name((key)agent));
                   llGiveInventory(agent,object_name);
                   agent_details(request,agent);
                   return;
               }
           }
       }
       else if (p0 == "anims")
       {
           if (path_segments == 1)
           {
               // <url>/anims
               // Show the animation selection page
               anims_page();
               return;
           }
           else if (path_segments == 2)
           {
               // <url>/anims/<anim-name>
               // Play the specified animation
               play_anim(llList2String(path,1));
               anims_page();
               return;
           }
       }
       else if (p0 == "video")
       {
           // <url>/video
           // Play a demo video
           string resp = header
               + "
    <iframe width='255' height='173' src='" + video_url + "' frameborder='0' allowfullscreen></iframe>" + footer; llSetContentType(current_request, CONTENT_TYPE_HTML); llHTTPResponse(current_request,200,resp); return; } else if (p0 == "config") { if (path_segments == 1) { // <url>/config config_page(); return; } if (llList2String(path,1) == "set") { // Parse config args list args = llParseString2List(llGetHTTPHeader(current_request,"x-query-string"),["&","="],[]); integer i = 0; integer argn = llGetListLength(args); while(i < argn) { string var = llList2String(args,i++); string val = llUnescapeURL(llList2String(args,i++)); if (var == "video") { video_url = val; } } config_page(); } } // Unknown page. 404.
    string resp = header + "

    404 Page Not Found.

    " + footer;
       llSetContentType(current_request, CONTENT_TYPE_HTML);
       llHTTPResponse(current_request,404,resp);
    

    }

    default {

       state_entry()
       {
           // Set the object name so inventory offers make sense to the receiver.
           llSetObjectName(llKey2Name(llGetOwner()) + "'s HUD");
           // Init button textures.
           llSetLinkPrimitiveParamsFast(link,init);
           // Reset to a hidden state
           toggle_show();
           // Request an URL.
           llRequestURL();
       }
    
       // We need to reset in the cases because we lose the URL.
       on_rez(integer n) { llResetScript();}
       changed(integer c)
       {
           if (c & (CHANGED_TELEPORT | CHANGED_OWNER | CHANGED_REGION)) llResetScript();
       }
    
       touch_start(integer total_number)
       {
           // Handles the button case.
           toggle_show();
       } 
    
       http_request(key id, string method, string body)
       {
           if (method == URL_REQUEST_GRANTED)
           {
               set_url(body);
               return;
           }
           else if (method == URL_REQUEST_DENIED)
           {
               llOwnerSay("Unable to get url: " + body);
               llSleep(10);
               llRequestURL();
               return;
           }
    
           if (method == "GET")
           {
               list path = llParseString2List(llGetHTTPHeader(id,"x-path-info"),["/"],[]);
               GET(id,path);
               return;
           }
    
           llHTTPResponse(id,400,"Unsupported method.");
           return;
       }
    
       sensor(integer n)
       {
           // This is actually the main page.
           // Shows nearby residents for interacting with.
    
    string resp = header + "

    Scan Results:

      ";
             // TODO: Only fits 14 right now. Figure out how to fit all 16. 
             // TODO: Figure out how to find more than 16.
             if (n > 14) n = 14;
             for (--n; n >= 0; --n)
             {
      
      resp += "
    • <a href='agent/" + (string)llDetectedKey(n) + "'>" + agent_name(llDetectedKey(n)) + "</a>
    • ";
             }
      
      resp += "
    " + footer;
           llSetContentType(current_request, CONTENT_TYPE_HTML);
           llHTTPResponse(current_request,200,resp);
       }
    
       no_sensor()
       {
           // No one nearby.
           // Include a link to the owner's details to make it easier to debug when no one is around.
    
    string resp = header + "

    Scan Results:

      "; resp += "
    • No one near by.
    • "; resp += "
    • Owner: <a href='agent/" + (string)llGetOwner() + "'>" + llKey2Name(llGetOwner()) + "</a>
    • "; resp += "
    " + footer;
           llSetContentType(current_request, CONTENT_TYPE_HTML);
           llHTTPResponse(current_request,200,resp);
       }
    

    }</lsl>

    <lsl> /*

    • HTML-based, single script HUD
    • To use:
    • - create a default prim (cube)
    • - wear it as a HUD on top_left (script needs tweaking for other attachment points)
    • - edit the cube while wearing
    • - add animations you want to use
    • - add notecards and objects you want to hand out
    • - add this script
    • License:
    • This script itself is free to share, modify and use without restriction.
    • Any linked or referenced files are not included in this license and are licensed by their respective owners under their own respective copyright and other licenses.
    • original by Kelly Linden and reformatted by Kireji Haiku, 2011.
    • /

    key owner; string ownerName;

    string url;

    key currentRequestID;

    integer responseStatus; string responseBody;

    list lastPath;

    string video_url;

    string header; string footer;

    string currentAnimation;

    integer isVisible;

    string exceptions;

    /*

    • user-function: init
    • - does not return anything
    • - sets initial variable values
    • - sets object's name and textures
    • - request a url to use the HUD
    • /

    init() {

       owner = llGetOwner();
       ownerName = llKey2Name(owner);
       llSetObjectName(ownerName+"'s HUD");
    
       video_url = "http://www.youtube.com/embed/m7p9IEpPu-c?rel=0";
    
       //header set in set_link_media(url)
       //footer set in set_link_media(url)
    
       llSetLinkPrimitiveParamsFast(LINK_THIS, [
           PRIM_TEXTURE, ALL_SIDES, TEXTURE_BLANK, <1.0, 1.0, 1.0>, ZERO_VECTOR, (float)FALSE,
           PRIM_TEXTURE, 2, "0b815b79-c8f5-fc98-91fc-e77b53a468e2", <1.0, 1.0, 1.0>, ZERO_VECTOR, (float)FALSE]);
    
       toggle_visibility_of_HUD_button();
       request_url();
    

    }

    /*

    • user-function: toggle_visibility_of_HUD_button
    • - does not return anything
    • - toggle the visibility of the prim
    • - will rotate, position and scale the prim
    • /

    toggle_visibility_of_HUD_button() {

       if (isVisible)
           llSetLinkPrimitiveParamsFast(LINK_THIS, [
               PRIM_POS_LOCAL, <0.0, -0.13, -0.13>,
               PRIM_ROT_LOCAL, <0.0, 0.0, 0.0, 1.0>,
               PRIM_SIZE, <0.01, 0.25, 0.25>]);
       else
           llSetLinkPrimitiveParamsFast(LINK_THIS, [
               PRIM_POS_LOCAL, <0.0, -0.04, -0.04>,
               PRIM_ROT_LOCAL, <0.0, 0.0, -1.0, 0.0>,
               PRIM_SIZE, <0.05, 0.05, 0.05>]);
    
       isVisible = !isVisible;
    

    }

    /*

    • user-function: request url
    • - does not return anything
    • - make sure we drop the old url before requesting a new one
    • /

    request_url() {

       llReleaseURL(url);
       llRequestURL();
    

    }

    /*

    • user-function: set_link_media
    • - does not return anything
    • - set the values for the string variables 'header' and 'footer'
    • - prepare face 4 for media on a prim
    • /

    set_link_media(string scriptUrl) {

       url = scriptUrl;
    
       header = "<html><head><link href='https://d1979ns0fqtj19.cloudfront.net/assets/common-103828347986224535963905120979424958961.css' media='all' rel='stylesheet' type='text/css' /><base href='" + scriptUrl + "/' /></head><body>";
    
    footer = "
    <a href=>Scan</a> | <a href='anims'>Anims</a> | <a href='video'>Video</a> | <a href='config'>Config</a> | <a href='hide'>Hide</a>
    <script src='https://d2mjw3k7q9u8rb.cloudfront.net/assets/common-170919042270376442559931151451605602726.js' type='text/javascript'></script></body></html>";
       llSetLinkMedia(LINK_THIS, 4, [
                               PRIM_MEDIA_AUTO_PLAY, TRUE,
                               PRIM_MEDIA_CURRENT_URL, url,
                               PRIM_MEDIA_HOME_URL, url,
                               PRIM_MEDIA_HEIGHT_PIXELS, 256,
                               PRIM_MEDIA_WIDTH_PIXELS, 256,
                               PRIM_MEDIA_PERMS_CONTROL, PRIM_MEDIA_PERM_NONE]);
    

    }

    /*

    • user-function:
    • - does not return anything
    • - used if there's not a request to the homepage of our website
    • - check where on the site we are and prepare variables for the response
    • /

    http_get(key requestID, list path) {

       currentRequestID = requestID;
    
       integer numOfPathsParts = llGetListLength(path);
       string firstPathPart = llList2String(path, 0);
    
       if (firstPathPart == "hide")
       {
           toggle_visibility_of_HUD_button();
           http_get(requestID, lastPath);
           return;
       }
    
       lastPath = path;
    
       if (firstPathPart == "agent")
       {
           if (numOfPathsParts == 1)
           {
               prepare_profile_overview_page(requestID, owner);
               return;
           }
           else if (numOfPathsParts == 2)
           {
               key id = (key)llList2String(path, 1);
               prepare_profile_overview_page(requestID, id);
               return;
           }
           else if (numOfPathsParts == 4)
           {
               if (llList2String(path, 2) != "give")
                   return;
    
               key id = (key)llList2String(path, 1);
               string name = llKey2Name(id);
               string itemName = llUnescapeURL(llList2String(path, 3));
    
               llOwnerSay("Giving '" + itemName + "' to '" + name + "'.");
               llGiveInventory(id, itemName);
               prepare_profile_overview_page(requestID, id);
               return;
           }
       }
       else if (firstPathPart == "anims")
       {
           if (numOfPathsParts == 1)
           {
               anims_page();
               return;
           }
           else if (numOfPathsParts == 2)
           {
               play_anim(llList2String(path, 1));
               anims_page();
               return;
           }
       }
       else if (firstPathPart == "video")
       {
           responseStatus = 200;
           responseBody = header + "
    <iframe width='255' height='173' src='" + video_url + "' frameborder='0' allowfullscreen></iframe>" + footer; return; } else if (firstPathPart == "config") { if (numOfPathsParts == 1) { config_page(); return; } else if (llList2String(path, 1) == "set") { string queryString = llGetHTTPHeader(requestID, "x-query-string"); list args = llParseString2List(queryString, ["="], ["&"]); integer index = ~llGetListLength(args); while (index) { string variable = llList2String(args, index); string value = llUnescapeURL(llList2String(args, index + 1)); if (variable == "video") video_url = value;
                   //because: var,val,&,var,val,&,...
                   index = index + 3;
               }
               config_page();
           }
       }
    
       responseStatus = 404;
    
    responseBody = header + "

    404 Page Not Found.

    " + footer;
       throw_exception("There has been a HTTP-request to a non-existant page on your HUD's website. Please check the path of the request for mistakes.");
    

    }

    /*

    • user-function: prepare_profile_overview_page
    • - does not return anything
    • - prepares a response for a page with information about a certain avatar
    • - includes profile thumbnail, name, script info, give menu
    • /

    prepare_profile_overview_page(key requestID, key id) {

       list avatarDetails = llGetObjectDetails(id, [
                                   OBJECT_POS,
                                   OBJECT_TOTAL_SCRIPT_COUNT,
                                   OBJECT_SCRIPT_MEMORY,
                                   OBJECT_SCRIPT_TIME]);
    
       responseStatus = 200;
    
    responseBody = header + "

    " + html_body_with_formatted_avatar_name(id) + "

    " + llGetUsername(id) + "

    " + html_body_with_links_for_interaction_with_certain_avatar(id) + "
    " + html_body_with_inventory_overview_for_give_menu(id) + "
    " + html_body_avatar_profile_pic_thumbnail(id) + "
    • Scripts:
      • " + (string)llList2Integer(avatarDetails, 1) + " total
      • " + bytes2str(llList2Integer(avatarDetails, 2)) + "
      • " + (string)((integer)(llList2Float(avatarDetails, 3) * 1000000.0)) + "us
    " + footer;

    }

    /*

    • user-function: html_body_with_formatted_avatar_name
    • - returns the name of the avatar in html format
    • - removes the lastname if it is Resident
    • - adds a line-break for long names
    • /

    string html_body_with_formatted_avatar_name(key id) {

       string stringToReturn = llKey2Name(id);
    
       if (llGetSubString(stringToReturn, -9, -1) == " Resident")
           stringToReturn = llDeleteSubString(stringToReturn, -9, -1);
    
       if (15 < llStringLength(stringToReturn))
           stringToReturn = llDumpList2String(llParseString2List(stringToReturn,[" "], []), "
    ");
       return stringToReturn;
    

    }

    /*

    • user-function: html_body_with_links_for_interaction_with_certain_avatar
    • - returns html text with links for an avatar to interact with who has a certain uuid
    • /

    string html_body_with_links_for_interaction_with_certain_avatar(key id) {

    return "";

    }

    /*

    • user-function: html_body_with_inventory_overview_for_give_menu
    • - returns html text with inventory item lists
    • /

    string html_body_with_inventory_overview_for_give_menu(key id) {

    string stringToReturn = "";
       return stringToReturn;
    

    }

    /*

    • user-function: bytes2str
    • - returns a string with script memory info in readable format
    • /

    string bytes2str(integer bytes) {

       // 1024² = 1048576
    
       if (bytes < 1048576)
           return (string)(bytes / 1024) + " KB";
       //else
           return (string)(bytes / 1048576) + " MB";
    

    }

    /*

    • user-function: html_body_avatar_profile_pic_thumbnail
    • - returns html text with urls to someone's profile pic
    • /

    string html_body_avatar_profile_pic_thumbnail(key id) {

       return "<a href='secondlife:///app/agent/" + (string)id + "/about' class='avatar avatar_thumb' rel='#sl_image_zoom' title='Click to zoom'<img alt='Thumb_sl_image' src='https://my-secondlife.s3.amazonaws.com/users/" + llGetUsername(id) + "/sl_image.png' /></a>";
    

    }

    /*

    • user-function: inventory_list_in_html_format_for_give_menu
    • - returns html text with inventory item list of given type
    • /

    string inventory_list_in_html_format_for_give_menu(key id, integer type) {

       string stringToReturn;
    
       integer index = llGetInventoryNumber(type);
       while (index)
       {
           --index;
    
           string name = llGetInventoryName(type, index);
    
    stringToReturn += "
  • <a href='agent/" + (string)id + "/give/" + name + "'>" + name + "</a>
  • ";
       }
    
       return stringToReturn;
    

    }

    /*

    • user-function: anims_page
    • - does not return anything
    • - prepares an html text overview page of animations
    • /

    anims_page() {

       responseStatus = 200;
    
    responseBody = header + "

    Animations

    Choose an animation:

    " + html_body_animations_overview() + "


    " + footer;

    }

    /*

    • user-function: html_body_animations_overview
    • - returns html text with a list of included animations
    • /

    string html_body_animations_overview() {

    string stringToReturn = "";
       return stringToReturn;
    

    }

    /*

    • user-function: play_anim
    • - does not return anything
    • - prompts a perms request to animate owner
    • /

    play_anim(string anim) {

       llRequestPermissions(owner, PERMISSION_TRIGGER_ANIMATION);
       currentAnimation = llUnescapeURL(anim);
    

    }

    /*

    • user-function: config_page
    • - does not return anything
    • - prepares page to configure youtube video link
    • /

    config_page() {

       responseStatus = 200;
    
    responseBody = header + "

    Options:

    <form action='config/set' method='get'>Video URL: <input type='text' name='video' value='" + video_url + "' /><input type='submit' value='Set' /></form>" + footer;

    }

    /*

    • user-function: throw_exception
    • - does not return anything
    • - logs errors into cache for later viewing and debugging
    • /

    throw_exception(string inputString) {

       if (exceptions == "")
           exceptions = "The following un-handled exception(s) occurred that are preventing this device's operation:\n";
    
       exceptions += "\t"+inputString+"\n";
    

    }

    default {

       on_rez(integer start_param)
       {
           llReleaseURL(url);
           llResetScript();
       }
    
       changed(integer change)
       {
           if (change & (CHANGED_OWNER | CHANGED_INVENTORY))
           {
               llReleaseURL(url);
               llResetScript();
           }
    
           if (change & (CHANGED_REGION | CHANGED_REGION_START | CHANGED_TELEPORT))
               request_url();
       }
    
       state_entry()
       {
           init();
       }
    
       touch_start(integer num_detected)
       {
           toggle_visibility_of_HUD_button();
       }
    
       http_request(key id, string method, string body)
       {
           integer sendResponseNow = TRUE;
    
           responseStatus = 400;
           responseBody = "Unsupported method";
    
           if (method == URL_REQUEST_GRANTED)
           {
               responseStatus = 200;
               responseBody = "OK";
    
               set_link_media(body);
           }
           else if (method == URL_REQUEST_DENIED)
           {
               responseStatus = 400;
               responseBody = "Bad request";
    
               throw_exception("The following error occurred while attempting to get a free URL for this device:\n \n" + body);
           }
           else if (method == "GET")
           {
               responseStatus = 200;
               responseBody = "GET";
    
               string pathInfoHeader = llGetHTTPHeader(id, "x-path-info");
               list path = llParseString2List(pathInfoHeader, ["/"], []);
    
               if (path == [])
               {
                   sendResponseNow = FALSE;
                   llSensor("", NULL_KEY, AGENT_BY_LEGACY_NAME, 96.0, PI);
                   lastPath = [];
               }
               else
                   http_get(id, path);
           }
    

    /*

    • check if doing a sensor sweep and if so don't send a response
    • but send the response in 'sensor event' or 'no_sensor event'
    • (time-out 30.0 seconds for response)
    • /
           if (sendResponseNow)
           {
               llSetContentType(id, CONTENT_TYPE_HTML);
               llHTTPResponse(id, responseStatus, responseBody);
           }
    
           if (exceptions != "")
               state error;
       }
    
       run_time_permissions(integer perm)
       {
           if (perm & PERMISSION_TRIGGER_ANIMATION)
           {
               llStartAnimation(currentAnimation);
           }
           else
               throw_exception("This HUD has tried to animate your avatar WITHOUT having the permissions to do so. You must grant this HUD permissions to animate your avatar for this feature to work.");
    
           if (exceptions != "")
               state error;
       }
    
       sensor(integer num_detected)
       {
           responseStatus = 200;
    
    responseBody = header + "

    Scan Results:

      ";
             if (14 < num_detected)
                 num_detected = 14;
      
             while (num_detected)
             {
                 --num_detected;
      
                 key id = llDetectedKey(num_detected);
      
      responseBody += "
    • <a href='agent/" + (string)id + "'>" + html_body_with_formatted_avatar_name(id) + "</a>
    • ";
             }
      
      responseBody += "
    " + footer;
           llSetContentType(currentRequestID, CONTENT_TYPE_HTML);
           llHTTPResponse(currentRequestID, responseStatus, responseBody);
       }
    
       no_sensor()
       {
           responseStatus = 200;
    
    responseBody = header + "

    Scan Results:

    • No one near by.
    • Owner: <a href='agent/" + (string)owner + "'>" + ownerName + "</a>
    " + footer;
           llSetContentType(currentRequestID, CONTENT_TYPE_HTML);
           llHTTPResponse(currentRequestID, responseStatus, responseBody);
       }
    
       state_exit()
       {
           llReleaseURL(url);
       }
    

    }

    state error {

       on_rez(integer start_param)
       {
           llResetScript();
       }
    
       changed(integer change)
       {
           if (change & (CHANGED_OWNER | CHANGED_INVENTORY))
               llResetScript();
       }
    
       state_entry()
       {
           llOwnerSay("========== ERROR REPORT START ==========");
           llOwnerSay(exceptions);
           llOwnerSay("========== ERROR REPORT END ==========");
           llOwnerSay("Resetting now...");
           llResetScript();
       }
    

    } </lsl>