HTML HUD Demo
From Second Life Wiki
Revision as of 13:49, 22 January 2015 by Lady Sumoku (Talk | contribs)
TODO:
- Suggest a
Stop All Animations
button be added to Animations selection frame for convenience.
HTML HUD Demo:
Screenshot:
Tip:
![]() |
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'> </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(); } }