HTML HUD Demo: Difference between revisions
Added image (collage of 8 HUD screenshots). Resulting page layout may be suboptimal, but I couldn't do better. |
Kireji Haiku (talk | contribs) added formatted copy, does the same thing |
||
| Line 1: | Line 1: | ||
Suggest a "Stop All Animations" button be added to Animations selection frame for convenience. | 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. | |||
[[File:HTML HUD Demo.jpg|200px|thumb|top|screenshots]] | [[File:HTML HUD Demo.jpg|200px|thumb|top|screenshots]] | ||
| Line 470: | Line 472: | ||
} | } | ||
}</lsl> | }</lsl> | ||
<lsl> | |||
/* | |||
* 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; | |||
string ownerName; | |||
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(ownerName+"'s HUD"); | |||
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) | |||
llSetLinkPrimitiveParamsFast(LINK_THIS, [ | |||
PRIM_TEXTURE, ALL_SIDES, TEXTURE_BLANK, <1.0, 1.0, 1.0>, ZERO_VECTOR, (float)FALSE, | |||
PRIM_TEXTURE, 2, "0b815b79-c8f5-fc98-91fc-e77b53a468e2", <1.0, 1.0, 1.0>, ZERO_VECTOR, (float)FALSE]); | |||
toggle_visibility_of_HUD_button(); | |||
request_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: request url | |||
* - does not return anything | |||
* - make sure we drop the old url before requesting a new one | |||
*/ | |||
request_url() | |||
{ | |||
llReleaseURL(url); | |||
llRequestURL(); | |||
} | |||
/* | |||
* 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 = 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) | |||
{ | |||
llReleaseURL(url); | |||
llResetScript(); | |||
} | |||
changed(integer change) | |||
{ | |||
if (change & (CHANGED_OWNER | CHANGED_INVENTORY)) | |||
{ | |||
llReleaseURL(url); | |||
llResetScript(); | |||
} | |||
if (change & (CHANGED_REGION | CHANGED_REGION_START | CHANGED_TELEPORT)) | |||
request_url(); | |||
} | |||
state_entry() | |||
{ | |||
init(); | |||
} | |||
touch_start(integer num_detected) | |||
{ | |||
toggle_visibility_of_HUD_button(); | |||
} | |||
http_request(key id, string method, string body) | |||
{ | |||
integer sendResponseNow = TRUE; | |||
responseStatus = 400; | |||
responseBody = "Unsupported method"; | |||
if (method == URL_REQUEST_GRANTED) | |||
{ | |||
responseStatus = 200; | |||
responseBody = "OK"; | |||
set_link_media(body); | |||
} | |||
else if (method == URL_REQUEST_DENIED) | |||
{ | |||
responseStatus = 400; | |||
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 == []) | |||
{ | |||
sendResponseNow = FALSE; | |||
llSensor("", NULL_KEY, AGENT_BY_LEGACY_NAME, 96.0, PI); | |||
lastPath = []; | |||
} | |||
else | |||
http_get(id, path); | |||
} | |||
/* | |||
* check if doing a sensor sweep and if so don't send a response | |||
* but send the response in 'sensor event' or 'no_sensor event' | |||
* | |||
* (time-out 30.0 seconds for response) | |||
*/ | |||
if (sendResponseNow) | |||
{ | |||
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; | |||
} | |||
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() | |||
{ | |||
llReleaseURL(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(); | |||
} | |||
} | |||
</lsl> | |||
Revision as of 06:40, 31 October 2011
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.

<lsl>// HTML Based, Single Script HUD // This is intended to be a tech demo of what html on a hud allows. // * Interaction with SL world (give items, play animations, scan nearby avatars) // * Simple, low overhead (1 scripts, 1 prim) // * Rich, dynamic interface (images, menus, flash) // Updates and improvements are welcome at http://wiki.secondlife.com/wiki/HTML_HUD_Demo
// To Use: // * Create a cube and wear it as a hud on the top-left. (script will need tweaking for other spots) // * Edit the cube while wearing it and add this script to it. // * Add any animations you want to play to the object // * Add any notecards or objects you want to give away to the object.
// License: // This script itself is free to share, modify and use without restriction. // Any linked or referenced files are not included in this license and are licensed by their respective owners under their own respective copyright and other licenses. // Created by Kelly Linden, 2011.
// CONFIG PARAMS
string video_url = "http://www.youtube.com/embed/m7p9IEpPu-c?rel=0";
integer display_face = 4; // The face to display HTML on integer button_face = 2; // The face that shows when hiding the HTML integer link = LINK_THIS; // The prim with media and button.
// Prim params for when media is shown. Rotates and scales to show the media. list visible = [PRIM_POS_LOCAL,<0,-0.13,-0.13>,PRIM_SIZE,<0.01,0.25,0.25>,PRIM_ROT_LOCAL,<0,0,0,1>]; // Prim params for when media is hidden. Rotates and scales to show the button. list button = [PRIM_POS_LOCAL,<0,-0.04,-0.04>,PRIM_SIZE,<0.05,0.05,0.05>,PRIM_ROT_LOCAL,<0,0,-1,0>]; // Initial setup: sets the button texture. list init = [PRIM_TEXTURE,ALL_SIDES,TEXTURE_BLANK,<1,1,1>,<1,1,1>,0,PRIM_TEXTURE,button_face,"0b815b79-c8f5-fc98-91fc-e77b53a468e2",<1,1,1>,<1,1,1>,0];
// INTERNAL VARS // These variables are set by the program. integer is_visible = FALSE; // Whether or not the hud is showing HTML side. list last_path; // Last visited string footer; // Footer: set in set_url string header; // Header: set in set_url key current_request; // HTTP request ID for the current request.
// Toggle between button and HTML toggle_show() {
if (is_visible)
llSetLinkPrimitiveParamsFast(link,visible);
else
llSetLinkPrimitiveParamsFast(link,button);
is_visible = !is_visible;
}
// Build an image url from an avatar's key. string av_image(key id) {
return "https://my-secondlife.s3.amazonaws.com/users/" + llGetUsername(id) + "/thumb_sl_image.png";
}
// Build an url that opens the profile for an avatar string app_profile_url(key id) {
return "secondlife:///app/agent/" + (string)id + "/about";
}
// Setup required after getting an url. set_url(string url) {
// Build the common header:
// * link to the css file
// * set a base url
// * html boilerplate code.
// TODO: Remove dependency on this css file.
header = "<html><head>"
+ "<link href='https://d1979ns0fqtj19.cloudfront.net/assets/common-103828347986224535963905120979424958961.css' media='all' rel='stylesheet' type='text/css' />"
+ "<base href='" + url + "/' />"
+ "</head><body>";
// Build the common footer // * Navigation menu // * JS helper script (for fancy buttons) // * html boilerplate code // TODO: Remove dependency on the js file
footer = "
+ "<a href=>Scan</a> | "
+ "<a href='anims'>Anims</a> | "
+ "<a href='video'>Video</a> | "
+ "<a href='config'>Config</a> | "
+ "<a href='hide'>Hide</a>"
+ "<script src='https://d2mjw3k7q9u8rb.cloudfront.net/assets/common-170919042270376442559931151451605602726.js' type='text/javascript'></script></body></html>";
llSetLinkMedia(link, display_face, // Side to display the media on.
[PRIM_MEDIA_AUTO_PLAY,TRUE, // Show this page immediately
PRIM_MEDIA_CURRENT_URL,url, // The url if they hit 'home'
PRIM_MEDIA_HOME_URL,url, // The url currently showing
PRIM_MEDIA_HEIGHT_PIXELS,256, // Height/width of media texture will be
PRIM_MEDIA_WIDTH_PIXELS,256, // rounded up to nearest power of 2.
PRIM_MEDIA_PERMS_CONTROL, PRIM_MEDIA_PERM_NONE]);
}
// Turn a number of bytes (script size) into human readable format. string bytes2str(integer b) {
if (b < 1024 * 1024) return (string)(b / 1024) + "KB"; return (string)(b / (1024 * 1024)) + "MB";
}
// Build the html for the profile thumbnail image that links to their profile. string profile_thumb_html(key agent) {
return "<a href='" + app_profile_url(agent) + "' class='avatar avatar_thumb' rel='#sl_image_zoom' title='Click to zoom'>"
+ "<img alt='Thumb_sl_image' src='https://my-secondlife.s3.amazonaws.com/users/" + llGetUsername(agent) + "/sl_image.png' /></a>";
}
// Build the agent options menu string agent_menu_html(key agentid) {
return "
";
}
// Build an html list of items of type for the Give menu string give_list(key agentid, integer type) {
string resp;
integer n = llGetInventoryNumber(type);
integer i;
for (i=0;i<n;++i)
{
resp += "
"; } return resp; } // Build the html for the Give menu and fill it with notecards and objects. string give_menu_html(key agentid) { string resp = "
";
return resp;
}
// Strip the "Resident" last name, break long names into 2 lines if possible. string agent_name(key id) {
string name = llList2String(llParseString2List(llKey2Name(id),[" Resident"],[]),0);
if (llStringLength(name) > 15)
{
name = llDumpList2String(llParseString2List(name,[" "],[]),"
");
}
return name;
}
// Build the Agent Details page. agent_details(key request, key agent) {
// Get the details we want. list r = llGetObjectDetails(agent,[OBJECT_POS,OBJECT_TOTAL_SCRIPT_COUNT,OBJECT_SCRIPT_MEMORY,OBJECT_SCRIPT_TIME]); // Build the html.
string resp = header + "
" + "" + ""
+ "
" + agent_name(agent) + "" + "" + llGetUsername(agent) + "" + " | " + agent_menu_html(agent) + " " + give_menu_html(agent) + " | |
| " + profile_thumb_html(agent) + " | "
+ "
| |
" + footer;
// Send the response.
llSetContentType(current_request, CONTENT_TYPE_HTML);
llHTTPResponse(current_request,200,resp);
// Uncomment this to debug memory usage.
// Done here as this is the largest, most complex page.
// llOwnerSay("Memory used: " + (string)llGetUsedMemory() + ", response size: " + (string)llStringLength(resp));
}
// Build up an animation menu with any animations in the object. string anim_menu_html() {
string resp = "
";
return resp;
}
// Play an animation given the name. play_anim(string anim) {
llRequestPermissions(llGetOwner(),PERMISSION_TRIGGER_ANIMATION); llStartAnimation(llUnescapeURL(anim));
}
// Build the anims page. anims_page() {
string resp = header + "
Animations
Choose an animation:
" + "
" + footer;
llSetContentType(current_request, CONTENT_TYPE_HTML); llHTTPResponse(current_request,200,resp);
}
// Build the configuration page. config_page() {
string resp = header + "
Options:
"
+ "<form action='config/set' method='get'>"
+ "Video URL: <input type='text' name='video' value='" + video_url + "' />"
+ "<input type='submit' value='Set' /></form>"
+ footer;
llSetContentType(current_request, CONTENT_TYPE_HTML);
llHTTPResponse(current_request,200,resp);
}
// Process a get request with a path. GET(key request, list path) {
current_request = request;
integer path_segments = llGetListLength(path);
if (path_segments == 0)
{
// Home page is handled in the sensor response.
llSensor("",NULL_KEY,AGENT,96,PI);
// Set the last_path incase the next request is a hide
last_path = [];
return;
}
string p0 = llList2String(path,0);
if (p0 == "hide")
{
toggle_show();
// We need to send back a page to show otherwise the
// the next time the hud is shown there will be an error.
// So we loop back in here with our last path.
// Effectively this just refreshes the page we were already on.
GET(request,last_path);
// Do *not* set the last_path here to avoid any infinite loops.
return;
}
// Set the last path here, after the recursive hide call.
last_path = path;
if (p0 == "agent")
{
if (path_segments == 1)
{
// <url>/agent
// Show owner details.
agent_details(request,llGetOwner());
return;
}
// p1 should be an agent id
key agent = (key)llList2String(path,1);
if (path_segments == 2)
{
// <url>/agent/<id>
// Show the details for the agent.
agent_details(request,agent);
return;
}
if (path_segments == 4)
{
// <url>/agent/<id>/<cmd>/<option>
string cmd = llList2String(path,2);
if (cmd == "give")
{
string object_name = llUnescapeURL(llList2String(path,3));
llOwnerSay("Giving " + object_name + " to " + llKey2Name((key)agent));
llGiveInventory(agent,object_name);
agent_details(request,agent);
return;
}
}
}
else if (p0 == "anims")
{
if (path_segments == 1)
{
// <url>/anims
// Show the animation selection page
anims_page();
return;
}
else if (path_segments == 2)
{
// <url>/anims/<anim-name>
// Play the specified animation
play_anim(llList2String(path,1));
anims_page();
return;
}
}
else if (p0 == "video")
{
// <url>/video
// Play a demo video
string resp = header
+ "
<iframe width='255' height='173' src='" + video_url + "' frameborder='0' allowfullscreen></iframe>"
+ footer;
llSetContentType(current_request, CONTENT_TYPE_HTML);
llHTTPResponse(current_request,200,resp);
return;
}
else if (p0 == "config")
{
if (path_segments == 1)
{
// <url>/config
config_page();
return;
}
if (llList2String(path,1) == "set")
{
// Parse config args
list args = llParseString2List(llGetHTTPHeader(current_request,"x-query-string"),["&","="],[]);
integer i = 0;
integer argn = llGetListLength(args);
while(i < argn)
{
string var = llList2String(args,i++);
string val = llUnescapeURL(llList2String(args,i++));
if (var == "video")
{
video_url = val;
}
}
config_page();
}
}
// Unknown page. 404.
string resp = header + "
404 Page Not Found.
" + footer;
llSetContentType(current_request, CONTENT_TYPE_HTML); llHTTPResponse(current_request,404,resp);
}
default {
state_entry()
{
// Set the object name so inventory offers make sense to the receiver.
llSetObjectName(llKey2Name(llGetOwner()) + "'s HUD");
// Init button textures.
llSetLinkPrimitiveParamsFast(link,init);
// Reset to a hidden state
toggle_show();
// Request an URL.
llRequestURL();
}
// We need to reset in the cases because we lose the URL.
on_rez(integer n) { llResetScript();}
changed(integer c)
{
if (c & (CHANGED_TELEPORT | CHANGED_OWNER | CHANGED_REGION)) llResetScript();
}
touch_start(integer total_number)
{
// Handles the button case.
toggle_show();
}
http_request(key id, string method, string body)
{
if (method == URL_REQUEST_GRANTED)
{
set_url(body);
return;
}
else if (method == URL_REQUEST_DENIED)
{
llOwnerSay("Unable to get url: " + body);
llSleep(10);
llRequestURL();
return;
}
if (method == "GET")
{
list path = llParseString2List(llGetHTTPHeader(id,"x-path-info"),["/"],[]);
GET(id,path);
return;
}
llHTTPResponse(id,400,"Unsupported method.");
return;
}
sensor(integer n)
{
// This is actually the main page.
// Shows nearby residents for interacting with.
string resp = header + "
Scan Results:
- ";
// TODO: Only fits 14 right now. Figure out how to fit all 16.
// TODO: Figure out how to find more than 16.
if (n > 14) n = 14;
for (--n; n >= 0; --n)
{
resp += "" + footer;
llSetContentType(current_request, CONTENT_TYPE_HTML);
llHTTPResponse(current_request,200,resp);
}
no_sensor()
{
// No one nearby.
// Include a link to the owner's details to make it easier to debug when no one is around.
string resp = header + "
Scan Results:
- ";
resp += "
- No one near by. "; resp += "
- Owner: <a href='agent/" + (string)llGetOwner() + "'>" + llKey2Name(llGetOwner()) + "</a> "; resp += "
" + footer;
llSetContentType(current_request, CONTENT_TYPE_HTML);
llHTTPResponse(current_request,200,resp);
}
}</lsl>
<lsl> /*
- 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; string ownerName;
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(ownerName+"'s HUD");
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)
llSetLinkPrimitiveParamsFast(LINK_THIS, [
PRIM_TEXTURE, ALL_SIDES, TEXTURE_BLANK, <1.0, 1.0, 1.0>, ZERO_VECTOR, (float)FALSE,
PRIM_TEXTURE, 2, "0b815b79-c8f5-fc98-91fc-e77b53a468e2", <1.0, 1.0, 1.0>, ZERO_VECTOR, (float)FALSE]);
toggle_visibility_of_HUD_button(); request_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: request url
- - does not return anything
- - make sure we drop the old url before requesting a new one
- /
request_url() {
llReleaseURL(url); llRequestURL();
}
/*
- 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 = "
<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 + "
<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 = index + 3;
}
config_page();
}
}
responseStatus = 404;
responseBody = header + "
404 Page Not Found.
" + 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 + "
" + html_body_with_formatted_avatar_name(id) + "" + llGetUsername(id) + " | " + html_body_with_links_for_interaction_with_certain_avatar(id) + " " + html_body_with_inventory_overview_for_give_menu(id) + " | |
| " + html_body_avatar_profile_pic_thumbnail(id) + " |
| |
" + 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,[" "], []), "
");
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 "
";
}
/*
- 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 = "
";
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 += "
"; } return stringToReturn; } /*
- user-function: anims_page
- - does not return anything
- - prepares an html text overview page of animations
- /
anims_page() { responseStatus = 200; responseBody = header + "
Animations
Choose an animation:
" + footer;
}
/*
- user-function: html_body_animations_overview
- - returns html text with a list of included animations
- /
string html_body_animations_overview() {
string stringToReturn = "
";
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 + "
Options:
<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)
{
llReleaseURL(url);
llResetScript();
}
changed(integer change)
{
if (change & (CHANGED_OWNER | CHANGED_INVENTORY))
{
llReleaseURL(url);
llResetScript();
}
if (change & (CHANGED_REGION | CHANGED_REGION_START | CHANGED_TELEPORT))
request_url();
}
state_entry()
{
init();
}
touch_start(integer num_detected)
{
toggle_visibility_of_HUD_button();
}
http_request(key id, string method, string body)
{
integer sendResponseNow = TRUE;
responseStatus = 400;
responseBody = "Unsupported method";
if (method == URL_REQUEST_GRANTED)
{
responseStatus = 200;
responseBody = "OK";
set_link_media(body);
}
else if (method == URL_REQUEST_DENIED)
{
responseStatus = 400;
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 == [])
{
sendResponseNow = FALSE;
llSensor("", NULL_KEY, AGENT_BY_LEGACY_NAME, 96.0, PI);
lastPath = [];
}
else
http_get(id, path);
}
/*
- check if doing a sensor sweep and if so don't send a response
- but send the response in 'sensor event' or 'no_sensor event'
- (time-out 30.0 seconds for response)
- /
if (sendResponseNow)
{
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;
}
sensor(integer num_detected)
{
responseStatus = 200;
responseBody = header + "
Scan Results:
- ";
if (14 < num_detected)
num_detected = 14;
while (num_detected)
{
--num_detected;
key id = llDetectedKey(num_detected);responseBody += "
" + footer;
llSetContentType(currentRequestID, CONTENT_TYPE_HTML);
llHTTPResponse(currentRequestID, responseStatus, responseBody);
}
no_sensor()
{
responseStatus = 200;
responseBody = header + "
Scan Results:
- No one near by.
- Owner: <a href='agent/" + (string)owner + "'>" + ownerName + "</a>
" + footer;
llSetContentType(currentRequestID, CONTENT_TYPE_HTML);
llHTTPResponse(currentRequestID, responseStatus, responseBody);
}
state_exit()
{
llReleaseURL(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();
}
} </lsl>