Difference between revisions of "HTML HUD Demo"

From Second Life Wiki
Jump to: navigation, search
Line 5: Line 5:
 
// * Rich, dynamic interface (images, menus, flash)
 
// * Rich, dynamic interface (images, menus, flash)
 
// Updates and improvements are welcome at http://wiki.secondlife.com/wiki/HTML_HUD_Demo
 
// Updates and improvements are welcome at http://wiki.secondlife.com/wiki/HTML_HUD_Demo
 
+
 
// To Use:
 
// To Use:
 
// * Create a cube and wear it as a hud on the top-left. (script will need tweaking for other spots)
 
// * Create a cube and wear it as a hud on the top-left. (script will need tweaking for other spots)
Line 11: Line 11:
 
// * Add any animations you want to play to the object
 
// * Add any animations you want to play to the object
 
// * Add any notecards or objects you want to give away to the object.
 
// * Add any notecards or objects you want to give away to the object.
 
+
 
// License:  
 
// License:  
 
// This script itself is free to share, modify and use without restriction.
 
// This script itself is free to share, modify and use without restriction.
Line 17: Line 17:
 
// Created by Kelly Linden, 2011.
 
// Created by Kelly Linden, 2011.
  
integer display_face = 4;
+
 
integer button_face = 2;
+
// CONFIG PARAMS
integer link = LINK_THIS;
+
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>];
 
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>];
 
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];
 
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];
integer is_visible = FALSE;
 
string my_url = "";
 
  
string footer;
+
// INTERNAL VARS
string header;
+
// These variables are set by the program.
key current_request;
+
integer is_visible = FALSE; // Whether or not the hud is showing HTML side.
 
+
string my_url = "";        // Current LSL base url.
set_header()
+
list last_path;             // Last visited
{
+
string footer;             // Footer: set in set_url
    // TODO: Remove dependency on this css file.
+
string header;             // Header: set in set_url
    header = "<html><head>"
+
key current_request;        // HTTP request ID for the current request.
            + "<link href='https://d1979ns0fqtj19.cloudfront.net/assets/common-103828347986224535963905120979424958961.css' media='all' rel='stylesheet' type='text/css' />"
+
             + "<base href='" + my_url + "/' />"
+
            + "</head><body>";
+
}
+
   
+
set_footer()
+
{
+
    // TODO: Remove dependency on the js file
+
    footer = "<div align='center' style='position:absolute;top:93%;left:15%;'><a href='anims'>Anims</a> | <a href=''>Scan</a> | <a href='hide'>Hide</a> | <a href='video'>Video</a></div>"
+
        + "<script src='https://d2mjw3k7q9u8rb.cloudfront.net/assets/common-170919042270376442559931151451605602726.js' type='text/javascript'></script></body></html>";
+
}
+
  
 +
// Toggle between button and HTML
 
toggle_show()
 
toggle_show()
 
{
 
{
Line 55: Line 51:
 
}
 
}
  
 +
// Build an image url from an avatar's key.
 
string av_image(key id)
 
string av_image(key id)
 
{
 
{
Line 60: Line 57:
 
}
 
}
  
 +
// Build an url that opens the profile for an avatar
 
string app_profile_url(key id)
 
string app_profile_url(key id)
 
{
 
{
Line 65: Line 63:
 
}
 
}
  
set_media(string url)
+
// Setup required after getting an url.
 +
set_url(string url)
 
{
 
{
     //llClearLinkMedia(link,display_face);
+
     // Store our base url for future link building.
 +
    my_url = 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 = "<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, display_face,              // Side to display the media on.
 
     llSetLinkMedia(link, display_face,              // Side to display the media on.
 
             [PRIM_MEDIA_AUTO_PLAY,TRUE,    // Show this page immediately
 
             [PRIM_MEDIA_AUTO_PLAY,TRUE,    // Show this page immediately
Line 74: Line 98:
 
             PRIM_MEDIA_HEIGHT_PIXELS,256,  // Height/width of media texture will be
 
             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_WIDTH_PIXELS,256,  //  rounded up to nearest power of 2.
             PRIM_MEDIA_PERMS_CONTROL, PRIM_MEDIA_PERM_NONE])
+
             PRIM_MEDIA_PERMS_CONTROL, PRIM_MEDIA_PERM_NONE]);
}
+
 
+
string vec2str(vector v)
+
{
+
    return "<" + (string)((integer)v.x) + "," + (string)((integer)v.y) + ","  + (string)((integer)v.z) + ">";
+
 
}
 
}
  
 +
// Turn a number of bytes (script size) into human readable format.
 
string bytes2str(integer b)
 
string bytes2str(integer b)
 
{
 
{
Line 88: Line 108:
 
}
 
}
  
string img_html(key agent)
+
// 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>";
+
     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)
 
string agent_menu_html(key agentid)
 
{
 
{
 
     return "<div class='menu_button'>"
 
     return "<div class='menu_button'>"
         + "<a class='button call_to_action'><b class='actions_dropdown'>&nbsp;</b></a>"
+
         + "<a class='button call_to_action'>Options</a>"
 
         + "<ul style='list-style-type:none;text-align:left' class='menu'>"
 
         + "<ul style='list-style-type:none;text-align:left' class='menu'>"
 
         + "<li><a href='secondlife:///app/agent/" + (string)agentid + "/im'>IM</a></li>"
 
         + "<li><a href='secondlife:///app/agent/" + (string)agentid + "/im'>IM</a></li>"
Line 105: Line 128:
 
}
 
}
  
 +
// Build an html list of items of type for the Give menu
 
string give_list(key agentid, integer type)
 
string give_list(key agentid, integer type)
 
{
 
{
Line 117: Line 141:
 
}
 
}
  
 +
// Build the html for the Give menu and fill it with notecards and objects.
 
string give_menu_html(key agentid)
 
string give_menu_html(key agentid)
 
{
 
{
Line 122: Line 147:
 
         + "<a class='button call_to_action'>Give</a>"
 
         + "<a class='button call_to_action'>Give</a>"
 
         + "<ul style='list-style-type:none;text-align:left;white-space:nowrap' class='menu'>";
 
         + "<ul style='list-style-type:none;text-align:left;white-space:nowrap' class='menu'>";
   
+
 
     // Give notecards and objects.
 
     // Give notecards and objects.
 
     resp += give_list(agentid,INVENTORY_NOTECARD);
 
     resp += give_list(agentid,INVENTORY_NOTECARD);
 
     resp += give_list(agentid,INVENTORY_OBJECT);
 
     resp += give_list(agentid,INVENTORY_OBJECT);
 
+
 
     resp += "</ul></div>";
 
     resp += "</ul></div>";
 
     return 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) > 14)
 +
    {
 +
        name = llDumpList2String(llParseString2List(name,[" "],[]),"<br>");
 +
    }
 +
    return name;
 +
}
 +
 +
// Build the Agent Details page.
 
agent_details(key request, key agent)
 
agent_details(key request, key agent)
 
{
 
{
 
     // Get the details we want.
 
     // Get the details we want.
 
     list r = llGetObjectDetails(agent,[OBJECT_POS,OBJECT_TOTAL_SCRIPT_COUNT,OBJECT_SCRIPT_MEMORY,OBJECT_SCRIPT_TIME]);
 
     list r = llGetObjectDetails(agent,[OBJECT_POS,OBJECT_TOTAL_SCRIPT_COUNT,OBJECT_SCRIPT_MEMORY,OBJECT_SCRIPT_TIME]);
 
+
 
     // Build the html.
 
     // Build the html.
 
     string resp = header + "<table border='0' cellspacing='1' cellpadding='1'>"
 
     string resp = header + "<table border='0' cellspacing='1' cellpadding='1'>"
 
         + "<tr><td colspan='2' style='white-space:nowrap'><div class='profile_title'>"
 
         + "<tr><td colspan='2' style='white-space:nowrap'><div class='profile_title'>"
         +  "<h1 id='display_name'>" + llKey2Name(agent) + "</h1>"
+
         +  "<h1 id='display_name'>" + agent_name(agent) + "</h1>"
 
         +  "<h2 id='username'>" + llGetUsername(agent) + "</h2>"
 
         +  "<h2 id='username'>" + llGetUsername(agent) + "</h2>"
 
         + "</div></td><td><div align='right'>" + agent_menu_html(agent) + "<br>" + give_menu_html(agent) + "</div></td></tr>"
 
         + "</div></td><td><div align='right'>" + agent_menu_html(agent) + "<br>" + give_menu_html(agent) + "</div></td></tr>"
         + "<tr><td width='80'>" + img_html(agent) + "</td><td colspan='2'>"
+
         + "<tr><td width='80'>" + profile_thumb_html(agent) + "</td><td colspan='2'>"
 
         +  "<ul style='list-style-type:circle'>"
 
         +  "<ul style='list-style-type:circle'>"
 
         +  "<li>Scripts:"
 
         +  "<li>Scripts:"
Line 150: Line 187:
 
         +      "<li>" + (string)((integer)(llList2Float(r,3) * 1000000.0)) + "us</li>"
 
         +      "<li>" + (string)((integer)(llList2Float(r,3) * 1000000.0)) + "us</li>"
 
         +  "</ul></li></ul></td></tr></table>" + footer;
 
         +  "</ul></li></ul></td></tr></table>" + footer;
   
+
 
     // Send the response.
 
     // Send the response.
 
     llSetContentType(current_request, CONTENT_TYPE_HTML);
 
     llSetContentType(current_request, CONTENT_TYPE_HTML);
 
     llHTTPResponse(current_request,200,resp);
 
     llHTTPResponse(current_request,200,resp);
 
+
     //llOwnerSay("Memory used: " + (string)llGetUsedMemory() + ", response size: " + (string)llStringLength(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 anim_menu_html()
 
{
 
{
Line 163: Line 203:
 
         + "<a class='button call_to_action'>Animate<b class='actions_dropdown'>&nbsp;</b></a>"
 
         + "<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'>";
 
         + "<ul style='list-style-type:none;text-align:left' class='menu'>";
 
+
 
     integer n = llGetInventoryNumber(INVENTORY_ANIMATION);
 
     integer n = llGetInventoryNumber(INVENTORY_ANIMATION);
 
     if (n == 0)
 
     if (n == 0)
Line 177: Line 217:
 
         }
 
         }
 
     }
 
     }
 
+
 
     resp += "</ul></div>";
 
     resp += "</ul></div>";
 
     return resp;
 
     return resp;
 
}
 
}
  
 +
// Play an animation given the name.
 
play_anim(string anim)
 
play_anim(string anim)
 
{
 
{
Line 188: Line 229:
 
}
 
}
  
 +
// Build the anims page.
 
anims_page()
 
anims_page()
 
{
 
{
Line 196: Line 238:
 
}
 
}
  
 +
// Build the configuration page.
 +
config_page()
 +
{
 +
    string resp = header + "<h1>Not yet implemented.</h1>" + footer;
 +
    llSetContentType(current_request, CONTENT_TYPE_HTML);
 +
    llHTTPResponse(current_request,200,resp);
 +
}
 +
 
// Process a get request with a path.
 
// Process a get request with a path.
 
GET(key request, list path)
 
GET(key request, list path)
Line 203: Line 253:
 
     if (path_segments == 0)
 
     if (path_segments == 0)
 
     {
 
     {
         // Home page.
+
         // Home page is handled in the sensor response.
 
         llSensor("",NULL_KEY,AGENT,96,PI);
 
         llSensor("",NULL_KEY,AGENT,96,PI);
 +
       
 +
        // Set the last_path incase the next request is a hide
 +
        last_path = [];
 
         return;
 
         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;
 
      
 
      
    string p0 = llList2String(path,0);
 
 
     if (p0 == "agent")
 
     if (p0 == "agent")
 
     {
 
     {
 
         if (path_segments == 1)
 
         if (path_segments == 1)
 
         {
 
         {
             // just /agent lets show stuff for the owner.
+
             // <url>/agent
 +
            // Show owner details.
 
             agent_details(request,llGetOwner());
 
             agent_details(request,llGetOwner());
 
             return;
 
             return;
 
         }
 
         }
       
+
 
         // p1 should be an agent id
 
         // p1 should be an agent id
 
         key agent = (key)llList2String(path,1);
 
         key agent = (key)llList2String(path,1);
 
+
 
         if (path_segments == 2)
 
         if (path_segments == 2)
 
         {
 
         {
             // Just an agent id. Get their info.
+
             // <url>/agent/<id>
 +
            // Show the details for the agent.
 
             agent_details(request,agent);
 
             agent_details(request,agent);
 
             return;
 
             return;
 
         }
 
         }
         else if (path_segments == 4)
+
          
 +
        if (path_segments == 4)
 
         {
 
         {
             // agent/<id>/<cmd>/<option>
+
             // <url>/agent/<id>/<cmd>/<option>
             string p2 = llList2String(path,2);
+
             string cmd = llList2String(path,2);
             if (p2 == "give")
+
             if (cmd == "give")
 
             {
 
             {
                 string p3 = llUnescapeURL(llList2String(path,3));
+
                 string object_name = llUnescapeURL(llList2String(path,3));
                 llOwnerSay("Giving " + p3 + " to " + llKey2Name((key)agent));
+
                 llOwnerSay("Giving " + object_name + " to " + llKey2Name((key)agent));
                 llGiveInventory(agent,p3);
+
                 llGiveInventory(agent,object_name);
 
                 agent_details(request,agent);
 
                 agent_details(request,agent);
 
                 return;
 
                 return;
 
             }
 
             }
 
         }
 
         }
    }
 
    else if (p0 == "hide")
 
    {
 
        llSensor("",NULL_KEY,AGENT,96,PI);
 
        toggle_show();
 
 
     }
 
     }
 
     else if (p0 == "anims")
 
     else if (p0 == "anims")
Line 250: Line 318:
 
         if (path_segments == 1)
 
         if (path_segments == 1)
 
         {
 
         {
 +
            // <url>/anims
 +
            // Show the animation selection page
 
             anims_page();
 
             anims_page();
 +
            return;
 
         }
 
         }
 
         else if (path_segments == 2)
 
         else if (path_segments == 2)
 
         {
 
         {
 +
            // <url>/anims/<anim-name>
 +
            // Play the specified animation
 
             play_anim(llList2String(path,1));
 
             play_anim(llList2String(path,1));
 
             anims_page();
 
             anims_page();
 +
            return;
 
         }
 
         }
 
     }
 
     }
 
     else if (p0 == "video")
 
     else if (p0 == "video")
 
     {
 
     {
 +
        // <url>/video
 +
        // Play a demo video
 
         string resp = header
 
         string resp = header
             + "<br><iframe width='255' height='173' src='http://www.youtube.com/embed/m7p9IEpPu-c?rel=0' frameborder='0' allowfullscreen></iframe>" + footer;
+
             + "<br><iframe width='255' height='173' src='" + video_url + "' frameborder='0' allowfullscreen></iframe>"
 +
            + footer;
 
         llSetContentType(current_request, CONTENT_TYPE_HTML);
 
         llSetContentType(current_request, CONTENT_TYPE_HTML);
 
         llHTTPResponse(current_request,200,resp);
 
         llHTTPResponse(current_request,200,resp);
 +
        return;
 
     }
 
     }
 +
    else if (p0 == "config")
 +
    {
 +
        config_page();
 +
        return;
 +
    }
 +
   
 +
    // Unknown page. 404.
 +
    string resp = header + "<h1>404 Page Not Found.</h1>" + footer;
 +
    llSetContentType(current_request, CONTENT_TYPE_HTML);
 +
    llHTTPResponse(current_request,404,resp);
 
}
 
}
 
+
 
default  
 
default  
 
{  
 
{  
Line 274: Line 362:
 
         llSetLinkPrimitiveParamsFast(link,init);
 
         llSetLinkPrimitiveParamsFast(link,init);
 
         toggle_show();
 
         toggle_show();
        //llOwnerSay(llList2CSV(llGetLinkPrimitiveParams(2,[PRIM_POS_LOCAL])));
 
        //llSetColor(<1,0,0>,button_face);
 
 
         llRequestURL();
 
         llRequestURL();
 
     }
 
     }
 
+
 
     on_rez(integer n) { llResetScript();}
 
     on_rez(integer n) { llResetScript();}
 
     changed(integer c)
 
     changed(integer c)
Line 284: Line 370:
 
         if (c & (CHANGED_TELEPORT | CHANGED_OWNER | CHANGED_REGION)) { llResetScript();}
 
         if (c & (CHANGED_TELEPORT | CHANGED_OWNER | CHANGED_REGION)) { llResetScript();}
 
     }
 
     }
   
+
 
     touch_start(integer total_number)
 
     touch_start(integer total_number)
 
     {
 
     {
 
         toggle_show();
 
         toggle_show();
 
     }  
 
     }  
   
+
 
     http_request(key id, string method, string body)
 
     http_request(key id, string method, string body)
 
     {
 
     {
        //llOwnerSay(method + ": "  + llGetHTTPHeader(id,"x-path-info"));
 
 
         if (method == URL_REQUEST_GRANTED)
 
         if (method == URL_REQUEST_GRANTED)
 
         {
 
         {
             my_url = body;
+
             set_url(body);
            set_footer();
+
            set_header();
+
            set_media(my_url);
+
 
             return;
 
             return;
 
         }
 
         }
Line 308: Line 390:
 
             return;
 
             return;
 
         }
 
         }
       
+
 
         if (method == "GET")
 
         if (method == "GET")
 
         {
 
         {
Line 315: Line 397:
 
             return;
 
             return;
 
         }
 
         }
       
+
 
         llHTTPResponse(id,400,"Unsupported method.");
 
         llHTTPResponse(id,400,"Unsupported method.");
 
         return;
 
         return;
 
     }
 
     }
   
+
 
     sensor(integer n)
 
     sensor(integer n)
 
     {
 
     {
 
         string resp = header + "<h2>Scan Results:</h2><ul style='list-style-type:circle;margin-left:20px'>";
 
         string resp = header + "<h2>Scan Results:</h2><ul style='list-style-type:circle;margin-left:20px'>";
       
+
 
         // TODO: Only fits 14 right now. Figure out how to fit all 16. Figure out how to find more than 16.
 
         // TODO: Only fits 14 right now. Figure out how to fit all 16. Figure out how to find more than 16.
 
         if (n > 14) n = 14;
 
         if (n > 14) n = 14;
 
         for (--n; n >= 0; --n)
 
         for (--n; n >= 0; --n)
 
         {
 
         {
             resp += "<li><a href='agent/" + (string)llDetectedKey(n) + "'>" + llDetectedName(n) + "</a></li>";
+
             resp += "<li><a href='agent/" + (string)llDetectedKey(n) + "'>" + agent_name(llDetectedKey(n)) + "</a></li>";
 
         }
 
         }
 
         resp += "</ul>" + footer;
 
         resp += "</ul>" + footer;
Line 334: Line 416:
 
         llHTTPResponse(current_request,200,resp);
 
         llHTTPResponse(current_request,200,resp);
 
     }
 
     }
   
+
 
     no_sensor()
 
     no_sensor()
 
     {
 
     {
Line 344: Line 426:
 
         llHTTPResponse(current_request,200,resp);
 
         llHTTPResponse(current_request,200,resp);
 
     }
 
     }
}
+
}</lsl>
</lsl>
+

Revision as of 10:34, 26 August 2011

<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. string my_url = ""; // Current LSL base url. 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) {

   // Store our base url for future link building.
   my_url = 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) > 14)
       {
           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 + "

    Not yet implemented.

    " + 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") { config_page(); return; } // 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()
       {
           llSetObjectName(llKey2Name(llGetOwner()) + "'s HUD");
           llSetLinkPrimitiveParamsFast(link,init);
           toggle_show();
           llRequestURL();
       }
    
       on_rez(integer n) { llResetScript();}
       changed(integer c)
       {
           if (c & (CHANGED_TELEPORT | CHANGED_OWNER | CHANGED_REGION)) { llResetScript();}
       }
    
       touch_start(integer total_number)
       {
           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)
       {
    
    string resp = header + "

    Scan Results:

      ";
             // TODO: Only fits 14 right now. Figure out how to fit all 16. 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()
       {
    
    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>