HTML HUD Demo

From Second Life Wiki
Revision as of 11:16, 27 August 2011 by Ai Austin (talk | contribs) (Corrrect typo on Options" in LSL. Suggest a "Stop All Animations" button.)
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.

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