Difference between revisions of "XyzzyText"

From Second Life Wiki
Jump to navigation Jump to search
m (making the code faster and smarter)
Line 75: Line 75:
// XyzzyText v2.0 Script (5 Face, Single Texture)  
// XyzzyText v2.0 Script (5 Face, Single Texture)  
//
//
// Heavily Modified by Thraxis Epsilon and Gigs Taggart 5/2007
// 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
// Rewrite to allow one-script-per-object operation w/ optional slaves
// Enable prim-label functionality
// Enable prim-label functionality
Line 124: Line 124:


// Used to hide the text after a fade-out.  
// Used to hide the text after a fade-out.  
key    TRANSPARENT    = "701917a8-d614-471f-13dd-5f4644e36e3c";  
key    TRANSPARENT    = "701917a8-d614-471f-13dd-5f4644e36e3c";
key    null_key        = NULL_KEY;
///////////// END CONSTANTS ////////////////  
///////////// END CONSTANTS ////////////////  


Line 206: Line 207:
   // Use these grid positions to display the correct textures/offsets.  
   // Use these grid positions to display the correct textures/offsets.  
   ShowChars(link,GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.  
   ShowChars(link,GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.  
     float Alpha;  
     float Alpha = 0.10;  
     for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)  
     for (; Alpha <= 1.0; Alpha += 0.05)  
       llSetLinkAlpha(link,Alpha, ALL_SIDES);  
       llSetLinkAlpha(link,Alpha, ALL_SIDES);  
           // See if we want to fade out as well.  
           // See if we want to fade out as well.  
Line 225: Line 226:
   // Look for escape sequences.  
   // Look for escape sequences.  
   integer length = 0;
   integer length = 0;
   list Parsed      = llParseString2List(str, [], [ESCAPE_SEQUENCE]);  
   list Parsed      = llParseString2List(str, [], (list)ESCAPE_SEQUENCE);  
   integer ParsedLen = llGetListLength(Parsed);  
   integer ParsedLen = llGetListLength(Parsed);  


Line 233: Line 234:
   integer IndicesLeft = 5;  
   integer IndicesLeft = 5;  


  integer i;
   string Token;  
   string Token;  
   integer Clipped;  
   integer Clipped;  
   integer LastWasEscapeSequence = FALSE;  
   integer LastWasEscapeSequence = FALSE;  
   // Work from left to right.  
   // Work from left to right.  
   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {  
   integer i = 0;
  for (; i < ParsedLen && IndicesLeft > 0; ++i) {  
       Token = llList2String(Parsed, i);  
       Token = llList2String(Parsed, i);  


Line 251: Line 252:
           // Clip if necessary.  
           // Clip if necessary.  
           if (TokenLength > IndicesLeft) {  
           if (TokenLength > IndicesLeft) {  
               Token = llGetSubString(Token, 0, IndicesLeft - 1);
               TokenLength = llStringLength(Token = llGetSubString(Token, 0, IndicesLeft - 1));  
              TokenLength = llStringLength(Token);  
               IndicesLeft = 0;  
               IndicesLeft = 0;  
               Clipped = TRUE;  
               Clipped = TRUE;  
Line 262: Line 262:
           if (LastWasEscapeSequence) {  
           if (LastWasEscapeSequence) {  
               // Yes, the first character is an escape character, the rest are normal.  
               // Yes, the first character is an escape character, the rest are normal.  
               length += 3;
               length += 2 + TokenLength;
              // This is the extended character.
              if(render)             
                    Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
 
              // These are the normal characters.
              length+=TokenLength - 1;  
               if(render)
               if(render)
               {
               {
                  integer j;  
                    // This is the extended character.
                  for (j = 1; j < TokenLength; j++)  
                    Indices += (llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95);
                  {
                   
                       Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];  
                    // These are the normal characters.
                    integer j = 1;  
                    for (; j < TokenLength; ++j)  
                    {
                       Indices += llSubStringIndex(gCharIndex, llGetSubString(Token, j, j));  
                     }
                     }
                 }
                 }
Line 280: Line 278:
           else { // Normal string.  
           else { // Normal string.  
               // Just add the characters normally.  
               // Just add the characters normally.  
               length+=TokenLength;
               length += TokenLength;
               if(render)
               if(render)
               {               
               {               
                   integer j;  
                   integer j = 0;  
                   for (j = 0; j < TokenLength; j++)  
                   for (; j < TokenLength; ++j)  
                   {                   
                   {                   
                       Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
                       Indices += llSubStringIndex(gCharIndex, llGetSubString(Token, j, j));
                   }
                   }
                 }
                 }
Line 330: Line 328:
PassToRender(integer render,string message, integer bank)
PassToRender(integer render,string message, integer bank)
{
{
    float time;
//    float time;
     integer extendedlen = 0;
     integer extendedlen = 0;
     integer link;
     integer link;
    integer x;
     integer i = 0;
     integer i = 0;
     integer msgLen = llStringLength(message);
     integer msgLen = llStringLength(message);
Line 346: Line 343:
     integer bank_highest_dirty=llList2Integer(gBankingData, (bank * BANK_STRIDE) + 2);
     integer bank_highest_dirty=llList2Integer(gBankingData, (bank * BANK_STRIDE) + 2);
      
      
     for (x=0;x < msgLen;x = x + 5)
     integer x = 0;
    for (;x < msgLen;x = x + 5)
     {
     {


Line 352: Line 350:
         {
         {
             //set the dirty to max, and bail out, we're done
             //set the dirty to max, and bail out, we're done
             gBankingData=llListReplaceList(gBankingData, [bank_length], (bank * BANK_STRIDE) + 2, (bank * BANK_STRIDE) + 2);
             gBankingData=llListReplaceList(gBankingData, (list)bank_length, (bank * BANK_STRIDE) + 2, (bank * BANK_STRIDE) + 2);
             return;
             return;
         }  
         }
          
          
         link = unpack(gXyTextPrims,(i + bank_offset));
         link = unpack(gXyTextPrims,(i + bank_offset));
         TextToRender = llGetSubString(message, x, x + 15);
         TextToRender = llGetSubString(message, x, x + 15);
          
          
         if(gSlaveRegistered && (link % (num_slaves +1) != 0))
         if(gSlaveRegistered && (link % (num_slaves +1)))
         {
         {
             slave_name=llList2String(gSlaveNames, (link % (num_slaves + 1)) - 1);
             slave_name=llList2String(gSlaveNames, (link % (num_slaves + 1)) - 1);
Line 396: Line 394:
                 RenderWithEffects(link,TextToRender);
                 RenderWithEffects(link,TextToRender);
         }
         }
         i=i+1;             
         ++i;             
     }
     }
      
      
Line 417: Line 415:
             RenderString(link,"    ");
             RenderString(link,"    ");
         }
         }
         i=i+1;         
         ++i;         
     }
     }
     gBankingData=llListReplaceList(gBankingData, [current_highest_dirty], (bank * BANK_STRIDE) + 2, (bank * BANK_STRIDE) + 2);
     gBankingData=llListReplaceList(gBankingData, (list)current_highest_dirty, (bank * BANK_STRIDE) + 2, (bank * BANK_STRIDE) + 2);
}
}


// Bitwise Voodoo by Gigs Taggart
// Bitwise Voodoo by Gigs Taggart and optimized by Strife Onizuka
list gXyTextPrims;
list gXyTextPrims;


Line 429: Line 427:
{//ignores avatars.
{//ignores avatars.
     integer a = llGetNumberOfPrims();
     integer a = llGetNumberOfPrims();
     if(1 < a)
     while(llGetAgentSize(llGetLinkKey(a)))
        while(llGetAgentSize(llGetLinkKey(a)))
        --a;
            --a;
     return a;
     return a;
}
}


//functions to pack 8-bit shorts into ints
//functions to pack 8-bit shorts into ints
list pack_and_insert(list in_list, integer pos, integer value)
list pack_and_insert(list in_list, integer pos, integer value)
{
{
    //figure out the bitpack position
//    //figure out the bitpack position
    integer pack=pos%4; //4 bytes per int
//    integer pack = pos & 3; //4 bytes per int
    pos=pos/4;
//    pos=pos >> 2;
    integer shifted=value << (pack * 8);
//    integer shifted = value << (pack << 3);
    integer old_value=llList2Integer(in_list, pos);
//    integer old_value = llList2Integer(in_list, pos);
    shifted=old_value | shifted;
//    shifted = old_value | shifted;
    in_list = llListReplaceList(in_list, [ shifted ], pos, pos);
//    in_list = llListReplaceList(in_list, (list)shifted, pos, pos);
     return in_list;
//    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)
integer unpack(list in_list, integer pos)
{
{
     integer pack=pos%4;
     return (llList2Integer(in_list, pos >> 2) >> ((pos & 3) << 3)) & 0x000000FF;//unsigned
    pos=pos/4;
//    return (llList2Integer(in_list, pos >> 2) << (((~pos) & 3) << 3)) >> 24;//signed
    integer value=llList2Integer(in_list, pos);
    return value >> (pack * 8) & 0x000000FF;
}
}


change_color(vector color)
change_color(vector color)
{
{
     integer num_prims=llGetListLength(gXyTextPrims);
     integer num_prims=llGetListLength(gXyTextPrims) << 2;
    num_prims=num_prims*4;
      
      
     integer i;
     integer i = 0;
      
      
     for (i=0;i<=num_prims;i++)
     for (; i<=num_prims; ++i)
     {
     {
         integer link = unpack(gXyTextPrims,i);
         integer link = unpack(gXyTextPrims,i);
         if (link==0)
         if (!link)
             return;
             return;
           
       
           
         llSetLinkPrimitiveParams( link,[  
         llSetLinkPrimitiveParams( link,[  
        PRIM_COLOR, FACE_1, color, 1.0,
            PRIM_COLOR, FACE_1, color, 1.0,
        PRIM_COLOR, FACE_2, color, 1.0,
            PRIM_COLOR, FACE_2, color, 1.0,
        PRIM_COLOR, FACE_3, color, 1.0,
            PRIM_COLOR, FACE_3, color, 1.0,
        PRIM_COLOR, FACE_4, color, 1.0,
            PRIM_COLOR, FACE_4, color, 1.0,
        PRIM_COLOR, FACE_5, color, 1.0
            PRIM_COLOR, FACE_5, color, 1.0
         ]);  
         ]);
     }
     }
}
}


change_line_color(integer bank, vector color)
change_line_color(integer bank, vector color)
Line 487: Line 480:


     //get the bank offset and length
     //get the bank offset and length
     integer bank_offset=llList2Integer(gBankingData, (bank * BANK_STRIDE));
     integer i = llList2Integer(gBankingData, (bank * BANK_STRIDE));
     integer bank_end= bank_offset + llList2Integer(gBankingData, (bank * BANK_STRIDE) + 1);
     integer bank_end = i + llList2Integer(gBankingData, (bank * BANK_STRIDE) + 1);
       
 
    integer i;
     for (; i < bank_end; ++i)
   
     for (i=bank_offset; i < bank_end; i++)
     {     
     {     
         integer link = unpack(gXyTextPrims,i);
         integer link = unpack(gXyTextPrims,i);
         if (link==0)
         if (!link)
             return;
             return;
           
       
           
         llSetLinkPrimitiveParams( link,[  
         llSetLinkPrimitiveParams( link,[  
        PRIM_COLOR, FACE_1, color, 1.0,
            PRIM_COLOR, FACE_1, color, 1.0,
        PRIM_COLOR, FACE_2, color, 1.0,
            PRIM_COLOR, FACE_2, color, 1.0,
        PRIM_COLOR, FACE_3, color, 1.0,
            PRIM_COLOR, FACE_3, color, 1.0,
        PRIM_COLOR, FACE_4, color, 1.0,
            PRIM_COLOR, FACE_4, color, 1.0,
        PRIM_COLOR, FACE_5, color, 1.0
            PRIM_COLOR, FACE_5, color, 1.0
         ]);  
         ]);
     }
     }
}
}
Line 513: Line 503:
{
{
     integer num_prims=get_number_of_prims();
     integer num_prims=get_number_of_prims();
    integer x;
     string link_name;
     string link_name;
     integer bank=0;
     integer bank=0;
    integer bank_empty=FALSE;
     integer prims_pointer=0; //"pointer" to the next entry to be used in the gXyTextPrims list.
     integer prims_pointer=0; //"pointer" to the next entry to be used in the gXyTextPrims list.
      
      
     list temp_bank;
     list temp_bank = [];
     integer temp_bank_stride=2;
     integer temp_bank_stride=2;
   
      
      
     //FIXME: font texture might should be per-bank
     //FIXME: font texture might should be per-bank
     llMessageLinked(LINK_THIS, SET_FONT_TEXTURE, "" ,gFontTexture);
     llMessageLinked(LINK_THIS, SET_FONT_TEXTURE, "", gFontTexture);
      
      
     gXyTextPrims=[];
     gXyTextPrims=[];
     for (x=0;x<64;x++)
     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
         gXyTextPrims = (gXyTextPrims = []) + gXyTextPrims + 0;  //we need to pad out the list to make it easier to add things in any order later
     }
     }
      
      
      
     @loop;
    while(!bank_empty)
     {
     {
        bank_empty=TRUE;
       
         //loop over all prims, looking for ones in the current bank
         //loop over all prims, looking for ones in the current bank
         for(x=0;x<=num_prims;x++)
         for(x=0;x<=num_prims;++x)
         {
         {
             link_name=llGetLinkName(x);
             link_name=llGetLinkName(x);
             list tmp = llParseString2List(link_name, ["-"], []);
             list tmp = llParseString2List(link_name, (list)"-", []);
             string xyzzytext = llList2String(tmp,0);
             if(llList2String(tmp,0) == "xyzzytext")
            if(xyzzytext == "xyzzytext")
             {
             {
                 integer prims_bank=llList2Integer(tmp,1);
                 if (llList2Integer(tmp,1) == bank)
                if (prims_bank==bank)
                 {
                 {
                     bank_empty=FALSE;
                     temp_bank += llList2Integer(tmp,2) + (list)x;
                    integer pos=llList2Integer(tmp,2);
                    temp_bank+=[pos];
                    temp_bank+=[x];
                 }
                 }
             }
             }
Line 558: Line 537:
         }
         }


         if (!bank_empty)
         if (temp_bank != [])
         {
         {
             //sort the current bank
             //sort the current bank
             temp_bank=llListSort(temp_bank, temp_bank_stride, TRUE);
             temp_bank = llListSort(temp_bank, temp_bank_stride, TRUE);
              
              
             integer y;
             integer y;
             integer temp_len=llGetListLength(temp_bank);
             integer temp_len = llGetListLength(temp_bank);
              
              
             //store metadata
             //store metadata
             gBankingData+=[prims_pointer];
             gBankingData += [prims_pointer, temp_len/temp_bank_stride, 0];
            gBankingData+=[temp_len/temp_bank_stride];
            gBankingData+=[0];
              
              
             //repack the bank into the prim list
             //repack the bank into the prim list
Line 575: Line 552:
             {
             {
                 gXyTextPrims = pack_and_insert(gXyTextPrims, prims_pointer, llList2Integer(temp_bank, y+1));
                 gXyTextPrims = pack_and_insert(gXyTextPrims, prims_pointer, llList2Integer(temp_bank, y+1));
                 prims_pointer++;
                 ++prims_pointer;
             }
             }
            ++bank;
            temp_bank=[];
            jump loop;
         }
         }
       
        temp_bank=[];       
        bank++;
     }
     }
      
      
     llMessageLinked(LINK_THIS, SLAVE_RESET, "" , NULL_KEY);
     llMessageLinked(LINK_THIS, SLAVE_RESET, "" , null_key);
     //llOwnerSay((string)llGetFreeMemory());
     //llOwnerSay((string)llGetFreeMemory());
}
}
Line 601: Line 578:
    
    
   link_message(integer sender, integer channel, string data, key id) {  
   link_message(integer sender, integer channel, string data, key id) {  
         if (id==NULL_KEY)
         if (id==null_key)
             id="0";
             id="0";
              
              
         if (channel == DISPLAY_STRING) {  
         if (channel == DISPLAY_STRING) {  
          PassToRender(1,data, (integer)((string)id));
            PassToRender(1,data, (integer)((string)id));  
          return;  
         }  
         }  
         if (channel == DISPLAY_EXTENDED) {  
         else if (channel == DISPLAY_EXTENDED) {  
          PassToRender(2,data, (integer)((string)id));
            PassToRender(2,data, (integer)((string)id));  
          return;  
         }  
         }  
         if (channel == REMAP_INDICES) {  
         else if (channel == REMAP_INDICES) {  
          // Parse the message, splitting it up into index values.  
            // Parse the message, splitting it up into index values.  
          list Parsed = llCSV2List(data);  
            list Parsed = llCSV2List(data);  
          integer i;  
            integer i;  
          // Go through the list and swap each pair of indices.  
            // Go through the list and swap each pair of indices.  
          for (i = 0; i < llGetListLength(Parsed); i += 2) {  
            for (i = 0; i < llGetListLength(Parsed); i += 2) {  
              integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );  
                integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );  
              integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );  
                integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );  
          
          
              // Swap these index values.  
                // Swap these index values.  
              string Value1 = llGetSubString(gCharIndex, Index1, Index1);  
                string Value1 = llGetSubString(gCharIndex, Index1, Index1);  
              string Value2 = llGetSubString(gCharIndex, Index2, Index2);  
                string Value2 = llGetSubString(gCharIndex, Index2, Index2);  
          
          
              gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);  
                gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);  
              gCharIndex = llInsertString(gCharIndex, Index1, Value2);  
                gCharIndex = llInsertString(gCharIndex, Index1, Value2);
          
          
              gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);  
                gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
              gCharIndex = llInsertString(gCharIndex, Index2, Value1);  
                gCharIndex = llInsertString(gCharIndex, Index2, Value1);
          }  
            }  
          return;
         }  
         }  
          
         else if (channel == RESCAN_LINKSET)
        if (channel == RESCAN_LINKSET)
         {
         {
             init();
             init();
         }
         }
          
         else if (channel == RESET_INDICES) {  
        if (channel == RESET_INDICES) {  
            // Restore the character index back to default settings.  
          // Restore the character index back to default settings.  
            ResetCharIndex();  
          ResetCharIndex();
          return;  
         }  
         }  
         if (channel == SET_FADE_OPTIONS) {  
         else if (channel == SET_FADE_OPTIONS) {  
          // Change the channel we listen to for cell commands, the  
            // Change the channel we listen to for cell commands, the  
          // starting character position to extract from, and  
            // starting character position to extract from, and  
          // special effect attributes.  
            // special effect attributes.  
          list Parsed = llCSV2List(data);  
            list Parsed = llCSV2List(data);  
          gCellUseFading      = (integer) llList2String(Parsed, 0);  
            gCellUseFading      = (integer) llList2String(Parsed, 0);  
          gCellHoldDelay      = (float)  llList2String(Parsed, 1);
            gCellHoldDelay      = (float)  llList2String(Parsed, 1);
          return;  
         }  
         }  
         if (channel == SET_FONT_TEXTURE) {  
         else if (channel == SET_FONT_TEXTURE) {  
          // Use the new texture instead of the current one.  
            // Use the new texture instead of the current one.  
          gFontTexture = id;
            gFontTexture = id;  
          return;  
         }  
         }  
         if (channel == SET_COLOR) {  
         else if (channel == SET_COLOR) {
          vector newColor = (vector)data;
            change_color((vector)data);  
          change_color(newColor);  
         }
         }
          
         else if (channel == SET_LINE_COLOR) {
        if (channel == SET_LINE_COLOR) {
             change_line_color((integer)((string)id), (vector)data);  
            vector newColor = (vector)data;       
             change_line_color((integer)((string)id), newColor);  
         }  
         }  
     
         else if (channel == REGISTER_SLAVE)
         if (channel == REGISTER_SLAVE)
         {
         {
             if(~llListFindList(gSlaveNames, [data]))
             if(!~llListFindList(gSlaveNames, (list)data))
             {//it already exists
             {//isn't registered yet
                 llMessageLinked(LINK_THIS, SLAVE_RECOGNIZED, data , NULL_KEY);
                 gSlaveNames += data;
                 //llOwnerSay((string)llGetListLength(gSlaveNames) + " Slave, Existing Slave Recognized: " + data);
                gSlaveRegistered=TRUE;
                return;
                 //llOwnerSay((string)llGetListLength(gSlaveNames) + " Slave(s) Recognized: " + data);
             }
             }
           
//            else
            gSlaveNames+=[data];
//            {//it already exists
            llMessageLinked(LINK_THIS, SLAVE_RECOGNIZED, data , NULL_KEY);
//               llOwnerSay((string)llGetListLength(gSlaveNames) + " Slave, Existing Slave Recognized: " + data);
            gSlaveRegistered=TRUE;
//            }
            //llOwnerSay((string)llGetListLength(gSlaveNames) + " Slave(s) Recognized: " + data);
            llMessageLinked(LINK_THIS, SLAVE_RECOGNIZED, data , null_key);
         }
         }
  }  
    }  
    
    
    
    
Line 689: Line 654:
         if(change&CHANGED_INVENTORY)
         if(change&CHANGED_INVENTORY)
         {
         {
             if(!gSlaveRegistered)         
             if(gSlaveRegistered)         
                return;
       
         
            integer num_slaves=llGetListLength(gSlaveNames);
 
            integer x;
            for (x=0;x<num_slaves;x++)
             {
             {
                 if ((llGetInventoryType(llList2String(gSlaveNames, x)) == -1)&&(x<llGetListLength(gSlaveNames)))
                 //by using negative indexes they don't need to be adjusted when an entry is deleted.
                integer x = -llGetListLength(gSlaveNames);
                for(;x;++x)
                 {
                 {
                     //llOwnerSay("Slave Removed: " + llList2String(gSlaveNames, x));
                     if (!~llGetInventoryType(llList2String(gSlaveNames, x)))
                    gSlaveNames = llDeleteSubList(gSlaveNames, x, x);
                    {
                     x--;//our indices shifted
                        //llOwnerSay("Slave Removed: " + llList2String(gSlaveNames, x));
                        gSlaveNames = llDeleteSubList(gSlaveNames, x, x);
                     }
                 }
                 }
                gSlaveRegistered = !(gSlaveNames == []);
             }
             }
         }
         }
Line 820: Line 783:
   integer IndicesLeft = 5;  
   integer IndicesLeft = 5;  


   integer i;  
   integer i = 0;  
   string Token;  
   string Token;  
   integer Clipped;  
   integer Clipped;  
   integer LastWasEscapeSequence = FALSE;  
   integer LastWasEscapeSequence = FALSE;  
   // Work from left to right.  
   // Work from left to right.  
   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {  
   for (; i < ParsedLen && IndicesLeft > 0; ++i) {  
       Token = llList2String(Parsed, i);  
       Token = llList2String(Parsed, i);  


Line 851: Line 814:


               // This is the extended character.  
               // This is the extended character.  
               Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];  
               Indices += (llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95);  


               // These are the normal characters.  
               // These are the normal characters.  
               integer j;  
               integer j = 1;  
               for (j = 1; j < TokenLength; j++)  
               for (; j < TokenLength; ++j)  
                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];  
                   Indices += llSubStringIndex(gCharIndex, llGetSubString(Token, j, j));  
           }  
           }  
           else { // Normal string.  
           else { // Normal string.  
               // Just add the characters normally.  
               // Just add the characters normally.  
               integer j;  
               integer j;  
               for (j = 0; j < TokenLength; j++)  
               for (j = 0; j < TokenLength; ++j)  
                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];  
                   Indices += llSubStringIndex(gCharIndex, llGetSubString(Token, j, j));  
           }  
           }  



Revision as of 08:09, 28 August 2007

XyzzyText is a new version of Xytext with some advanced features:

  • Efficiency! You can now control an entire linkset with just one script.
  • Flexible Speed vs Lag. Just drop in plug-and-play slave scripts to increase rendering speed. (More than 4-5 slaves not recommended, there is diminishing returns)
  • Ease of use. There is no need to know link numbers. Simply name the child prims xyzzytext-banknumber-cellnumber.
  • To use, pass the bank number to use in the "key" field of the link message of DISPLAY_STRING/EXTENDED.
  • Gigs Taggart hereby releases his contributions to this under the MIT license.

Example:

Display Prims named: 
xyzzytext-0-0 xyzzytext-0-1
xyzzytext-1-0 xyzzytext-1-1

Quickstart:

* Store all scripts in your inventory
* Rez a box
* Drop the prim setup script in it (You now have one unit, name it xyzzytext-0-0)
* Make a row of four of these prims (xyzzytext-0-0 xyzzytext-0-1,xyzzytext-0-2,xyzzytext-0-3)
* Move a copy of this row a little down, and rename the new row(xyzzytext-1-0 xyzzytext-1-1,xyzzytext-1-2,xyzzytext-1-3)
* Do this for every row you make
* Create a big box that serves as the board-background(mainprim)
* Link all prims with the mainprim(selected last), so the mainprim keeps yellow after linking(root-prim)
* Copy the main script and usage script into the root prim
* Type something, and check if it all works
* Flatten the prims, so the text looks straight
* Consider adding slave scripts to the root prim if you have a lot of rows

Usage:

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;


integer gToggle;

default
{
    state_entry()
    {
        llListen(0,"",NULL_KEY,"");
       
    }

    
    listen(integer channel,string name, key id, string message)
    {
        if (gToggle)
            llMessageLinked(LINK_THIS,DISPLAY_STRING,name + ":"+ message,"0");
        else
            llMessageLinked(LINK_THIS,DISPLAY_STRING,name + ":"+ message,"1");
        
        gToggle=!gToggle;

    }
}

Main Script:

//////////////////////////////////////////// 
// XyzzyText v2.1 Script (Set Line Color) by Huney Jewell
// XyzzyText v2.0 Script (5 Face, Single Texture) 
//
// 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
//
// 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  = "12345"; 

// 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;
///////////// END CONSTANTS //////////////// 

///////////// GLOBAL VARIABLES /////////////// 
// This is the key of the font we are displaying. 
key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e"; 
// All displayable characters.  Default to ASCII order. 
string gCharIndex; 

// 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[\\]^_`"; 
   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~"; 
   gCharIndex += "\n\n\n\n\n"; 
} 

vector GetGridOffset(integer index) { 
   // Calculate the offset needed to display this character. 
   integer Row = index / 10; 
   integer Col = index % 10; 

   // Return the offset in the texture. 
   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>; 
} 

ShowChars(integer link,vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) { 
   // Set the primitive textures directly. 
    
   // <-0.256, 0, 0> 
   // <0, 0, 0> 
   // <0.130, 0, 0> 
   // <0, 0, 0> 
   // <-0.74, 0, 0> 
    
   llSetLinkPrimitiveParams( link,[ 
        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.126, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0, 
        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0, 
        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0, 
        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0, 
        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.126, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0 
        ]); 
} 

RenderString(integer link, string str) { 
   // Get the grid positions for each pair of characters. 
   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) ); 
   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) ); 
   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) ); 
   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) ); 
   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) ); 

   // Use these grid positions to display the correct textures/offsets. 
   ShowChars(link,GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5); 
} 

RenderWithEffects(integer link, string str) { 
   // Get the grid positions for each pair of characters. 
   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) ); 
   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) ); 
   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) ); 
   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) ); 
   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) ); 

   // First set the alpha to the lowest possible. 
   llSetLinkAlpha(link,0.05, ALL_SIDES); 

   // Use these grid positions to display the correct textures/offsets. 
   ShowChars(link,GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength. 
    float Alpha = 0.10; 
    for (; Alpha <= 1.0; Alpha += 0.05) 
       llSetLinkAlpha(link,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) 
       llSetLinkAlpha(link,Alpha, ALL_SIDES); 
          // Make the text transparent to fully hide it. 
   llSetLinkTexture(link,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 5 indices. 
   integer IndicesLeft = 5; 

   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; 
       } 
   } 

   // Use the indices to create grid positions.
  if(render)
  {   
       vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0) ); 
       vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) ); 
       vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) ); 
       vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) ); 
       vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) ); 
    
       // Use these grid positions to display the correct textures/offsets. 
       ShowChars(link,GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
   }
   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 + 5)
    {

        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, (list)bank_length, (bank * BANK_STRIDE) + 2, (bank * BANK_STRIDE) + 2);
            return;
        }
        
        link = unpack(gXyTextPrims,(i + bank_offset));
        TextToRender = llGetSubString(message, x, x + 15);
        
        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)
            {
                //time = llGetAndResetTime();
                if(llSubStringIndex(TextToRender,"\e") > x)
                    extendedlen = 5;
                else
                    extendedlen = RenderExtended(link,TextToRender,0);
                if (extendedlen > 5)
                {
                    x += extendedlen - 5;
                }
               llMessageLinked(LINK_THIS, SLAVE_DISPLAY_EXTENDED, TextToRender, (key)((string)link + "," + slave_name));
              // llOwnerSay((string)llGetAndResetTime());
            }
               //sorry, no fade effect with slave
        }
        else
        {
            if (render == 1)
                RenderString(link,TextToRender);
            if (render == 2)
            {
                extendedlen = RenderExtended(link,TextToRender,1);
                if (extendedlen > 5)
                {
                    x += extendedlen - 5;
                }
                
            }
            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, (list)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;
        
        llSetLinkPrimitiveParams( 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;
        
        llSetLinkPrimitiveParams( 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 prims_pointer=0; //"pointer" to the next entry to be used in the gXyTextPrims list.
    
    list temp_bank = [];
    integer temp_bank_stride=2;
    
    //FIXME: font texture might should be per-bank
    llMessageLinked(LINK_THIS, SET_FONT_TEXTURE, "", gFontTexture);
    
    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
    }
    
    @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, (list)"-", []);
            if(llList2String(tmp,0) == "xyzzytext")
            {
                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 y;
            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 (y=0; y < temp_len; y+=temp_bank_stride)
            {
                gXyTextPrims = pack_and_insert(gXyTextPrims, prims_pointer, llList2Integer(temp_bank, y+1));
                ++prims_pointer;
            }
            ++bank;
            temp_bank=[];
            jump loop;
        }
    }
    
    llMessageLinked(LINK_THIS, SLAVE_RESET, "" , null_key);
    //llOwnerSay((string)llGetFreeMemory());
}


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)); 
        } 
        else if (channel == DISPLAY_EXTENDED) { 
            PassToRender(2,data, (integer)((string)id)); 
        } 
        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);
            } 
        } 
        else if (channel == RESCAN_LINKSET)
        {
            init();
        }
        else if (channel == RESET_INDICES) { 
            // Restore the character index back to default settings. 
            ResetCharIndex(); 
        } 
        else if (channel == SET_FADE_OPTIONS) { 
            // Change the channel we listen to for cell commands, the 
            // starting character position to extract from, and 
            // special effect attributes. 
            list Parsed = llCSV2List(data); 
            gCellUseFading      = (integer) llList2String(Parsed, 0); 
            gCellHoldDelay      = (float)   llList2String(Parsed, 1);
        } 
        else if (channel == SET_FONT_TEXTURE) { 
            // Use the new texture instead of the current one. 
            gFontTexture = id; 
        } 
        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);
                for(;x;++x)
                {
                    if (!~llGetInventoryType(llList2String(gSlaveNames, x)))
                    {
                        //llOwnerSay("Slave Removed: " + llList2String(gSlaveNames, x));
                        gSlaveNames = llDeleteSubList(gSlaveNames, x, x);
                    }
                }
                gSlaveRegistered = !(gSlaveNames == []);
            }
        }
    }
} 


Slave Script:

//////////////////////////////////////////// 
// XyText v2.0 SLAVE Script (5 Face, Single Texture) 
//
// Modified by Thraxis Epsilon and Gigs Taggart 5/2007
// Rewrite to allow one-script-per-object operation
//
// Modified by Kermitt Quirk 19/01/2006 
// To add support for 5 face prim instead of 3 
// 
// Originally Written by Xylor Baysklef 
// 
//
//////////////////////////////////////////// 


integer REMAP_INDICES       = 204002; 
integer RESET_INDICES       = 204003;

//internal API
integer REGISTER_SLAVE      = 205000;
integer SLAVE_RECOGNIZED    = 205001;
integer SLAVE_DISPLAY       = 205003;
integer SET_FONT_TEXTURE    = 204005; 

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  = "12345"; 

// Face numbers. 
integer FACE_1          = 3; 
integer FACE_2          = 7; 
integer FACE_3          = 4; 
integer FACE_4          = 6; 
integer FACE_5          = 1; 


///////////// GLOBAL VARIABLES /////////////// 
// This is the key of the font we are displaying. 
key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e"; 
// All displayable characters.  Default to ASCII order. 
string gCharIndex; 

integer gActive; //if we are recognized, this is true
/////////// END GLOBAL VARIABLES //////////// 

ResetCharIndex() { 
   gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"; 
   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~"; 
   gCharIndex += "\n\n\n\n\n"; 
} 

vector GetGridOffset(integer index) { 
   // Calculate the offset needed to display this character. 
   integer Row = index / 10; 
   integer Col = index % 10; 

   // Return the offset in the texture. 
   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>; 
} 

ShowChars(integer link,vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) { 
   // Set the primitive textures directly. 
    
   // <-0.256, 0, 0> 
   // <0, 0, 0> 
   // <0.130, 0, 0> 
   // <0, 0, 0> 
   // <-0.74, 0, 0> 
    
   llSetLinkPrimitiveParams( link,[ 
        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.126, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0, 
        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0, 
        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0, 
        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0, 
        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.126, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0 
        ]); 
} 

RenderString(integer link, string str) {
   // Get the grid positions for each pair of characters. 
   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) ); 
   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) ); 
   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) ); 
   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) ); 
   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) ); 

   // Use these grid positions to display the correct textures/offsets. 
   ShowChars(link,GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5); 
} 

RenderExtended(integer link, string str) { 
   // Look for escape sequences. 
   list Parsed       = llParseString2List(str, [], [ESCAPE_SEQUENCE]); 
   integer ParsedLen = llGetListLength(Parsed); 

   // Create a list of index values to work with. 
   list Indices; 
   // We start with room for 5 indices. 
   integer IndicesLeft = 5; 

   integer i = 0; 
   string Token; 
   integer Clipped; 
   integer LastWasEscapeSequence = FALSE; 
   // Work from left to right. 
   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) { 
               Token = llGetSubString(Token, 0, IndicesLeft - 1); 
               TokenLength = llStringLength(Token); 
               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. 

               // 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. 
               integer j; 
               for (j = 0; j < TokenLength; ++j) 
                   Indices += llSubStringIndex(gCharIndex, llGetSubString(Token, j, j)); 
           } 

           // Unset this flag, since this was not an escape sequence. 
           LastWasEscapeSequence = FALSE; 
       } 
   } 

   // Use the indices to create grid positions. 
   vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0) ); 
   vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) ); 
   vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) ); 
   vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) ); 
   vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) ); 

   // Use these grid positions to display the correct textures/offsets. 
   ShowChars(link,GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5); 
} 

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; 
} 


default 
{ 
    state_entry() 
    { 
        // Initialize the character index. 
        ResetCharIndex();
        llMessageLinked(LINK_THIS, REGISTER_SLAVE, llGetScriptName() , NULL_KEY);  
    } 
    
    on_rez(integer num)
    {
        llResetScript();       
    }
    
    link_message(integer sender, integer channel, string data, key id) 
    { 
    
        if (channel == SLAVE_RECOGNIZED)
        {
            if (data == llGetScriptName())
            {
                gActive=TRUE;
            }
            return;
        }
    
        if (channel == SLAVE_DISPLAY) 
        { 
            if (!gActive)  
                return;
                
            list params=llCSV2List((string)id);
            if (llList2String(params, 1) != llGetScriptName())
                return;
            
            
            RenderString(llList2Integer(params, 0),data);
            return; 
        } 
       
       if (channel == SLAVE_DISPLAY_EXTENDED) 
       {
            if (!gActive)  
                return;
                
            list params=llCSV2List((string)id);
            if (llList2String(params, 1) != llGetScriptName())
                return;
                
            RenderExtended(llList2Integer(params, 0),data);
       } 
       
        if (channel == SET_FONT_TEXTURE) 
        { 
           gFontTexture = id; 
           return; 
        } 
       
        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; 
        } 
        if (channel == RESET_INDICES) { 
           // Restore the character index back to default settings. 
           ResetCharIndex(); 
           return; 
        } 

        
        if (channel == SLAVE_RESET)
        {
            ResetCharIndex();
            gActive=FALSE;
            llMessageLinked(LINK_THIS, REGISTER_SLAVE, llGetScriptName() , NULL_KEY);
        }
    
    } 
    
   
} 




Prim Setup (Caution! Not compatible with 5 face double texture Xytext):

////////////////////////////////////////////
// XyzzyText Prim Setup Script (5 Face)
//
// Modified by Thraxis Epsilon
//
////////////////////////////////////////////

default
{
    state_entry()
    {

        llSetPrimitiveParams([
                    PRIM_TYPE, PRIM_TYPE_PRISM, PRIM_HOLE_DEFAULT, <0.199, 0.8, 0.0>, 0.68, <0.0, 0.0, 0.0>, <1.0, 1.0, 0.0>, <0.0, 0.0, 0.0>, 
                    PRIM_SIZE, <0.03, 2.89, 0.5>, 
                    PRIM_TEXTURE, 1, "09b04244-9569-d21f-6de0-4bbcf5552222", <0.126, 0.10, 0.0>, <-0.740013, 0.0, 0.0>, 0.0, 
                    PRIM_TEXTURE, 6, "09b04244-9569-d21f-6de0-4bbcf5552222", <0.050, 0.100, 0.00>, <0.0, 0.0, 0.0>, 0.0, 
                    PRIM_TEXTURE, 4, "09b04244-9569-d21f-6de0-4bbcf5552222", <-0.740, 0.10, 0.00>, <0.130009, 0.0, 0.0>, 0.0, 
                    PRIM_TEXTURE, 7, "09b04244-9569-d21f-6de0-4bbcf5552222", <0.050, 0.100, 0.00>, <0.0, 0.0, 0.0>, 0.0, 
                    PRIM_TEXTURE, 3, "09b04244-9569-d21f-6de0-4bbcf5552222", <0.126, 0.10, 0.0>, <-0.255989, 0.0, 0.0>, 0.0]);
                    
        llRemoveInventory(llGetScriptName());
    } 
}