HTML HUD Demo
Jump to navigation
Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
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 2024-04-26). |
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();
}
}