Difference between revisions of "HTML HUD Demo"

From Second Life Wiki
Jump to navigation Jump to search
m
m (Replaced old <LSL> block with <source lang="lsl2">)
(10 intermediate revisions by 2 users not shown)
Line 1: Line 1:
<span id="top"></span>
<span id="top"></span>


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.
=TODO:=
'''[[#original by Kelly Linden | original script]]'''
'''[[#reformatted version | reformatted version]]'''


[[File:HTML HUD Demo.jpg|200px|thumb|top|screenshots]]
* Suggest a <code>Stop All Animations</code> button be added to Animations selection frame for convenience.


=== original by Kelly Linden ===
=HTML HUD Demo:=
{{Anchor|original by Kelly Linden}}


<lsl>// HTML Based, Single Script HUD
== Screenshot: ==
// 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.


[[File:HTML HUD Demo.jpg|200px|thumb|top|screenshots]]


// CONFIG PARAMS
==Tip:==
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 = "<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.
            [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 "<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)agentid + "/im'>IM</a></li>"
        + "<li><a href='secondlife:///app/agent/" + (string)agentid + "/offerteleport'>Offer Teleport</a></li>"
        + "<li><a href='secondlife:///app/maptrackavatar/" + (string)agentid + "'>Map</a></li>"
        + "<li><a href='secondlife:///app/sharewithavatar/" + (string)agentid + "'>Share</a></li>"
        + "<li><a href='secondlife:///app/agent/" + (string)agentid + "/pay'>Pay</a></li></ul></div>";
}
 
// 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 += "<li><a href='agent/" + (string)agentid + "/give/" + llGetInventoryName(type,i) + "'>" + llGetInventoryName(type,i) + "</a></li>";
    }
    return resp;
}
 
// Build the html for the Give menu and fill it with notecards and objects.
string give_menu_html(key agentid)
{
    string resp = "<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'>";
    // Give notecards and objects.
    integer resp_size = llStringLength(resp);
    resp += give_list(agentid,INVENTORY_NOTECARD);
    resp += give_list(agentid,INVENTORY_OBJECT);
 
    if (resp_size == llStringLength(resp))
    {
        // No inventory.
        resp += "<li>(no objects or notecards found)</li>";
    }
 
    resp += "</ul></div>";
    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,[" "],[]),"<br>");
    }
    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 + "<table border='0' cellspacing='1' cellpadding='1'>"
        + "<tr><td colspan='2' style='white-space:nowrap'><div class='profile_title'>"
        +  "<h1 id='display_name'>" + agent_name(agent) + "</h1>"
        +  "<h2 id='username'>" + llGetUsername(agent) + "</h2>"
        + "</div></td><td><div align='right'>" + agent_menu_html(agent) + "<br>" + give_menu_html(agent) + "</div></td></tr>"
        + "<tr><td width='80'>" + profile_thumb_html(agent) + "</td><td colspan='2'>"
        +  "<ul style='list-style-type:circle'>"
        +  "<li>Scripts:"
        +      "<ul style='list-style-type:disc;margin-left:10px'>"
        +      "<li>" + (string)llList2Integer(r,1) + " total</li>"
        +      "<li>" + bytes2str(llList2Integer(r,2)) + "</li>"
        +      "<li>" + (string)((integer)(llList2Float(r,3) * 1000000.0)) + "us</li>"
        +  "</ul></li></ul></td></tr></table>" + 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 = "<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 n = llGetInventoryNumber(INVENTORY_ANIMATION);
    if (n == 0)
    {
        resp += "<li>(no animations found)</li>";
    }
    else
    {
        integer i;
        for(i=0;i<n;++i)
        {
            resp += "<li><a href='anims/" + llGetInventoryName(INVENTORY_ANIMATION,i) + "'>" + llGetInventoryName(INVENTORY_ANIMATION,i) + "</a></li>";
        }
    }
    resp += "</ul></div>";
    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 + "<h1>Animations</h1><h2>Choose an animation:</h2>"
    + "<div style='margin-left:40px'>" + anim_menu_html() + "</div><br><br>" + footer;
    llSetContentType(current_request, CONTENT_TYPE_HTML);
    llHTTPResponse(current_request,200,resp);
}
 
// Build the configuration page.
config_page()
{
    string resp = 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;
    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
            + "<br><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 + "<h1>404 Page Not Found.</h1>" + 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)
{{KBtip|This HUD might take a second or two to load when switching from 'closed' to 'open' mode.}}
    {
        // 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 + "<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.
        // TODO: Figure out how to find more than 16.
        if (n > 14) n = 14;
        for (--n; n >= 0; --n)
        {
            resp += "<li><a href='agent/" + (string)llDetectedKey(n) + "'>" + agent_name(llDetectedKey(n)) + "</a></li>";
        }
        resp += "</ul>" + 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 + "<h2>Scan Results:</h2><ul style='list-style-type:circle;margin-left:20px'>";
        resp += "<li>No one near by.</li>";
        resp += "<li>Owner: <a href='agent/" + (string)llGetOwner() + "'>" + llKey2Name(llGetOwner()) + "</a></li>";
        resp += "</ul>" + footer;
        llSetContentType(current_request, CONTENT_TYPE_HTML);
        llHTTPResponse(current_request,200,resp);
    }
}</lsl>


[[#top|Go to top!]]
==Source code:==


=== reformatted version ===
<source lang="lsl2">
{{Anchor|reformatted version}}
//  HTML-based, single script HUD
//
//  original by Kelly Linden
//
//  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.


<lsl>
key    owner;
/*
string ownerName;
*  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;
integer scope;
string ownerName;


string url;
string url;


key currentRequestID;
key     currentRequestID;


integer responseStatus;
integer responseStatus;
string responseBody;
string responseBody;


list lastPath;
list   lastPath;


string video_url;
string video_url;


string header;
string header;
string footer;
string footer;


string currentAnimation;
string currentAnimation;


integer isVisible;
integer isVisible;


string exceptions;
string exceptions;


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


init()
init()
{
{
     owner = llGetOwner();
     owner     = llGetOwner();
     ownerName = llKey2Name(owner);
     ownerName = llKey2Name(owner);
     llSetObjectName(ownerName+"'s HUD");
     llSetObjectName("HTML HUD");
 
    scope = AGENT_LIST_PARCEL;


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


    //header set in set_link_media(url)
// header set in set_link_media(url)
    //footer set in set_link_media(url)
// footer set in set_link_media(url)
 
    float  FLOAT_FALSE      = 0.0;
    vector  RATIO_ONE_BY_ONE = <0.98, 0.98, 0.00>;//  fix Second Life ... or try to!


     llSetLinkPrimitiveParamsFast(LINK_THIS, [
     llSetLinkPrimitiveParamsFast(LINK_THIS, [
         PRIM_TEXTURE, ALL_SIDES, TEXTURE_BLANK, <1.0, 1.0, 1.0>, ZERO_VECTOR, (float)FALSE,
         PRIM_TEXTURE, ALL_SIDES, TEXTURE_BLANK,                         RATIO_ONE_BY_ONE, ZERO_VECTOR, FLOAT_FALSE,
         PRIM_TEXTURE, 2, "0b815b79-c8f5-fc98-91fc-e77b53a468e2", <1.0, 1.0, 1.0>, ZERO_VECTOR, (float)FALSE]);
         PRIM_TEXTURE, 2,         "0b815b79-c8f5-fc98-91fc-e77b53a468e2", RATIO_ONE_BY_ONE, ZERO_VECTOR, FLOAT_FALSE]);


     toggle_visibility_of_HUD_button();
     toggle_visibility_of_HUD_button();
     request_url();
     request_secure_url();
}
}


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


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


     isVisible = !isVisible;
     isVisible = !isVisible;
}
}


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


request_url()
release_url()
{
{
     llReleaseURL(url);
     llReleaseURL(url);
     llRequestURL();
     url = "";
}
}


/*
//  user-function: request secure url
* user-function: set_link_media
//  - does not return anything
* - does not return anything
//  - make sure we release the old url before requesting a new one
* - set the values for the string variables 'header' and 'footer'
 
* - prepare face 4 for media on a prim
request_secure_url()
*/
{
    release_url();
    currentRequestID = llRequestSecureURL();
}
 
// 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)
set_link_media(string scriptUrl)
Line 597: Line 144:
     url = 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>";
     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> |  
     footer = "<div align='center' style='position:absolute;top:93%;left:8%;'>"
    <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>";
        + "<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, [
     llSetLinkMedia(LINK_THIS, 4, [
                            PRIM_MEDIA_AUTO_PLAY, TRUE,
        PRIM_MEDIA_AUTO_PLAY, TRUE,
                            PRIM_MEDIA_CURRENT_URL, url,
        PRIM_MEDIA_CURRENT_URL, url,
                            PRIM_MEDIA_HOME_URL, url,
        PRIM_MEDIA_HOME_URL, url,
                            PRIM_MEDIA_HEIGHT_PIXELS, 256,
        PRIM_MEDIA_HEIGHT_PIXELS, 256,
                            PRIM_MEDIA_WIDTH_PIXELS, 256,
        PRIM_MEDIA_WIDTH_PIXELS, 256,
                            PRIM_MEDIA_PERMS_CONTROL, PRIM_MEDIA_PERM_NONE]);
        PRIM_MEDIA_PERMS_CONTROL, PRIM_MEDIA_PERM_NONE]);
}
}


/*
// user-function:
* user-function:
// - does not return anything
* - does not return anything
// - used if there's not a request to the homepage of our website
* - 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
* - check where on the site we are and prepare variables for the response
*/


http_get(key requestID, list path)
http_get(key requestID, list path)
{
{
     currentRequestID = requestID;
     currentRequestID       = requestID;


     integer numOfPathsParts = llGetListLength(path);
     integer numOfPathsParts = llGetListLength(path);
     string firstPathPart = llList2String(path, 0);
     string firstPathPart   = llList2String(path, 0);


     if (firstPathPart == "hide")
     if (firstPathPart == "hide")
Line 650: Line 202:
         {
         {
             if (llList2String(path, 2) != "give")
             if (llList2String(path, 2) != "give")
            {
                 return;
                 return;
            }


             key id = (key)llList2String(path, 1);
             key id         = (key)llList2String(path, 1);
             string name = llKey2Name(id);
             string name     = llKey2Name(id);
             string itemName = llUnescapeURL(llList2String(path, 3));
             string itemName = llUnescapeURL(llList2String(path, 3));


Line 679: Line 233:
     {
     {
         responseStatus = 200;
         responseStatus = 200;
         responseBody = header + "<br><iframe width='255' height='173' src='" + video_url + "' frameborder='0' allowfullscreen></iframe>" + footer;
         responseBody = header + "<br><iframe width='255' height='173' src='"
                    + video_url + "' frameborder='0' allowfullscreen></iframe>"
                    + footer;
 
         return;
         return;
     }
     }
Line 692: Line 249:
         {
         {
             string queryString = llGetHTTPHeader(requestID, "x-query-string");
             string queryString = llGetHTTPHeader(requestID, "x-query-string");
             list args = llParseString2List(queryString, ["="], ["&"]);
             list args         = llParseString2List(queryString, ["="], ["&"]);
             integer index = ~llGetListLength(args);
 
             integer index     = -llGetListLength(args);
             while (index)
             while (index)
             {
             {
                 string variable = llList2String(args, index);
                 string variable = llList2String(args, index);
                 string value = llUnescapeURL(llList2String(args, index + 1));
                 string value   = llUnescapeURL(llList2String(args, index + 1));
 
                 if (variable == "video")
                 if (variable == "video")
                {
                     video_url = value;
                     video_url = value;
                }


                //because: var,val,&,var,val,&,...
//             because: var, val, &, var, val, &, ...
                 index = index + 3;
                 index += 3;
             }
             }
             config_page();
             config_page();
Line 710: Line 271:
     responseStatus = 404;
     responseStatus = 404;
     responseBody = header + "<h1>404 Page Not Found.</h1>" + footer;
     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.");
     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
* user-function: prepare_profile_overview_page
// - does not return anything
* - does not return anything
// - prepares a response for a page with information about a certain avatar
* - prepares a response for a page with information about a certain avatar
// - includes profile thumbnail, name, script info, give menu
* - includes profile thumbnail, name, script info, give menu
*/


prepare_profile_overview_page(key requestID, key id)
prepare_profile_overview_page(key requestID, key id)
{
{
     list avatarDetails = llGetObjectDetails(id, [
     list avatarDetails = llGetObjectDetails(id, [
                                OBJECT_POS,
                                    OBJECT_POS, OBJECT_TOTAL_SCRIPT_COUNT,
                                OBJECT_TOTAL_SCRIPT_COUNT,
                                    OBJECT_SCRIPT_MEMORY, OBJECT_SCRIPT_TIME]);
                                OBJECT_SCRIPT_MEMORY,
                                OBJECT_SCRIPT_TIME]);


     responseStatus = 200;
     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;
     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
* user-function: html_body_with_formatted_avatar_name
// - returns the name of the avatar in html format
* - returns the name of the avatar in html format
// - removes the lastname if it is Resident
* - removes the lastname if it is Resident
// - adds a line-break for long names
* - adds a line-break for long names
*/


string html_body_with_formatted_avatar_name(key id)
string html_body_with_formatted_avatar_name(key id)
Line 744: Line 317:


     if (llGetSubString(stringToReturn, -9, -1) == " Resident")
     if (llGetSubString(stringToReturn, -9, -1) == " Resident")
    {
         stringToReturn = llDeleteSubString(stringToReturn, -9, -1);
         stringToReturn = llDeleteSubString(stringToReturn, -9, -1);
 
    }
     if (15 < llStringLength(stringToReturn))
     if (15 < llStringLength(stringToReturn))
         stringToReturn = llDumpList2String(llParseString2List(stringToReturn,[" "], []), "<br>");
    {
         stringToReturn = llDumpList2String(
                            llParseString2List(stringToReturn,[" "], []),
                            "<br>"
                        );
    }


     return stringToReturn;
     return stringToReturn;
}
}


/*
// user-function: html_body_with_links_for_interaction_with_certain_avatar
* 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
* - 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)
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>";
     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
* user-function: html_body_with_inventory_overview_for_give_menu
// - returns html text with inventory item lists
* - returns html text with inventory item lists
*/


string html_body_with_inventory_overview_for_give_menu(key id)
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'>";
     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);
     integer sizeOfStringBefore = llStringLength(stringToReturn);
Line 777: Line 362:


     if (llStringLength(stringToReturn) == sizeOfStringBefore)
     if (llStringLength(stringToReturn) == sizeOfStringBefore)
    {
         stringToReturn += "<li>(no objects or notecards found)</li>";
         stringToReturn += "<li>(no objects or notecards found)</li>";
    }


     stringToReturn += "</ul></div>";
     stringToReturn += "</ul></div>";
Line 783: Line 370:
}
}


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


string bytes2str(integer bytes)
string bytes2str(integer bytes)
{
{
    // 1024² = 1048576
// 1024² = 1048576


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


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


string html_body_avatar_profile_pic_thumbnail(key id)
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>";
     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
* user-function: inventory_list_in_html_format_for_give_menu
// - returns html text with inventory item list of given type
* - returns html text with inventory item list of given type
*/


string inventory_list_in_html_format_for_give_menu(key id, integer type)
string inventory_list_in_html_format_for_give_menu(key id, integer type)
Line 824: Line 408:
         string name = llGetInventoryName(type, index);
         string name = llGetInventoryName(type, index);


         stringToReturn += "<li><a href='agent/" + (string)id + "/give/" + name + "'>" + name + "</a></li>";
         stringToReturn += "<li><a href='agent/" + (string)id + "/give/" + name
                      + "'>" + name + "</a></li>";
     }
     }


Line 830: Line 415:
}
}


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


anims_page()
anims_page()
{
{
     responseStatus = 200;
     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;
     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
* user-function: html_body_animations_overview
// - returns html text with a list of included animations
* - returns html text with a list of included animations
*/


string html_body_animations_overview()
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'>";
     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);
     integer index = llGetInventoryNumber(INVENTORY_ANIMATION);


     if (!index)
     if (!index)
    {
         stringToReturn += "<li>(no animations found)</li>";
         stringToReturn += "<li>(no animations found)</li>";
 
    }
     else while (index)
     else while (index)
     {
     {
Line 870: Line 457:
}
}


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


play_anim(string anim)
play_anim(string anim)
Line 882: Line 467:
}
}


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


config_page()
config_page()
{
{
     responseStatus = 200;
     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;
     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
* user-function: throw_exception
// - does not return anything
* - does not return anything
// - logs errors into cache for later viewing and debugging
* - logs errors into cache for later viewing and debugging
*/


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


     exceptions += "\t"+inputString+"\n";
     exceptions += "\t"+inputString+"\n";
}
}


default  
default
{  
{
     on_rez(integer start_param)
     on_rez(integer start_param)
     {
     {
         llReleaseURL(url);
         release_url();
         llResetScript();
         llResetScript();
     }
     }
Line 920: Line 507:
         if (change & (CHANGED_OWNER | CHANGED_INVENTORY))
         if (change & (CHANGED_OWNER | CHANGED_INVENTORY))
         {
         {
             llReleaseURL(url);
             release_url();
             llResetScript();
             llResetScript();
         }
         }


         if (change & (CHANGED_REGION | CHANGED_REGION_START | CHANGED_TELEPORT))
         if (change & (CHANGED_REGION | CHANGED_REGION_START | CHANGED_TELEPORT))
             request_url();
        {
             request_secure_url();
        }
     }
     }


Line 940: Line 529:
     http_request(key id, string method, string body)
     http_request(key id, string method, string body)
     {
     {
        integer sendResponseNow = TRUE;
         responseStatus = 400;
         responseStatus = 400;
         responseBody = "Unsupported method";
         responseBody   = "Unsupported method";


         if (method == URL_REQUEST_GRANTED)
         if (method == URL_REQUEST_GRANTED)
         {
         {
             responseStatus = 200;
             responseStatus = 200;
             responseBody = "OK";
             responseBody   = "OK";


             set_link_media(body);
             set_link_media(body);
Line 954: Line 541:
         else if (method == URL_REQUEST_DENIED)
         else if (method == URL_REQUEST_DENIED)
         {
         {
            responseStatus = 400;
             responseBody   = "Bad request";
             responseBody = "Bad request";


             throw_exception("The following error occurred while attempting to get a free URL for this device:\n \n" + body);
             throw_exception("The following error occurred while attempting to "
                            + "get a free URL for this device:\n \n" + body);
         }
         }
         else if (method == "GET")
         else if (method == "GET")
         {
         {
             responseStatus = 200;
             responseStatus = 200;
             responseBody = "GET";
             responseBody   = "GET";


             string pathInfoHeader = llGetHTTPHeader(id, "x-path-info");
             string pathInfoHeader = llGetHTTPHeader(id, "x-path-info");
             list path = llParseString2List(pathInfoHeader, ["/"], []);
             list path             = llParseString2List(pathInfoHeader, ["/"], []);


             if (path == [])
             if (path == [])
             {
             {
                 sendResponseNow = FALSE;
                 currentRequestID = id;
                 llSensor("", NULL_KEY, AGENT_BY_LEGACY_NAME, 96.0, PI);
 
                list    agents = llGetAgentList(scope, []);
                integer index  = llGetListLength(agents);
 
                 if (!index)
                {
                    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;
                }
                else
                {
                    responseBody  = header + "<h2>Scan Results:</h2><ul style='"
                                  + "list-style-type:circle;margin-left:20px'>";
 
                    while (index)
                    {
                        --index;
 
                        key agent = llList2Key(agents, index);
 
                        responseBody += "<li><a href='agent/" + (string)agent + "'>"
                                    + html_body_with_formatted_avatar_name(agent) + "</a></li>";
                    }
 
                    responseBody += "</ul>" + footer;
                }
 
                 lastPath = [];
                 lastPath = [];
             }
             }
             else
             else
            {
                 http_get(id, path);
                 http_get(id, path);
            }
         }
         }


/*
        llSetContentType(id, CONTENT_TYPE_HTML);
*  check if doing a sensor sweep and if so don't send a response
        llHTTPResponse(id, responseStatus, responseBody);
*  but send the response in 'sensor event' or 'no_sensor event'
*
(time-out 30.0 seconds for response)
*/


         if (sendResponseNow)
         if (exceptions != "")
         {
         {
             llSetContentType(id, CONTENT_TYPE_HTML);
             state error;
            llHTTPResponse(id, responseStatus, responseBody);
         }
         }
        if (exceptions != "")
            state error;
     }
     }


Line 1,001: Line 610:
         }
         }
         else
         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.");
        {
             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 != "")
         if (exceptions != "")
        {
             state error;
             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()
     state_exit()
     {
     {
         llReleaseURL(url);
         release_url();
     }
     }
}
}
Line 1,055: Line 638:
     {
     {
         if (change & (CHANGED_OWNER | CHANGED_INVENTORY))
         if (change & (CHANGED_OWNER | CHANGED_INVENTORY))
        {
             llResetScript();
             llResetScript();
        }
     }
     }


Line 1,067: Line 652:
     }
     }
}
}
</lsl>
</source>


[[#top|Go to top!]]
[[#top|Go to top!]]

Revision as of 14:49, 22 January 2015

TODO:

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

HTML HUD Demo:

Screenshot:

screenshots

Tip:

KBtip2.png Tip: This HUD might take a second or two to load when switching from 'closed' to 'open' mode.

Source code:

//  HTML-based, single script HUD
//
//  original by Kelly Linden
//
//  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.

key     owner;
string  ownerName;

integer scope;

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("HTML HUD");

    scope = AGENT_LIST_PARCEL;

    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)

    float   FLOAT_FALSE      = 0.0;
    vector  RATIO_ONE_BY_ONE = <0.98, 0.98, 0.00>;//  fix Second Life ... or try to!

    llSetLinkPrimitiveParamsFast(LINK_THIS, [
        PRIM_TEXTURE, ALL_SIDES, TEXTURE_BLANK,                          RATIO_ONE_BY_ONE, ZERO_VECTOR, FLOAT_FALSE,
        PRIM_TEXTURE, 2,         "0b815b79-c8f5-fc98-91fc-e77b53a468e2", RATIO_ONE_BY_ONE, ZERO_VECTOR, FLOAT_FALSE]);

    toggle_visibility_of_HUD_button();
    request_secure_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: drop and clear the old url
//  - does not return anything

release_url()
{
    llReleaseURL(url);
    url = "";
}

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

request_secure_url()
{
    release_url();
    currentRequestID = llRequestSecureURL();
}

//  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 += 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)
    {
        release_url();
        llResetScript();
    }

    changed(integer change)
    {
        if (change & (CHANGED_OWNER | CHANGED_INVENTORY))
        {
            release_url();
            llResetScript();
        }

        if (change & (CHANGED_REGION | CHANGED_REGION_START | CHANGED_TELEPORT))
        {
            request_secure_url();
        }
    }

    state_entry()
    {
        init();
    }

    touch_start(integer num_detected)
    {
        toggle_visibility_of_HUD_button();
    }

    http_request(key id, string method, string body)
    {
        responseStatus = 400;
        responseBody   = "Unsupported method";

        if (method == URL_REQUEST_GRANTED)
        {
            responseStatus = 200;
            responseBody   = "OK";

            set_link_media(body);
        }
        else if (method == URL_REQUEST_DENIED)
        {
            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 == [])
            {
                currentRequestID = id;

                list    agents = llGetAgentList(scope, []);
                integer index  = llGetListLength(agents);

                if (!index)
                {
                    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;
                }
                else
                {
                    responseBody   = header + "<h2>Scan Results:</h2><ul style='"
                                   + "list-style-type:circle;margin-left:20px'>";

                    while (index)
                    {
                        --index;

                        key agent = llList2Key(agents, index);

                        responseBody += "<li><a href='agent/" + (string)agent + "'>"
                                     + html_body_with_formatted_avatar_name(agent) + "</a></li>";
                    }

                    responseBody += "</ul>" + footer;
                }

                lastPath = [];
            }
            else
            {
                http_get(id, path);
            }
        }

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

    state_exit()
    {
        release_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();
    }
}

Go to top!