
It scans a crowd, picks an AV at random and then displays their first name in an attractive particle stream. EG: "Joe Smith" will be "HI JOE" in particles
Not only that, it can be set up to display a choice of your own messges. EG: "HAPPY REZ DAY" "PARTICLE CRUCIBLE", "MY STORE NAME"
Wheras other attempts to achieve this in particles have been unreliable, this version corrects a bug and makes it much more attractive and stable.
It is easy to set and and use and is controlled from a blue dialog menu. Brief instructions are given within the script itself, but for full 'Help', including what all the buttons do, visit here: http://wiki.secondlife.com/wiki/Talk:Random_AV_Particle_Name_Generator
The script follows next, but to get a properly formated version, visit here: http://wiki.secondlife.com/wiki/Random_AV_Particle_Name_Generator
NB: for some reason the code below has been posted with a space that should not be there. I can't get rid of it. When you compile in-world you will get a message
"
272, 113) : ERROR : Sytax error"It is simple to fix. Look where the cursor is and you will see "DefaultTex tPalette". Take out the space between the 'x' and the 't' and re-click 'SAVE'
Or pick up a properly formated version of the code from http://wiki.secondlife.com/wiki/Talk:Random_AV_Particle_Name_Generator
Enjoy

CODE
//*********** START OF SCRIPT **********
// ~ PARTICLE TEXT GENERATOR AV SE v1.0 by Debbie Trilling ~
// This script performs two functions:
// 1) scans a crowd, randomly picks an AV and the displays their first name in an
// attractive particle stream. EG: "Joe Smith" will be "HI JOE" in particles
// 2) creates an attractive particle text banner from a selection of your choosing.
// EG: "HAPPY REZ DAY" "PARTICLE CRUCIBLE", "MY STORE NAME"
// Based on the original XYText work of Xylor Baysklef and an in-world script attributed to
// Zara Vale.
// All new functionality and code improvements by Debbie Trilling.
// Script is free to use and modify as you wish but under the condition that the title and
// this introduction remain in place, and that due credit continues to be given to Xylor
// Baysklef, Zara Vale & Debbie Trilling.
// Fonts and their texture UUIDS may be used but only under recognision that they were
// created by Debbie Trilling specifically for the 'Particle Crucible' brand and further, that
// no attempt is made to sell them or use them for any other branding purposes
// BRIEF INSTRUCTIONS:
// Full instructions at
// http://wiki.secondlife.com/wiki/Talk:Random_AV_Particle_Name_Generator
// It is controlled from a dialog menu. Simply put the script in a prim and 'touch'. Make a
// selection from the buttons.
// To change the default text to a selection of your own choice, change the text
// in 'DefaultTextPalette' a few lines below.
// Rather than display the same AV name twice in a row, on the second time it will display
// a random selection from the default text
// The bigger the prim that you put this script into, the further away you will be able to see
// the particle text
// Rotate the prim to have the text travel horizontally rather than vertically
// ** PARAMETERS THAT YOU CAN CHANGE **
// the default text palette, one of which that will be displayed when no agents have been
// detected
list DefaultTextPalette = ["Particle Crucible", "Particle Garden", "SpoLand", "Cartoonimals"];
//************************
// ** DO NOT CHANGE BELOW THIS LINE **
//************************
// particle variables
list WorkCharIndex = [];
list WorkUUIDPalette = [];
float Radius = 0.50;
float FontSize = 1.40;
// Control variables
integer Power = FALSE;
string PowerText = "On";
key ObjectOwner = "";
key LastTarget = "";
string WorkChar = "";
integer NoSensorCounter = 0;
integer ListenChannel = 0;
list MenuItems = [];
string MenuText = "";
// this is the text that will be displayed this iteration
string WorkDisplayText = "";
// controls gap between letters by time in seconds
float LetterTimeGap = 2.7;
// controls whether random AV names are displayed.
integer FindRandomAV = TRUE;
string FindRandomAVText = "Defaults";
// type of scan to be performedl
float ScanArc = 3.141592653;
string ScanArcText = "X-Axis";
// sensor range in meters.
float Range = 25.00;
// list of possible scan range options
list ScannerItems = ["75m", "90m","40m", "50m", "60m", "25m", "30m", "35m", "10m", "15m", "20m" ];
// Characters in 'The Particle Crucible' branded font
// Fonts remade in Photoshop CS3 by Debbie Trilling based on the 'Happy Days' font
list MasterUUIDPalette = [
"342005c0-e916-015a-1d9f-f5a5282658d6", //A
"3ceaeb61-7877-347f-200c-389b008dfba2", //B
"a767bfba-3580-48e5-bf82-843c69996828", //C
"3774a58b-b0a2-68d5-4a3a-ce1f85f719b7", //D
"b214c1c0-774a-becf-8a6f-4a771b3a16fb", //E
"ae3792c0-33e7-0de5-4c98-6e1cfa7a4c4c", //F
"694b92e3-bdec-534a-52c4-b955685ab109", //G
"dfd35eec-7232-26f8-bdd0-ce9a7c142451", //H
"18a70d23-254a-0a3c-4d4b-c6692f5941b3", //I
"b05d6f5f-2b1b-ff7c-2951-ec1f68238c90", //J
"4df1867f-bf31-17d5-3b88-d5703a782c46", //K
"1812a48f-0ee8-de20-b7d8-a33cff922303", //L
"d2bb1c89-5e3c-b084-33f1-a723e5ceb005", //M
"74a39f5d-0aa8-f7c1-e5b2-7ca6470b1877", //N
"340a9361-8f00-5e8c-ef41-89fb25fc26b9", //O
"d8b0f853-ee63-6248-ea70-bd0806fe5259", //P
"6dc7465d-bb3d-c664-98a1-15293d1c1973", //Q
"a45ee3fa-102a-32ce-140a-73c11fd3e873", //R
"378bf10c-86f7-69e5-d6cb-3a27bfa9ed0a", //S
"e8170991-11bd-e2e4-3662-d4b265d785ae", //T
"0541fb80-0055-3815-04d1-fbb2c10e9a59", //U
"45d0a340-32c1-f95a-0a76-7f96c1675d55", //V
"7c8f2c6e-1e44-1af7-4c2b-15c40fa1273d", //W
"0694bea0-21c0-67ec-f420-05e1a5e76e69", //X
"2de3d529-95e8-64a7-45d0-f7e497bf7480", //Y
"558a2f3a-5e76-f0c6-32ad-2db1258250f8", //Z
"75ef778c-cbb2-9a59-8044-49e37753dcfe", //1
"2ed7ef82-2770-b316-b796-84fec6273270", //2
"50fe9926-264a-2017-c7c4-f1ff8a0677eb", //3
"bccfb0e1-1ace-a919-1ca5-ee2387011129", //4
"c38898d2-8b84-eb09-6f63-f88fe8ce62ec", //5
"829e0036-75b0-54a0-2515-6ba1c01b5272", //6
"e84d0ad8-ff28-ba59-4d8e-698f5fc96311", //7
"c8a80085-079e-a00a-6ec4-7c0a82782043", //8
"57df7103-c08f-58b7-5f78-a22d5c200d58", //9
"c259edf8-9875-da57-34a2-d2428e39afb9", //ZERO
"701917a8-d614-471f-13dd-5f4644e36e3c" // Space
];
list MasterCharIndex = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","1","2","3","4","5","6","7","8","9","0"," "];
// product variables
string Author = "Debbie Trilling";
string Supplier = "The Particle Crucible";
string ObjectName = "Particle Text Generator AV SE";
string Version = " v1.0";
// sets the number of consecutive times that the scanner is allowed to operate without
// having located an AV within range
// eg: if RepeatTime = 60.0 seconds and TotalNoScansAllowed = 30, then the toy will
// operate for 1800 seconds (60x30, or 30 minutes) without locating
// anyone before it automatically powers down. Set to '0' to disable the auto-off function
integer TotalNoScansAllowed = 0;
InitialiseObject()
{
Power = FALSE;
PowerText = "On";
ObjectOwner = llGetOwner();
llSetObjectName(ObjectName + Version);
llSetObjectDesc("supplied courtesy of " + Author + "'s " + Supplier);
// set the particle radius to be 0.50m above the prim
vector PrimSize = llGetScale();
Radius = ((PrimSize.z / 2) + (FontSize/2)) + 0.50;
AnnounceWelcome();
StopOperation();
}
AnnounceWelcome()
{
llOwnerSay(
"Thank you for your interest in this " + ObjectName + " "
+ Version + " created by " + Author + " at " + Supplier + ". \nTouch to operate.");
}
MakeMenu()
{
integer CommChannel = (-200000 - ((integer)llFrand(12345) * -1));
ListenChannel = llListen(CommChannel, "", ObjectOwner, "");
llDialog(ObjectOwner, MenuText, MenuItems, CommChannel);
}
StartOperation()
{
WorkChar = "";
NoSensorCounter = 0;
StartSensorCheck();
llOwnerSay( ObjectName + " has been turned ON");
}
StartSensorCheck()
{
// we dont want to use llSensorRepeat, cos it might kick-off a new scan before
// the previous name has been fully displayed
// llSensor lets us control when we want a scan performing, which is not until
// after the previous text string has finished
if (FindRandomAV)
{
llSensor("",NULL_KEY, AGENT, Range, ScanArc);
}
else
{
ApplyDefaultText();
}
}
StartParticle(string ParticleChar)
{
llParticleSystem([
PSYS_PART_FLAGS, 0x100,
PSYS_SRC_PATTERN, 0x04,
PSYS_SRC_TEXTURE, llList2Key(WorkUUIDPalette, llListFindList(WorkCharIndex, (list)ParticleChar)),
PSYS_PART_START_SCALE,<FontSize, FontSize, 0.0>,
PSYS_PART_END_SCALE,<FontSize, FontSize, 0.0>,
PSYS_PART_START_ALPHA,1.0,
PSYS_PART_END_ALPHA,1.0,
PSYS_PART_MAX_AGE, 30.0,
PSYS_SRC_BURST_PART_COUNT, 1,
PSYS_SRC_BURST_RADIUS, Radius,
PSYS_SRC_BURST_RATE, LetterTimeGap,
PSYS_SRC_MAX_AGE, LetterTimeGap,
PSYS_SRC_BURST_SPEED_MIN,0.45,
PSYS_SRC_BURST_SPEED_MAX,0.45
]);
}
PrepareData()
{
// make a list index of unique characters
MakeCharIndex();
// make a list containing only the UUID's of the characters we need for this name
MakeTexturePalette();
// start processing the characters
llSetTimerEvent(LetterTimeGap);
}
MakeCharIndex()
{
// loop through WorkDisplayText letter-by-letter, add each unique character to
// list WorkCharIndex
WorkCharIndex = [];
integer x;
integer y = llStringLength(WorkDisplayText);
for (x = 0; x < y; x++)
{
if (llListFindList(WorkCharIndex, (list)llGetSubString(WorkDisplayText, x, x)) == -1)
{
WorkCharIndex = WorkCharIndex + (list)llGetSubString(WorkDisplayText, x, x);
}
}
}
MakeTexturePalette()
{
// locate the required texture UUID's in MasterUUIDPalette and extract them
// out into list WorkUUIDPalette
WorkUUIDPalette = [];
integer x;
integer y = llGetListLength(WorkCharIndex);
for (x = 0; x < y; x++)
{
WorkUUIDPalette = WorkUUIDPalette + (list)llList2Key(MasterUUIDPalette, llListFindList(MasterCharIndex, (list)llList2String(WorkCharIndex,x)));
}
}
ApplyDefaultText()
{
// select & assign a default text string for display, and add a space at the end,
// turn to upper case
WorkDisplayText = llToUpper(llList2String(DefaultTextPalette, (integer)llFrand((float)llGetListLength(DefaultTextPalette)))) + " ";
// put it all together
PrepareData();
}
SayScanSettings()
{
string MessageLine = "Scans will be performed ";
if (ScanArc == PI)
{
MessageLine = MessageLine + "360 degrees around the prim";
}
else
{
MessageLine = MessageLine + "forward of the prim's local X-Axis";
}
llOwnerSay(MessageLine + " with a range of " + (string)llFloor(Range) + "m.");
}
StopOperation()
{
llSetTimerEvent(0.0);
llSensorRemove();
llListenRemove(ListenChannel);
llParticleSystem([]);
}
default
{
on_rez(integer start_param)
{
// reset on rez
llResetScript();
}
changed( integer change )
{
if(change & CHANGED_OWNER )
{
// reset script on change of Owner
llResetScript();
}
}
state_entry()
{
InitialiseObject();
}
touch_start(integer num_detected)
{
key DetectedUser = llDetectedKey(0);
if (DetectedUser == ObjectOwner)
{
// owner only operation
if(Power)
{
PowerText = "Off";
}
else
{
PowerText = "On";
}
if (FindRandomAV)
{
// Senor mode & 'SetRange' options are only
// avaliable if we scanning for random AV's
MenuItems = [PowerText, FindRandomAVText, ScanArcText, "SetRange"];
}
else
{
MenuItems = [ PowerText, FindRandomAVText ];
}
MenuItems = ["Close", "Help", "ResetScript"] + llListSort(MenuItems,1,TRUE);
MenuText = "MAIN MENU: \n- On-Off: toggle power \n- Defaults-Names: toggle display mode \n- XAxis-360 degree: toggle if scan is forward of the prim, or all around it \n- SetRange: set scanner range \n- ResetScript: restore settings \n- Help: Link to product Help page \n- FreeStuff: got other cool free scripts ";
MakeMenu();
}
else
{
llInstantMessage(DetectedUser, "Thank you for your interest in this " + ObjectName + " "
+ Version + " created by " + Author + " at " + Supplier
+ ". \nYou can get a free copy of this script at http://wiki.secondlife.com/wiki/User:debbie_Trilling");
}
}
sensor(integer total_number)
{
// locate a random AV target
key IntendedTarget = llDetectedKey((integer)llFrand(total_number));
// did we use this AV last time? if so, display a default text string instead
if (IntendedTarget == LastTarget)
{
// we used this AV last time
ApplyDefaultText();
LastTarget = "";
}
else
{
// this is a different AV so populate LastTarget for the
// check next time around
LastTarget = IntendedTarget;
// determine the first name of the random AV, turn to
// upper case, add a space to the end & assign to WorkDisplayText
WorkDisplayText = "HI " + llToUpper(llGetSubString(llKey2Name(IntendedTarget), 0, (llSubStringIndex(llKey2Name(IntendedTarget), " ") -1))) + " ";
// put it all together
PrepareData();
}
}
no_sensor()
{
// counts the number of times that the scanner doesn't find anyone
// in range. If TotalNoScansAllowed is set to greater than zero, automatically powers down
// the toy
// when the number of no_sensors exceeds TotalNoScansAllowed.
// However, this functionality is disabled if TotalNoScansAllowed is set to zero.
NoSensorCounter++;
if ((NoSensorCounter > TotalNoScansAllowed) && (TotalNoScansAllowed > 0))
{
StopOperation();
llInstantMessage(ObjectOwner, "\nThe " + ObjectName + " has been automatically switched OFF as no Agents have been detected within the set timeframe.");
}
else
{
ApplyDefaultText();
}
}
timer()
{
// initialise WorkChar. This is the variable we will manipulate as we process the text string
if(WorkChar == "")
{
WorkChar = WorkDisplayText;
}
// fire the first character of the remaining string
StartParticle(llGetSubString(WorkChar, 0, 0));
if(llStringLength(WorkChar) == 1)
{
// this is the space character at the end, so prepare to re-initialise
WorkChar = "";
StartSensorCheck();
}
else
{
// remove first character of remaining string, as we've now processed it
WorkChar = llGetSubString(WorkChar, 1, -1);
}
}
listen(integer channel, string name, key id, string message)
{
// listen housekeeping
llListenRemove(ListenChannel);
message = llToLower(message);
// process selection
if ((message == "on") || (message == "off"))
{
// toggle power
if (message == "on")
{
StartOperation();
}
else
{
StopOperation();
llOwnerSay( ObjectName + " has been turned OFF");
}
Power = !Power;
}
else if ((message == "defaults") || (message == "names"))
{
// toggle display mode
if (message == "defaults")
{
FindRandomAVText = "Names";
llOwnerSay("Particle stream will display default texts only");
}
else
{
FindRandomAVText = "Defaults";
llOwnerSay("Particle stream will display random names and default texts");
}
FindRandomAV = !FindRandomAV;
}
else if ((message == "x-axis") || (message == "360degree"))
{
// toggle sensor arc mode
if (message == "x-axis")
{
ScanArc = PI_BY_TWO;
ScanArcText = "360degree";
}
else
{
ScanArc = PI;
ScanArcText = "X-Axis";
}
SayScanSettings();
}
else if (message == "setrange")
{
// send Scanner Range Menu
MenuText = "SET SCANNER RANGE MENU: Please set the scanner range:";
MenuItems = (list)"Close" + ScannerItems;
MakeMenu();
}
else if (llListFindList(ScannerItems, (list)message) != -1)
{
// derive the new range by removing the "m" from the end of 'message'
Range = (float)llGetSubString(message, 0, 1);
SayScanSettings();
}
else if (message == "resetscript")
{
llOwnerSay("Resetting script. Please wait...");
llResetScript();
}
else if (message == "help")
{
string URL_HELPPAGE = "http://wiki.secondlife.com/wiki/Talk:Random_AV_Particle_Name_Generator";
llLoadURL(ObjectOwner, "This link will take you to the " + ObjectName + "'s Help page.", URL_HELPPAGE);
}
else if (message == "close")
{
// let fall thro'
}
else
{
llOwnerSay("MAIN MENU: Unrecognised menu selection");
}
}
// default end
}
//*********** END OF SCRIPT **********
TECHNICAL BLURB:
You don't really need to read the next stuff, but some techie-types might find it
interesting....
Code Improvements:
This is not the first time attempt use the XYText technology to create a particle text stream. A number of free scripts are available in-world, although they all seem to stem from the same source. For the most part they are unstable and unreliable. This has been blamed on "lag". A text particle stream is indeed a delicate operation and it is particularily susceptible to time dilation and "client-lag". However, although this is true, the scripts currently in-world also contain a fundamental llParticleSystem() call flaw which, when corrected, helps dampen the effects of time dilation. Additionally, by making other code changes, the effciency of this time-critical operation is significantly improved. A number of comparisons tests between the existing attempts and the one presented here, in both healthy and heavily time-dilated regions, show that the code improvements in this make it into a viable product.
Client-lag however, is not so easily overcome as it usually a reflection of the PC spec rather than the script as such. High spec PC's, especially those capable of long draw distances and particles set to 8912, will have no problems rendering the characters but tests with lower-end specs have shown that they occasionally miss letters.
Palette Design:
Take the name "William" (7 characters) as an example.
In the original scripts, seven 'finds' would required of the 36 entries in the master UUID palette. In a worst case scenerio, this amounts to 252 (7 x 36) individual 'finds'. And this during the time critical period.
In this new version, before even entering the time critical period and actually firing any particles, we strip the name down to its individual unique characters: "Wilam" (5 characters). We then fetch and use the UUID's for these five characters only. Therefore, during the time critical period, and in the worst case scenerio, only 35 (7 x 5) individual 'finds' are required.
Although this method adds on a fraction of a second before firing the first particle, the benefit in terms of stability is significant.
Optimisations:
This version also introduces a number of code optimisations during the time critcal period. Although performing the same action, the method employed is more efficient. For example:
string WorkChar = llGetSubString(WorkDisplayText, 0, 0);
StartParticle(WorkChar);
is changed to
StartParticle(llGetSubString(WorkDisplayText, 0, 0));
and
string Texture = llList2Key(MasterUUIDPalette, llSubStringIndex(MasterCharIndex, llToUpper(ParticleChar)));
llParticleSystem([
...
PSYS_SRC_TEXTURE, Texture,
...];
is changed to
llParticleSystem([
...
PSYS_SRC_TEXTURE, llList2Key(WorkUUIDPalette, llListFindList(WorkCharIndex, (list)ParticleChar)),
...];
llParticleSystem() Parsing:
In the original versions of the script, parameters such as PSYS_SRC_ACCEL and PSYS_SRC_OMEGA are being forced to be parsed even though they have no active effect on the particle emmsion. The new version removes all such superfluous entries from the llParticleSystem() call code. Furthermore, rather than passing constants to PSYS_SRC_PATTERN and PSYS_PART_FLAGS, which then requires interpreting, we directly pass a heximdecimal value.
Burst Rate and System Age:
Of all the code improvement discussed, the changes made to PSYS_SRC_BURST_RATE and PSYS_SRC_MAX_AGE are by far the single most significant. In the original Particle XYText scripts (and indeed in many particle scripts circulating in-world) PSYS_SRC_MAX_AGE is erroneously set to 0.00. According to the LSL Wiki, "Zero will give the particle system an infinite duration". So, if you want the particle system to last for one second you set PSYS_SRC_MAX_AGE to "1.00", or "2.00" for two seconds and so on. But, if you set it to "0.00" it will continue pumping out particles at the rate defined in PSYS_SRC_BURST_RATE. However, as the emission of each individual character is controlled by a timer, we do not want an emission of infinite duration. What we want is an emission of a most definately defined finite duration. The absolute maximum duration that we want for the particle system is one character ~ which clearly is the same float value that llSetTimerEvent() has been set to.
Similarly, PSYS_SRC_BURST_RATE ~ "the time interval, in seconds, between bursts of particles being emitted" ~ must not be less than the float value that llSetTimerEvent() has been set to. If the PSYS_SRC_BURST_RATE value is set to less than the llSetTimerEvent() value, then (PSYS_SRC_BURST_RATE * PSYS_SRC_BURST_PART_COUNT) emissions will be made before the timer states that the next character in the string is due.
It is the combination of these two misunderstandings that has caused previous attempts at Particle XYText to emit, for example, the name "William" as "WII LLL IAMM". It is not "lag" causing it but incorrect values being passed to PSYS_SRC_BURST_RATE and PSYS_SRC_MAX_AGE.
The version presented here corrects this by introducing a variable "LetterTimeGap". The float value entered for LetterTimeGap not only controls the timer (llSetTimerEvent(LetterTimeGap)) but is also used to populate PSYS_SRC_BURST_RATE and PSYS_SRC_MAX_AGE. This ensures that only one character is capable of being emitted before the particle system shuts down. Furthermore, if the region is subject to time dilation, that dilation is passed through to the particle system rather than just effecting the timer. Any time dilation then remains proportional throughout those parts of the script that it effects most.
