HTML HUD Demo

From Second Life Wiki
Revision as of 14:08, 13 November 2011 by Kireji Haiku (talk | contribs)
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

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.

original script reformatted version

screenshots

original by Kelly Linden

<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_name(agent) + "

    " + "

    " + llGetUsername(agent) + "

    " + "
    " + 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>

    Go to top!

    reformatted version

    <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>

    Go to top!