Random AV Particle Name Generator

~ PARTICLE TEXT GENERATOR AV SE v1.0 by Debbie Trilling ~
Set-up, configuration and operation Help: http://wiki.secondlife.com/wiki/Talk:Random_AV_Particle_Name_Generator
Profile: http://wiki.secondlife.com/wiki/User:Debbie_Trilling
<lsl>
// ~ 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";
			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
}
</lsl>