/visitor-track-and-record/XyzzyText.lsl |
@@ -0,0 +1,711 @@ |
//////////////////////////////////////////// |
// XyzzyText v2.1(10-Char) by Thraxis Epsilon |
// XyzzyText v2.1 Script (Set Line Color) by Huney Jewell |
// XyzzyText v2.0 Script (5 Face, Single Texture) |
// |
// Edited trivially by Joel Cloquet on 1/2011 to use the (relatively) |
// new llSetLinkPrimitiveParamsFast function, thereby removing the need in |
// some cases to use the slave script. |
// |
// Heavily Modified by Thraxis Epsilon, Gigs Taggart 5/2007 and Strife Onizuka 8/2007 |
// Rewrite to allow one-script-per-object operation w/ optional slaves |
// Enable prim-label functionality |
// Enabled Banking |
// Enabled 10-char per prim |
// |
// Modified by Kermitt Quirk 19/01/2006 |
// To add support for 5 face prim instead of 3 |
// |
// Core XyText Originally Written by Xylor Baysklef |
// |
// |
//////////////////////////////////////////// |
|
/////////////// CONSTANTS /////////////////// |
// XyText Message Map. |
integer DISPLAY_STRING = 204000; |
integer DISPLAY_EXTENDED = 204001; |
integer REMAP_INDICES = 204002; |
integer RESET_INDICES = 204003; |
integer SET_FADE_OPTIONS = 204004; |
integer SET_FONT_TEXTURE = 204005; |
integer SET_LINE_COLOR = 204006; |
integer SET_COLOR = 204007; |
integer RESCAN_LINKSET = 204008; |
|
//internal API |
integer REGISTER_SLAVE = 205000; |
integer SLAVE_RECOGNIZED = 205001; |
integer SLAVE_DISPLAY = 205003; |
integer SLAVE_DISPLAY_EXTENDED = 205004; |
integer SLAVE_RESET = 205005; |
|
// This is an extended character escape sequence. |
string ESCAPE_SEQUENCE = "\\e"; |
|
// This is used to get an index for the extended character. |
string EXTENDED_INDEX = "123456789abcdef"; |
|
|
// Face numbers. |
integer FACE_1 = 3; |
integer FACE_2 = 7; |
integer FACE_3 = 4; |
integer FACE_4 = 6; |
integer FACE_5 = 1; |
|
// Used to hide the text after a fade-out. |
key TRANSPARENT = "701917a8-d614-471f-13dd-5f4644e36e3c"; |
key null_key = NULL_KEY; |
|
// This is a list of textures for all 2-character combinations. |
list CHARACTER_GRID = [ |
"00e9f9f7-0669-181c-c192-7f8e67678c8d", |
"347a5cb6-0031-7ec0-2fcf-f298eebf3c0e", |
"4e7e689e-37f1-9eca-8596-a958bbd23963", |
"19ea9c21-67ba-8f6f-99db-573b1b877eb1", |
"dde7b412-cda1-652f-6fc2-73f4641f96e1", |
"af6fa3bb-3a6c-9c4f-4bf5-d1c126c830da", |
"a201d3a2-364b-43b6-8686-5881c0f82a94", |
"b674dec8-fead-99e5-c28d-2db8e4c51540", |
"366e05f3-be6b-e5cf-c33b-731dff649caa", |
"75c4925c-0427-dc0c-c71c-e28674ff4d27", |
"dcbe166b-6a97-efb2-fc8e-e5bc6a8b1be6", |
"0dca2feb-fc66-a762-db85-89026a4ecd68", |
"a0fca76f-503a-946b-9336-0a918e886f7a", |
"67fb375d-89a1-5a4f-8c7a-0cd1c066ffc4", |
"300470b2-da34-5470-074c-1b8464ca050c", |
"d1f8e91c-ce2b-d85e-2120-930d3b630946", |
"2a190e44-7b29-dadb-0bff-c31adaf5a170", |
"75d55e71-f6f8-9835-e746-a45f189f30a1", |
"300fac33-2b30-3da3-26bc-e2d70428ec19", |
"0747c776-011a-53ce-13ee-8b5bb9e87c1e", |
"85a855c3-a94f-01ca-33e0-7dde92e727e2", |
"cbc1dab2-2d61-2986-1949-7a5235c954e1", |
"f7aef047-f266-9596-16df-641010edd8e1", |
"4c34ebf7-e5e1-2e1a-579f-e224d9d5e71b", |
"4a69e98c-26a5-ad05-e92e-b5b906ad9ef9", |
"462a9226-2a97-91ac-2d89-57ab33334b78", |
"20b24b3a-8c57-82ee-c6ed-555003f5dbcd", |
"9b481daa-9ea8-a9fa-1ee4-ab9a0d38e217", |
"c231dbdc-c842-15b0-7aa6-6da14745cfdc", |
"c97e3cbb-c9a3-45df-a0ae-955c1f4bf9cf", |
"f1e7d030-ff80-a242-cb69-f6951d4eae3b", |
"ed32d6c4-d733-c0f1-f242-6df1d222220d", |
"88f96a30-dccf-9b20-31ef-da0dfeb23c72", |
"252f2595-58b8-4bcc-6515-fa274d0cfb65", |
"f2838c4f-de80-cced-dff8-195dfdf36b2c", |
"cc2594fe-add2-a3df-cdb3-a61711badf53", |
"e0ce2972-da00-955c-129e-3289b3676776", |
"3e0d336d-321f-ddfa-5c1b-e26131766f6a", |
"d43b1dc4-6b51-76a7-8b90-38865b82bf06", |
"06d16cbb-1868-fd1d-5c93-eae42164a37d", |
"dd5d98cf-273e-3fd0-f030-48be58ee3a0b", |
"0e47c89e-de4a-6233-a2da-cb852aad1b00", |
"fb9c4a55-0e13-495b-25c4-f0b459dc06de", |
"e3ce8def-312c-735b-0e48-018b6799c883", |
"2f713216-4e71-d123-03ed-9c8554710c6b", |
"4a417d8a-1f4f-404b-9783-6672f8527911", |
"ca5e21ec-5b20-5909-4c31-3f90d7316b33", |
"06a4fcc3-e1c4-296d-8817-01f88fbd7367", |
"130ac084-6f3c-95de-b5b6-d25c80703474", |
"59d540a0-ae9d-3606-5ae0-4f2842b64cfa", |
"8612ae9a-f53c-5bf4-2899-8174d7abc4fd", |
"12467401-e979-2c49-34e0-6ac761542797", |
"d53c3eaa-0404-3860-0675-3e375596c3e3", |
"9f5b26bd-81d3-b25e-62fe-5b671d1e3e79", |
"f57f0b64-a050-d617-ee00-c8e9e3adc9cb", |
"beff166a-f5f3-f05e-e020-98f2b00e27ed", |
"02278a65-94ba-6d5e-0d2b-93f2e4f4bf70", |
"a707197d-449e-5b58-846c-0c850c61f9d6", |
"021d4b1a-9503-a44f-ee2b-976eb5d80e68", |
"0ae2ffae-7265-524d-cb76-c2b691992706"]; |
list CHARACTER_GRID2 = [ |
"f6e41cf2-1104-bd0b-0190-dffad1bac813", |
"2b4bb15e-956d-56ae-69f5-d26a20de0ce7", |
"f816da2c-51f1-612a-2029-a542db7db882", |
"345fea05-c7be-465c-409f-9dcb3bd2aa07", |
"b3017e02-c063-5185-acd5-1ef5f9d79b89", |
"4dcff365-1971-3c2b-d73c-77e1dc54242a" |
]; |
|
///////////// END CONSTANTS //////////////// |
|
///////////// GLOBAL VARIABLES /////////////// |
// All displayable characters. Default to ASCII order. |
string gCharIndex; |
// This is the channel to listen on while acting |
// as a cell in a larger display. |
integer gCellChannel = -1; |
// This is the starting character position in the cell channel message |
// to render. |
integer gCellCharPosition = 0; |
// This is whether or not to use the fade in/out special effect. |
integer gCellUseFading = FALSE; |
// This is how long to display the text before fading out (if using |
// fading special effect). |
// Note: < 0 means don't fade out. |
float gCellHoldDelay = 1.0; |
|
integer gSlaveRegistered; |
list gSlaveNames; |
|
integer BANK_STRIDE=3; //offset, length, highest_dirty |
list gBankingData; |
|
/////////// END GLOBAL VARIABLES //////////// |
|
ResetCharIndex() { |
gCharIndex = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"; |
// \" <-- Fixes LSL syntax highlighting bug. |
gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~"; |
gCharIndex += "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"; |
} |
|
vector GetGridPos(integer index1, integer index2) { |
// There are two ways to use the lookup table... |
integer Col; |
integer Row; |
if (index1 >= index2) { |
// In this case, the row is the index of the first character: |
Row = index1; |
// And the col is the index of the second character (x2) |
Col = index2 * 2; |
} |
else { // Index1 < Index2 |
// In this case, the row is the index of the second character: |
Row = index2; |
// And the col is the index of the first character, x2, offset by 1. |
Col = index1 * 2 + 1; |
} |
return <Col, Row, 0>; |
} |
|
string GetGridTexture(vector grid_pos) { |
// Calculate the texture in the grid to use. |
integer GridCol = llRound(grid_pos.x) / 20; |
integer GridRow = llRound(grid_pos.y) / 10; |
|
// Lookup the texture. |
key Texture = llList2Key(CHARACTER_GRID, GridRow * (GridRow + 1) / 2 + GridCol); |
return Texture; |
} |
|
vector GetGridOffset(vector grid_pos) { |
// Zoom in on the texture showing our character pair. |
integer Col = llRound(grid_pos.x) % 20; |
integer Row = llRound(grid_pos.y) % 10; |
|
// Return the offset in the texture. |
return <-0.45 + 0.05 * Col, 0.45 - 0.1 * Row, 0.0>; |
} |
|
ShowChars(integer link,vector grid_pos1, vector grid_pos2, vector grid_pos3, vector grid_pos4, vector grid_pos5) { |
// Set the primitive textures directly. |
|
|
llSetLinkPrimitiveParamsFast( link , [ |
PRIM_TEXTURE, FACE_1, GetGridTexture(grid_pos1), <0.25, 0.1, 0>, GetGridOffset(grid_pos1) + <0.075, 0, 0>, 0.0, |
PRIM_TEXTURE, FACE_2, GetGridTexture(grid_pos2), <0.1, 0.1, 0>, GetGridOffset(grid_pos2), 0.0, |
PRIM_TEXTURE, FACE_3, GetGridTexture(grid_pos3), <-1.48, 0.1, 0>, GetGridOffset(grid_pos3)+ <0.37, 0, 0>, 0.0, |
PRIM_TEXTURE, FACE_4, GetGridTexture(grid_pos4), <0.1, 0.1, 0>, GetGridOffset(grid_pos4), 0.0, |
PRIM_TEXTURE, FACE_5, GetGridTexture(grid_pos5), <0.25, 0.1, 0>, GetGridOffset(grid_pos5) - <0.075, 0, 0>, 0.0 |
]); |
} |
|
RenderString(integer link, string str) { |
// Get the grid positions for each pair of characters. |
vector GridPos1 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)), |
llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) ); |
vector GridPos2 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)), |
llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) ); |
vector GridPos3 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)), |
llSubStringIndex(gCharIndex, llGetSubString(str, 5, 5)) ); |
vector GridPos4 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 6, 6)), |
llSubStringIndex(gCharIndex, llGetSubString(str, 7, 7)) ); |
vector GridPos5 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 8, 8)), |
llSubStringIndex(gCharIndex, llGetSubString(str, 9, 9)) ); |
|
// Use these grid positions to display the correct textures/offsets. |
ShowChars(link,GridPos1, GridPos2, GridPos3, GridPos4, GridPos5); |
} |
|
//RenderWithEffects(integer link, string str) { |
// // Get the grid positions for each pair of characters. |
// vector GridPos1 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)), |
// llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) ); |
// vector GridPos2 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)), |
// llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) ); |
// vector GridPos3 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)), |
// llSubStringIndex(gCharIndex, llGetSubString(str, 5, 5)) ); |
// vector GridPos4 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 6, 6)), |
// llSubStringIndex(gCharIndex, llGetSubString(str, 7, 7)) ); |
// vector GridPos5 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 8, 8)), |
// llSubStringIndex(gCharIndex, llGetSubString(str, 9, 9)) ); // |
// |
// // First set the alpha to the lowest possible. |
// llSetAlpha(0.05, ALL_SIDES); |
// |
// // Use these grid positions to display the correct textures/offsets. |
// ShowChars(link,GridPos1, GridPos2, GridPos3, GridPos4, GridPos5); |
// |
// float Alpha = 0.10; |
// for (; Alpha <= 1.0; Alpha += 0.05) |
// llSetAlpha(Alpha, ALL_SIDES); |
// // See if we want to fade out as well. |
// if (gCellHoldDelay < 0.0) |
// // No, bail out. (Just keep showing the string at full strength). |
// return; |
// // Hold the text for a while. |
// llSleep(gCellHoldDelay); |
// // Now fade out. |
// for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05) |
// llSetAlpha(Alpha, ALL_SIDES); |
// // Make the text transparent to fully hide it. |
// llSetTexture(TRANSPARENT, ALL_SIDES); |
//} |
|
integer RenderExtended(integer link, string str,integer render) { |
// Look for escape sequences. |
integer length = 0; |
list Parsed = llParseString2List(str, [], (list)ESCAPE_SEQUENCE); |
integer ParsedLen = llGetListLength(Parsed); |
|
// Create a list of index values to work with. |
list Indices; |
// We start with room for 6 indices. |
integer IndicesLeft = 10; |
|
string Token; |
integer Clipped; |
integer LastWasEscapeSequence = FALSE; |
// Work from left to right. |
integer i=0; |
for (; i < ParsedLen && IndicesLeft > 0; ++i) { |
Token = llList2String(Parsed, i); |
|
// If this is an escape sequence, just set the flag and move on. |
if (Token == ESCAPE_SEQUENCE) { |
LastWasEscapeSequence = TRUE; |
} |
else { // Token != ESCAPE_SEQUENCE |
// Otherwise this is a normal token. Check its length. |
Clipped = FALSE; |
integer TokenLength = llStringLength(Token); |
// Clip if necessary. |
if (TokenLength > IndicesLeft) { |
TokenLength = llStringLength(Token = llGetSubString(Token, 0, IndicesLeft - 1)); |
IndicesLeft = 0; |
Clipped = TRUE; |
} |
else |
IndicesLeft -= TokenLength; |
|
// Was the previous token an escape sequence? |
if (LastWasEscapeSequence) { |
// Yes, the first character is an escape character, the rest are normal. |
length += 2 + TokenLength; |
if (render) |
{ |
// This is the extended character. |
Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95]; |
|
// These are the normal characters. |
integer j=1; |
for (; j < TokenLength; ++j) |
{ |
Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))]; |
} |
} |
} |
else { // Normal string. |
// Just add the characters normally. |
length += TokenLength; |
if(render) |
{ |
integer j=0; |
for (; j < TokenLength; ++j) |
{ |
Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))]; |
} |
} |
} |
|
// Unset this flag, since this was not an escape sequence. |
LastWasEscapeSequence = FALSE; |
} |
} |
|
if(render) |
{ |
// Use the indices to create grid positions. |
vector GridPos1 = GetGridPos( llList2Integer(Indices, 0), llList2Integer(Indices, 1) ); |
vector GridPos2 = GetGridPos( llList2Integer(Indices, 2), llList2Integer(Indices, 3) ); |
vector GridPos3 = GetGridPos( llList2Integer(Indices, 4), llList2Integer(Indices, 5) ); |
vector GridPos4 = GetGridPos( llList2Integer(Indices, 6), llList2Integer(Indices, 7) ); |
vector GridPos5 = GetGridPos( llList2Integer(Indices, 8), llList2Integer(Indices, 9) ); |
|
// Use these grid positions to display the correct textures/offsets. |
ShowChars(link,GridPos1, GridPos2, GridPos3, GridPos4, GridPos5); |
} |
return length; |
} |
|
|
integer ConvertIndex(integer index) { |
// This converts from an ASCII based index to our indexing scheme. |
if (index >= 32) // ' ' or higher |
index -= 32; |
else { // index < 32 |
// Quick bounds check. |
if (index > 15) |
index = 15; |
|
index += 94; // extended characters |
} |
|
return index; |
} |
|
|
PassToRender(integer render,string message, integer bank) |
{ |
float time; |
integer extendedlen = 0; |
integer link; |
|
integer i = 0; |
integer msgLen = llStringLength(message); |
string TextToRender; |
integer num_slaves=llGetListLength(gSlaveNames); |
string slave_name; //avoids unnecessary casts, keeping it as a string |
|
|
//get the bank offset and length |
integer bank_offset=llList2Integer(gBankingData, (bank * BANK_STRIDE)); |
integer bank_length=llList2Integer(gBankingData, (bank * BANK_STRIDE) + 1); |
integer bank_highest_dirty=llList2Integer(gBankingData, (bank * BANK_STRIDE) + 2); |
|
integer x=0; |
for (;x < msgLen;x = x + 10) |
{ |
|
if (i >= bank_length) //we don't want to run off the end of the bank |
{ |
//set the dirty to max, and bail out, we're done |
gBankingData=llListReplaceList(gBankingData, [bank_length], (bank * BANK_STRIDE) + 2, (bank * BANK_STRIDE) + 2); |
return; |
} |
|
link = unpack(gXyTextPrims,(i + bank_offset)); |
TextToRender = llGetSubString(message, x, x + 20); |
|
if(gSlaveRegistered && (link % (num_slaves +1))) |
{ |
slave_name=llList2String(gSlaveNames, (link % (num_slaves + 1)) - 1); |
if (render == 1) |
llMessageLinked(LINK_THIS, SLAVE_DISPLAY, TextToRender, (key)((string)link + "," + slave_name)); |
if (render == 2) |
{ |
if(llSubStringIndex(TextToRender,"\e")>x+10) |
{ |
extendedlen = 10; |
} |
else |
{ |
extendedlen = RenderExtended(link,TextToRender,0); |
} |
|
if(extendedlen>10) |
{ |
x += extendedlen-10; |
} |
|
llMessageLinked(LINK_THIS,SLAVE_DISPLAY_EXTENDED,TextToRender,(key)((string)link+","+slave_name)); |
} |
} |
else |
{ |
if (render == 1) |
RenderString(link,TextToRender); |
if (render == 2) |
{ |
extendedlen = RenderExtended(link,TextToRender,1); |
if(extendedlen>10) |
{ |
x += extendedlen-10; |
} |
} |
|
// if (render == 3) |
// RenderWithEffects(link,TextToRender); |
} |
++i; |
} |
|
if (bank_highest_dirty==0) |
bank_highest_dirty=bank_length; |
|
integer current_highest_dirty=i; |
while (i < bank_highest_dirty) |
{ |
link = unpack(gXyTextPrims,(i + bank_offset)); |
|
if(gSlaveRegistered && (link % (num_slaves+1) != 0)) |
{ |
slave_name=llList2String(gSlaveNames, (link % (num_slaves + 1)) - 1); |
llMessageLinked(LINK_THIS, SLAVE_DISPLAY, " ", (key)((string)link + "," + slave_name)); |
//sorry, no fade effect with slave |
} |
else |
{ |
RenderString(link," "); |
} |
++i; |
} |
gBankingData=llListReplaceList(gBankingData, [current_highest_dirty], (bank * BANK_STRIDE) + 2, (bank * BANK_STRIDE) + 2); |
} |
|
// Bitwise Voodoo by Gigs Taggart and optimized by Strife Onizuka |
list gXyTextPrims; |
|
|
integer get_number_of_prims() |
{//ignores avatars. |
integer a = llGetNumberOfPrims(); |
while(llGetAgentSize(llGetLinkKey(a))) |
--a; |
return a; |
} |
|
//functions to pack 8-bit shorts into ints |
list pack_and_insert(list in_list, integer pos, integer value) |
{ |
// //figure out the bitpack position |
// integer pack = pos & 3; //4 bytes per int |
// pos=pos >> 2; |
// integer shifted = value << (pack << 3); |
// integer old_value = llList2Integer(in_list, pos); |
// shifted = old_value | shifted; |
// in_list = llListReplaceList(in_list, (list)shifted, pos, pos); |
// return in_list; |
//Safe optimized version |
integer index = pos >> 2; |
return llListReplaceList(in_list, (list)(llList2Integer(in_list, index) | (value << ((pos & 3) << 3))), index, index); |
} |
|
integer unpack(list in_list, integer pos) |
{ |
return (llList2Integer(in_list, pos >> 2) >> ((pos & 3) << 3)) & 0x000000FF;//unsigned |
// return (llList2Integer(in_list, pos >> 2) << (((~pos) & 3) << 3)) >> 24;//signed |
} |
|
|
change_color(vector color) |
{ |
integer num_prims=llGetListLength(gXyTextPrims) << 2; |
|
integer i = 0; |
|
for (; i<=num_prims; ++i) |
{ |
integer link = unpack(gXyTextPrims,i); |
if (!link) |
return; |
|
llSetLinkPrimitiveParamsFast( link,[ |
PRIM_COLOR, FACE_1, color, 1.0, |
PRIM_COLOR, FACE_2, color, 1.0, |
PRIM_COLOR, FACE_3, color, 1.0, |
PRIM_COLOR, FACE_4, color, 1.0, |
PRIM_COLOR, FACE_5, color, 1.0 |
]); |
} |
} |
|
change_line_color(integer bank, vector color) |
{ |
|
//get the bank offset and length |
integer i = llList2Integer(gBankingData, (bank * BANK_STRIDE)); |
integer bank_end = i + llList2Integer(gBankingData, (bank * BANK_STRIDE) + 1); |
|
for (; i < bank_end; ++i) |
{ |
integer link = unpack(gXyTextPrims,i); |
if (!link) |
return; |
|
llSetLinkPrimitiveParamsFast( link,[ |
PRIM_COLOR, FACE_1, color, 1.0, |
PRIM_COLOR, FACE_2, color, 1.0, |
PRIM_COLOR, FACE_3, color, 1.0, |
PRIM_COLOR, FACE_4, color, 1.0, |
PRIM_COLOR, FACE_5, color, 1.0 |
]); |
} |
} |
|
init() |
{ |
integer num_prims=get_number_of_prims(); |
|
string link_name; |
integer bank=0; |
integer bank_empty=FALSE; |
integer prims_pointer=0; //"pointer" to the next entry to be used in the gXyTextPrims list. |
|
list temp_bank=[]; |
integer temp_bank_stride=2; |
|
// moving this before the prim scan so that the slaves properly configure themseves before |
// any requests to display |
llMessageLinked(LINK_THIS, SLAVE_RESET, "" , null_key); |
|
gXyTextPrims=[]; |
integer x=0; |
for (;x<64;++x) |
{ |
gXyTextPrims= (gXyTextPrims = []) + gXyTextPrims + [0]; //we need to pad out the list to make it easier to add things in any order later |
} |
|
gBankingData = []; |
@loop; |
{ |
//loop over all prims, looking for ones in the current bank |
for(x=0;x<=num_prims;++x) |
{ |
link_name=llGetLinkName(x); |
list tmp = llParseString2List(link_name, ["-"], []); |
if(llList2String(tmp,0)== "xyzzytext") |
{ |
integer prims_bank=llList2Integer(tmp,1); |
if (llList2Integer(tmp,1)==bank) |
{ |
temp_bank+=llList2Integer(tmp,2) + (list)x; |
} |
} |
|
} |
|
|
if (temp_bank!=[]) |
{ |
//sort the current bank |
temp_bank=llListSort(temp_bank, temp_bank_stride, TRUE); |
|
integer temp_len=llGetListLength(temp_bank); |
|
//store metadata |
gBankingData+=[prims_pointer,temp_len/temp_bank_stride,0]; |
|
//repack the bank into the prim list |
for (x=0; x < temp_len; x+=temp_bank_stride) |
{ |
gXyTextPrims = pack_and_insert(gXyTextPrims, prims_pointer, llList2Integer(temp_bank, x+1)); |
++prims_pointer; |
} |
++bank; |
temp_bank=[]; |
jump loop; |
} |
} |
} |
|
default { |
state_entry() { |
// Initialize the character index. |
ResetCharIndex(); |
init(); |
} |
|
on_rez(integer num) |
{ |
llResetScript(); |
} |
|
link_message(integer sender, integer channel, string data, key id) { |
if(id == null_key) |
id="0"; |
|
if (channel == DISPLAY_STRING) { |
PassToRender(1,data, (integer)((string)id)); |
return; |
} |
else if (channel == DISPLAY_EXTENDED) { |
PassToRender(2,data, (integer)((string)id)); |
return; |
} |
else if (channel == REMAP_INDICES) { |
// Parse the message, splitting it up into index values. |
list Parsed = llCSV2List(data); |
integer i; |
// Go through the list and swap each pair of indices. |
for (i = 0; i < llGetListLength(Parsed); i += 2) { |
integer Index1 = ConvertIndex( llList2Integer(Parsed, i) ); |
integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) ); |
|
// Swap these index values. |
string Value1 = llGetSubString(gCharIndex, Index1, Index1); |
string Value2 = llGetSubString(gCharIndex, Index2, Index2); |
|
gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1); |
gCharIndex = llInsertString(gCharIndex, Index1, Value2); |
|
gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2); |
gCharIndex = llInsertString(gCharIndex, Index2, Value1); |
} |
return; |
} |
else if (channel == RESET_INDICES) { |
// Restore the character index back to default settings. |
ResetCharIndex(); |
return; |
} |
else if (channel == RESCAN_LINKSET) |
{ |
init(); |
} |
else if (channel == SET_COLOR) { |
change_color((vector)data); |
} |
else if (channel == SET_LINE_COLOR) { |
change_line_color((integer)((string)id), (vector)data); |
} |
else if (channel == REGISTER_SLAVE) |
{ |
if(!~llListFindList(gSlaveNames, (list)data)) |
{//isn't registered yet |
gSlaveNames += data; |
gSlaveRegistered=TRUE; |
//llOwnerSay((string)llGetListLength(gSlaveNames) + " Slave(s) Recognized: " + data); |
} |
// else |
// {//it already exists |
// llOwnerSay((string)llGetListLength(gSlaveNames) + " Slave, Existing Slave Recognized: " + data); |
// } |
llMessageLinked(LINK_THIS, SLAVE_RECOGNIZED, data , null_key); |
} |
} |
|
changed(integer change) |
{ |
if(change&CHANGED_INVENTORY) |
{ |
if(gSlaveRegistered) |
{ |
//by using negative indexes they don't need to be adjusted when an entry is deleted. |
integer x = ~llGetListLength(gSlaveNames); |
while(++x) |
{ |
if (!~llGetInventoryType(llList2String(gSlaveNames, x))) |
{ |
//llOwnerSay("Slave Removed: " + llList2String(gSlaveNames, x)); |
gSlaveNames = llDeleteSubList(gSlaveNames, x, x); |
} |
} |
gSlaveRegistered = !(gSlaveNames == []); |
} |
} |
} |
} |
/visitor-track-and-record/visitor-track-and-record.lsl |
@@ -0,0 +1,729 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This project makes Corrade, the Second Life / OpenSim bot track all the |
// visitors on a region and record information to the group database. More |
// information on the Corrade Second Life / OpenSim scripted agent can be |
// found at the URL: http://grimore.org/secondlife/scripted_agents/corrade |
// |
// The script works in combination with a "configuration" notecard that |
// must be placed in the same primitive as this script. The purpose of this |
// script is to illustrate the Corrade built-in group database features |
// and you are free to use, change, and commercialize it under the terms |
// of the GNU/GPLv3 license at: http://www.gnu.org/licenses/gpl.html |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(a, [ k ]); |
if(i != -1) return llList2String(a, i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasCSVToList(string csv) { |
list l = []; |
list s = []; |
string m = ""; |
do { |
string a = llGetSubString(csv, 0, 0); |
csv = llDeleteSubString(csv, 0, 0); |
if(a == ",") { |
if(llList2String(s, -1) != "\"") { |
l += m; |
m = ""; |
jump continue; |
} |
m += a; |
jump continue; |
} |
if(a == "\"" && llGetSubString(csv, 0, 0) == a) { |
m += a; |
csv = llDeleteSubString(csv, 0, 0); |
jump continue; |
} |
if(a == "\"") { |
if(llList2String(s, -1) != a) { |
s += a; |
jump continue; |
} |
s = llDeleteSubList(s, -1, -1); |
jump continue; |
} |
m += a; |
@continue; |
} while(csv != ""); |
// postcondition: length(s) = 0 |
return l + m; |
} |
|
// corrade data |
key CORRADE = NULL_KEY; |
string GROUP = ""; |
string PASSWORD = ""; |
|
// for holding the callback URL |
string callback = ""; |
|
// for notecard reading |
integer line = 0; |
|
// key-value data will be read into this list |
list tuples = []; |
|
|
default { |
state_entry() { |
if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find a configuration inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading configuration file..."); |
llGetNotecardLine("configuration", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// invariant, length(tuples) % 2 == 0 |
if(llGetListLength(tuples) % 2 != 0) { |
llOwnerSay("Error in configuration notecard."); |
return; |
} |
CORRADE = llList2Key( |
tuples, |
llListFindList( |
tuples, |
[ |
"corrade" |
] |
) |
+1 |
); |
if(CORRADE == NULL_KEY) { |
llOwnerSay("Error in configuration notecard: corrade"); |
return; |
} |
GROUP = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"group" |
] |
) |
+1 |
); |
if(GROUP == "") { |
llOwnerSay("Error in configuration notecard: group"); |
return; |
} |
PASSWORD = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"password" |
] |
) |
+1 |
); |
if(PASSWORD == "") { |
llOwnerSay("Error in configuration notecard: password"); |
return; |
} |
// DEBUG |
llOwnerSay("Read configuration notecard..."); |
tuples = []; |
state url; |
} |
if(data == "") jump continue; |
integer i = llSubStringIndex(data, "#"); |
if(i != -1) data = llDeleteSubString(data, i, -1); |
list o = llParseString2List(data, ["="], []); |
// get rid of starting and ending quotes |
string k = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
0 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
string v = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
1 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
if(k == "" || v == "") jump continue; |
tuples += k; |
tuples += v; |
@continue; |
llGetNotecardLine("configuration", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state detect { |
state_entry() { |
// DEBUG |
llOwnerSay("Detecting if Corrade is online..."); |
llSetTimerEvent(5); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llSetTimerEvent(30); |
return; |
} |
state initialize; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state initialize { |
state_entry() { |
// DEBUG |
llOwnerSay("Creating the database if it does not exist..."); |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "database", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"SQL", wasURLEscape( |
"CREATE TABLE IF NOT EXISTS visitors ( |
'firstname' TEXT NOT NULL, |
'lastname' TEXT NOT NULL, |
'lastseen' TEXT NOT NULL, |
'time' INTEGER NOT NULL, |
'memory' INTEGER NOT NULL, |
PRIMARY KEY ('firstname', 'lastname') |
)" |
), |
"callback", wasURLEscape(callback) |
] |
) |
); |
// alarm 60 |
llSetTimerEvent(60); |
} |
timer() { |
// DEBUG |
llOwnerSay("Timeout creating table..."); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "database") return; |
if(wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to create the table: " + |
wasURLUnescape( |
wasKeyValueGet( |
"error", |
body |
) |
) |
); |
return; |
} |
// DEBUG |
llOwnerSay("Table created..."); |
state show; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state show { |
state_entry() { |
// DEBUG |
llOwnerSay("Updating display with the number of recorded visitors..."); |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "database", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"SQL", wasURLEscape( |
"SELECT |
COUNT(*) AS 'Visits', |
AVG(time) AS 'Time', |
AVG(memory) AS 'Memory' |
FROM visitors" |
), |
"callback", wasURLEscape(callback) |
] |
) |
); |
// alarm 60 |
llSetTimerEvent(60); |
} |
timer() { |
// DEBUG |
llOwnerSay("Timeout reading rows from visitors table..."); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "database") return; |
if(wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to enumerate visitors: " + |
wasURLUnescape( |
wasKeyValueGet( |
"error", |
body |
) |
) |
); |
llResetScript(); |
} |
list data = wasCSVToList( |
wasURLUnescape( |
wasKeyValueGet( |
"data", |
body |
) |
) |
); |
integer visits = llList2Integer( |
data, |
llListFindList( |
data, |
(list)"Visits" |
) + 1 |
); |
integer time = llList2Integer( |
data, |
llListFindList( |
data, |
(list)"Time" |
) + 1 |
); |
integer memory = llList2Integer( |
data, |
llListFindList( |
data, |
(list)"Memory" |
) + 1 |
); |
llMessageLinked(LINK_ROOT, 204000, "V:" + (string)visits, "0"); |
llMessageLinked(LINK_ROOT, 204000, "T:" + (string)time + "m", "1"); |
llMessageLinked(LINK_ROOT, 204000, "M:" + (string)memory + "k", "2"); |
state scan; |
} |
link_message(integer sender_num, integer num, string str, key id) { |
if(str == "reset") |
state reset; |
if(str == "display") { |
line = 0; |
state display; |
} |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state scan { |
state_entry() { |
// DEBUG |
llOwnerSay("Scanning for visitors..."); |
// Scan for visitors every 60 seconds. |
llSetTimerEvent(60); |
} |
timer() { |
// Check if Corrade is online. |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
// Get agents |
list as = llGetAgentList(AGENT_LIST_REGION, []); |
do { |
key a = llList2Key(as, 0); |
as = llDeleteSubList(as, 0, 0); |
list name = llParseString2List(llKey2Name(a), [" "], []); |
if(llGetListLength(name) != 2) return; |
string fn = llList2String(name, 0); |
string ln = llList2String(name, 1); |
// The command sent to Corrade responsible for adding a visitor |
// or updating the data for the visitor in case the visitor is |
// already entered into the visitors table. This is performed |
// with an INSER OR REPLACE sqlite command given the first name |
// and the last name of the avatar are unique primary keys. |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "database", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"SQL", wasURLEscape( |
"INSERT OR REPLACE INTO visitors ( |
firstname, |
lastname, |
lastseen, |
time, |
memory |
) VALUES( |
'" + fn + "', '" + ln + "', '" + llGetTimestamp() + "', |
COALESCE( |
( |
SELECT time FROM visitors WHERE |
firstname='" + fn + "' AND lastname='" + ln + "' |
) + 1 |
, |
1 |
), " + |
(string)( |
(integer)( |
llList2Float( |
llGetObjectDetails( |
a, |
[OBJECT_SCRIPT_MEMORY] |
), |
0 |
) |
/ |
1024 /*in kib, to mib 1048576*/ |
) |
|
) + |
")" |
) |
] |
) |
); |
} while(llGetListLength(as)); |
state show; |
} |
link_message(integer sender_num, integer num, string str, key id) { |
if(str == "reset") |
state reset; |
if(str == "display") { |
line = 0; |
state display; |
} |
} |
dataserver(key id, string data) { |
if(data == "1") return; |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
// Switch to detect loop and wait there for Corrade to come online. |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state display_trampoline { |
state_entry() { |
++line; |
state display; |
} |
link_message(integer sender_num, integer num, string str, key id) { |
if(str == "reset") |
state reset; |
} |
} |
|
state display { |
state_entry() { |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "database", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"SQL", wasURLEscape( |
"SELECT * FROM visitors |
ORDER BY lastseen DESC |
LIMIT 1 |
OFFSET " + (string)line |
), |
"callback", wasURLEscape(callback) |
] |
) |
); |
// alarm 60 |
llSetTimerEvent(60); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "database") return; |
if(wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to query the table: " + |
wasURLUnescape( |
wasKeyValueGet( |
"error", |
body |
) |
) |
); |
return; |
} |
// Grab the data key if it exists. |
string dataKey = wasURLUnescape( |
wasKeyValueGet( |
"data", |
body |
) |
); |
|
// We got no more rows, so switch back to scanning. |
if(dataKey == "") |
state scan; |
|
list data = wasCSVToList(dataKey); |
|
string firstname = llList2String( |
data, |
llListFindList( |
data, |
(list)"firstname" |
) + 1 |
); |
string lastname = llList2String( |
data, |
llListFindList( |
data, |
(list)"lastname" |
) + 1 |
); |
string lastseen = llList2String( |
data, |
llListFindList( |
data, |
(list)"lastseen" |
) + 1 |
); |
|
llOwnerSay(firstname + " " + lastname + " @ " + lastseen); |
state display_trampoline; |
} |
link_message(integer sender_num, integer num, string str, key id) { |
if(str == "reset") |
state reset; |
} |
timer() { |
// DEBUG |
llOwnerSay("Timeout reading rows from visitors table..."); |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state reset { |
state_entry() { |
// DEBUG |
llOwnerSay("Resetting all visitors..."); |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "database", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"SQL", "DROP TABLE visitors", |
"callback", wasURLEscape(callback) |
] |
) |
); |
// alarm 60 |
llSetTimerEvent(60); |
} |
timer() { |
// DEBUG |
llOwnerSay("Timeout deleting database..."); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "database") return; |
if(wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to drop the visitors table: " + |
wasURLUnescape( |
wasKeyValueGet( |
"error", |
body |
) |
) |
); |
llResetScript(); |
} |
// DEBUG |
llOwnerSay("Table dropped..."); |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |