// Special Particle Sensor "Brain" Script
// Written by Christopher Omega
//
// Tasks:
// Listen to the owner.
// Parse the owner's message,
// Signal individual locators to reset
// Or point at a certain object within
// 96 meters of the apparatus.

// Global Variables
// targetName Stores the name of the object that's being looked for.
string targetName = "";

// comChannel Stores the channel used to communicate with the rezzors.
integer comChannel;

// Global Constants
// LOCATOR_ALL_LOCATORS A constant defining when the message sent by this script is meant for all locators.
integer LOCATOR_ALL_LOCATORS = -1;

// SENSOR_TYPE_ALL A constant that tells the sensor to look for (ACTIVE|PASSIVE|AGENT).
// 7 is just the integer form of (ACTIVE|PASSIVE|AGENT)
integer SENSOR_TYPE_ALL = 7;

// SENSOR_MAX_RANGE A constant defining the max. range of the sensor to run. As of SL v1.2.13, it is 96.0 meters.
float SENSOR_MAX_RANGE = 96.0;

// MESSAGE_PARAMETER_SEPERATOR A constant that is used as a seperator in parsing lists sent as strings.
string MESSAGE_PARAMETER_SEPERATOR = "|~|";

// HELP_NOTECARD_NAME A constant that stores the name of the help notecard given out when object is touched.
string HELP_NOTECARD_NAME = "Finder - Usage Instructions";

// SENSOR_TYPE_* Provide a mapping between the sensor type integer values and the integer value's names.
list SENSOR_TYPE_STRINGS = ["AGENT", "ACTIVE", "PASSIVE", "SCRIPTED"];
list SENSOR_TYPE_CONSTANTS = [AGENT, ACTIVE, PASSIVE, SCRIPTED];

// strStartsWith()
// Returns a boolean-integer that tells if the string starts with the prefix.
// @param str The string to search.
// @param prefix The prefix of str to find.
// @return TRUE or FALSE, if str starts with prefix.
integer strStartsWith(string str, string prefix) {
return (llSubStringIndex(str, prefix) == 0);
}

// contactLocator()
// Sends a message to the locators.
// @param locatorNumber The number identifyer of the locater to send the message to. LOCATOR_ALL_LOCATORS to send to all.
// @param parameters The parameters to send to the locator.
// @param command The command to send to the locator.
contactLocator(integer locatorNumber, string parameters, string command) {
integer linkNumber = locatorNumber + 2;

if(locatorNumber == LOCATOR_ALL_LOCATORS) linkNumber = LINK_ALL_OTHERS;

llMessageLinked(linkNumber, 0, parameters, command);
}

// pointLocatorAt()
// Sends a message to the locator specified by locatorNumber
// Telling it to point itself and its particle system at a
// target defined by targetId and targetPosition.
// @param locatorNumber The particle emitter to tell to point.
// @param targetId The UUID (key) of the target to point at.
// @param targetPosition The position, in region-local coordinates, of the target.
pointLocatorAt(integer locatorNumber, key targetId, vector targetPosition) {
list paramList = [targetId, targetPosition];
contactLocator(locatorNumber, llDumpList2String(paramList, MESSAGE_PARAMETER_SEPERATOR), "POINT_AT");
}

// resetLocator()
// Kills the particle system emininating from the locator defined by locatorNumber.
// @param locatorNumber The particle emitter in which to shut off. LOCATOR_ALL_LOCATERS to turn off all.
resetLocator(integer locatorNumber) {
contactLocator(locatorNumber, "", "RESET"); // Turn off the particles.
}

// sensrTypeString2Integer()
// Translates a string containing sensor types, seperated by | into an integer
// that can be passed to llSensor to search for that OR'ed list of types.
// @param sensorTypeStr The string of sensor types.
// @return An integer that can be passed to llSensor representing the OR'ed list of types.
integer sensorTypeString2Integer(string sensorTypeStr) {
llSay(0, "Got argument: " + sensorTypeStr);
integer ret;
list sensorTypeNames = llParseString2List(sensorTypeStr, ["|"], []);

integer i;
integer len = llGetListLength(sensorTypeNames);
for (i = 0; i < len; i++) {
integer typeNameIndex = llListFindList(SENSOR_TYPE_STRINGS, [llList2String(sensorTypeNames, i)]);

if (typeNameIndex != -1) {
ret = ret | (integer) llList2String(SENSOR_TYPE_CONSTANTS, typeNameIndex);
}
else {
llSay(0, "Error: Sensor type unrecognized. You can only choose from: "
+ llList2CSV(SENSOR_TYPE_STRINGS));
return 0;
}
}
return ret;
}

default {
on_rez(integer p) {
resetLocator(LOCATOR_ALL_LOCATORS);
llShout(comChannel, "die");
llResetScript();
}
state_entry() {
llSay(0, "Running! To request usage instructions, click me!");
llListen(0, "", llGetOwner(), "");
resetLocator(LOCATOR_ALL_LOCATORS);
comChannel = (integer)llFrand(2147483648) + 1;
}
touch_start(integer n)
{
llGiveInventory(llDetectedKey(0), HELP_NOTECARD_NAME);
llSay(0, "Gave " + HELP_NOTECARD_NAME + ".");
}
listen(integer chan, string name, key id, string msg) {
if (strStartsWith(msg, "#reset")) { // User wants to reset the finder.
llSensorRemove();
llShout(comChannel, "die");
resetLocator(LOCATOR_ALL_LOCATORS);

llSay(0, "Finder reset.");
}
else if (strStartsWith(msg, "#die")) { // User wants to delete all moving pointers.
llShout(comChannel, "die");
}
else if (strStartsWith(msg, "#findall ")) { // User wants to find all of a certain type of thing.
resetLocator(LOCATOR_ALL_LOCATORS);

string sensorTypeStr = llDeleteSubString(msg, 0, 8); // Delete "#findall " from msg.

integer sensorType = sensorTypeString2Integer(sensorTypeStr);
if (sensorType != 0) { // Valid sensor string said by user.
llShout(comChannel, "die"); // Kill the remote locators.

llSensor("", NULL_KEY, sensorType, SENSOR_MAX_RANGE, TWO_PI);
llSay(0, "Searching for all objects of type: " + sensorTypeStr);
}
}
else if (strStartsWith(msg, "#find ")) {
resetLocator(LOCATOR_ALL_LOCATORS);

targetName = llDeleteSubString(msg, 0, 5); // Delete "#find " from msg
llShout(comChannel, "die");
llSensor(targetName, NULL_KEY, SENSOR_TYPE_ALL, SENSOR_MAX_RANGE, TWO_PI);
llSay(0, "Searching for " + targetName);
}
}
no_sensor()
{
llSay(0, targetName + " not found within a distance of "
+ (string) SENSOR_MAX_RANGE + " meters.");

llSensorRemove();
}
sensor(integer numDetected) {
integer i;
for (i = 0; i < numDetected; i++) {
key targetKey = llDetectedKey(i);
vector targetPos = llDetectedPos(i);
llSay(0, targetName + " found at " + (string) targetPos
+ " with key " + (string) targetKey);
pointLocatorAt(i, targetKey, targetPos);
llRezObject("movingPointer", llGetPos() + <0,0,1>, <0,0,0>, <0,0,0,1>, comChannel);
llSleep(0.5);
list toSay = [targetKey, targetPos];
llWhisper(comChannel, llDumpList2String(toSay, MESSAGE_PARAMETER_SEPERATOR));
}
}
}