Difference between revisions of "HTML HUD Demo"
Jump to navigation
Jump to search
Kireji Haiku (talk | contribs) (added formatted copy, does the same thing) |
(This is so useful (even if dated and with caveats!) that I added it to the LSL Library) |
||
(13 intermediate revisions by 4 users not shown) | |||
Line 1: | Line 1: | ||
{{LSL Header}} | |||
<span id="top"></span> | |||
{{notice|Some of the links/HTML used in this very old script may require updating/adjusting; see also the [[Talk:HTML HUD Demo|Discussion page]] for some hints on what to change and where (notice posted on {{CURRENTYEAR}}-{{CURRENTMONTH}}-{{CURRENTDAY}}).}} | |||
{{TOC right}} | |||
=TODO:= | |||
* Suggest a <code>Stop All Animations</code> button be added to Animations selection frame for convenience. | |||
=HTML HUD Demo:= | |||
== Screenshot: == | |||
[[File:HTML HUD Demo.jpg|200px|thumb|top|screenshots]] | |||
==Tip:== | |||
{{KBtip|This HUD might take a second or two to load when switching from 'closed' to 'open' mode.}} | |||
{ | |||
} | |||
==Source code:== | |||
<syntaxhighlight lang="lsl2"> | |||
// 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; | 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 | ||
// - does not return anything | |||
// - sets initial variable values | |||
// - sets object's name and textures | |||
// - request a url to use the HUD | |||
init() | init() | ||
{ | { | ||
owner = llGetOwner(); | owner = llGetOwner(); | ||
ownerName = llKey2Name(owner); | ownerName = llKey2Name(owner); | ||
llSetObjectName( | 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) | |||
// 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, | PRIM_TEXTURE, ALL_SIDES, TEXTURE_BLANK, RATIO_ONE_BY_ONE, ZERO_VECTOR, FLOAT_FALSE, | ||
PRIM_TEXTURE, 2, "0b815b79-c8f5-fc98-91fc-e77b53a468e2", | 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_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() | 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 | ||
// - does not return anything | |||
release_url() | |||
{ | { | ||
llReleaseURL(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) | set_link_media(string scriptUrl) | ||
Line 587: | Line 147: | ||
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=''>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_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) | 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 640: | Line 205: | ||
{ | { | ||
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 669: | Line 236: | ||
{ | { | ||
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 682: | Line 252: | ||
{ | { | ||
string queryString = llGetHTTPHeader(requestID, "x-query-string"); | string queryString = llGetHTTPHeader(requestID, "x-query-string"); | ||
list args = llParseString2List(queryString, ["="], ["&"]); | list args = llParseString2List(queryString, ["="], ["&"]); | ||
integer index = | |||
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, &, ... | |||
index = | index += 3; | ||
} | } | ||
config_page(); | config_page(); | ||
Line 700: | Line 274: | ||
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 | ||
// - 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) | prepare_profile_overview_page(key requestID, key id) | ||
{ | { | ||
list avatarDetails = llGetObjectDetails(id, [ | list avatarDetails = llGetObjectDetails(id, [ | ||
OBJECT_POS, OBJECT_TOTAL_SCRIPT_COUNT, | |||
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 | ||
// - 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 html_body_with_formatted_avatar_name(key id) | ||
Line 734: | Line 320: | ||
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 | ||
// - 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 | ||
// - 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 767: | Line 365: | ||
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 773: | Line 373: | ||
} | } | ||
/ | // user-function: bytes2str | ||
// - returns a string with script memory info in readable format | |||
string bytes2str(integer bytes) | string bytes2str(integer bytes) | ||
{ | { | ||
// 1024² = 1048576 | |||
if (bytes < 1048576) | if (bytes < 1048576) | ||
return (string)(bytes / 1024) + " KB"; | return (string)(bytes / 1024) + " KB"; | ||
// else | |||
return (string)(bytes / 1048576) + " MB"; | 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) | 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 | ||
// - 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 814: | Line 411: | ||
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 820: | Line 418: | ||
} | } | ||
/ | // user-function: anims_page | ||
// - does not return anything | |||
// - 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 | ||
// - 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'> </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'> </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 860: | Line 460: | ||
} | } | ||
/ | // user-function: play_anim | ||
// - does not return anything | |||
// - prompts a perms request to animate owner | |||
play_anim(string anim) | play_anim(string anim) | ||
Line 872: | Line 470: | ||
} | } | ||
/ | // user-function: config_page | ||
// - does not return anything | |||
// - 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 | ||
// - does not return anything | |||
// - 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) | ||
{ | { | ||
release_url(); | |||
llResetScript(); | llResetScript(); | ||
} | } | ||
Line 910: | Line 510: | ||
if (change & (CHANGED_OWNER | CHANGED_INVENTORY)) | if (change & (CHANGED_OWNER | CHANGED_INVENTORY)) | ||
{ | { | ||
release_url(); | |||
llResetScript(); | llResetScript(); | ||
} | } | ||
if (change & (CHANGED_REGION | CHANGED_REGION_START | CHANGED_TELEPORT)) | if (change & (CHANGED_REGION | CHANGED_REGION_START | CHANGED_TELEPORT)) | ||
{ | |||
request_secure_url(); | |||
} | |||
} | } | ||
Line 930: | Line 532: | ||
http_request(key id, string method, string body) | http_request(key id, string method, string body) | ||
{ | { | ||
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 944: | Line 544: | ||
else if (method == URL_REQUEST_DENIED) | else if (method == URL_REQUEST_DENIED) | ||
{ | { | ||
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 == []) | ||
{ | { | ||
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 = []; | lastPath = []; | ||
} | } | ||
else | else | ||
{ | |||
http_get(id, path); | http_get(id, path); | ||
} | |||
} | } | ||
llSetContentType(id, CONTENT_TYPE_HTML); | |||
llHTTPResponse(id, responseStatus, responseBody); | |||
if ( | if (exceptions != "") | ||
{ | { | ||
state error; | |||
} | } | ||
} | } | ||
Line 991: | Line 613: | ||
} | } | ||
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; | ||
} | } | ||
} | } | ||
state_exit() | state_exit() | ||
{ | { | ||
release_url(); | |||
} | } | ||
} | } | ||
Line 1,045: | Line 641: | ||
{ | { | ||
if (change & (CHANGED_OWNER | CHANGED_INVENTORY)) | if (change & (CHANGED_OWNER | CHANGED_INVENTORY)) | ||
{ | |||
llResetScript(); | llResetScript(); | ||
} | |||
} | } | ||
Line 1,057: | Line 655: | ||
} | } | ||
} | } | ||
</ | </syntaxhighlight> | ||
[[#top|Go to top!]] | |||
[[Category:LSL Library]] |
Latest revision as of 15:43, 10 June 2023
LSL Portal | Functions | Events | Types | Operators | Constants | Flow Control | Script Library | Categorized Library | Tutorials |
Some of the links/HTML used in this very old script may require updating/adjusting; see also the Discussion page for some hints on what to change and where (notice posted on 2025-01-3). |
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();
}
}