HTML HUD Demo

From Second Life Wiki
Jump to: navigation, search

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!