Code summary:
This is the main script to put in an attachment somewhere.
// Semaphore Animation Script
// Ordinal Malaprop
// 2007-09-15
// LICENCE
// This script is licenced under a Creative Commons
// Attribution-Noncommercial-Share Alike licence - see
// http://creativecommons.org/licenses/by-nc-sa/3.0/
// for exact details. To sum up, feel free to distribute or modify
// but it must retain my name and this licence and can't be sold,
// even for L$1.
//--------------------------------------------------------------------
// Globals and constants
// Flag positions for characters
// Every pair of digits indicates right+left arm positions, from 0 to 6
// The index of the correct pair is the index of the character in
// the LETTERS or NUMBERS list, x2
string FLAG_POS = "5512244535251552535436401341424344303132333421221403042363";
// (rest)(numerals)(cancel)ABCDEFGHIJKLMNOPQRSTUVWXYZ
// or in numeric mode
// (r)(n)(c)123456789(letters)0..(negative)
string LETTERS = " n#ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // when in letter mode
string NUMBERS = " ##123456789l0"; // when in number mode
string LEGAL = " ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; // all allowed input characters
// Wait this long between signals
float gInterval = 1.0;
// Channel for command input
integer CHANNEL_INPUT = 9;
// Channel for public output
integer CHANNEL_PUBLIC = -34562421;
// Channel for local communication with attachments
integer CHANNEL_ATTACH = -89823958;
// The current message being processed
string gMessage = "";
// Next message to process
string gMessageQueue = "";
// The final, cleaned message - say this at the end
string gFinalMsg = "";
// Current mode - 0 = letter, 1 = numbers
integer gMode = 0;
// Are we translating other people's messages?
integer gTranslating = TRUE;
// Assorted constants
string ANIM_POSE = "semaphore pose";
string ANIM_ERROR = "error";
string SOUND_CLICK = "4c8c3c77-de8d-bde2-b9b8-32635e0fd4a6";
key TEXTURE_ACTIVE = "30465632-5ac5-378f-a6bf-46ab56883e7a";
key TEXTURE_DISABLED = "2ecd1b3f-4659-2bf9-fda1-a4c47d30c78f";
// Some link numbers, hardcoded, naughty
integer LINK_STATUS_DISPLAY = 2;
integer LINK_INT_BUTTON = 3;
integer LINK_INT_DISPLAY = 4;
integer LINK_ERROR = 5;
//--------------------------------------------------------------------
// Functions
// update_display
// Updates the HUD's display features
update_display()
{
string interval = llGetSubString((string)gInterval, 0, 2);
llMessageLinked(LINK_INT_DISPLAY, -3, interval, NULL_KEY);
llOwnerSay("Interval between signals is " + interval + " seconds");
}
// local_msg
// Function to send messages to attachments etc
local_msg(string msg)
{
llWhisper(CHANNEL_ATTACH, msg);
}
// public_msg
// Function to send messages publicly
public_msg(string msg)
{
llShout(CHANNEL_PUBLIC, llKey2Name(llGetOwner()) + "|" + msg);
}
update_translate(integer translating)
{
gTranslating = translating;
string word = "";
if (!gTranslating) {
word = "not ";
}
llOwnerSay("Currently " + word + "translating other people's semaphore messages");
}
// preload
// Preloads all the animations in the HUD. It does this each time you
// activate it, so that anyone nearby will have them preloaded.
preload()
{
integer n = llGetInventoryNumber(INVENTORY_ANIMATION);
integer f = 0;
string name = "";
do {
name = llGetInventoryName(INVENTORY_ANIMATION, f);
if (name != ANIM_POSE) {
llStartAnimation(name);
llStopAnimation(name);
}
} while (++f < n);
llStartAnimation(ANIM_POSE);
}
// process_next_char
// Main routine function - process the next letter or number in the current message
// and returns the message with that trimmed off.
string process_next_char(string msg)
{
// Quit if there's nothing more to do
if (msg == "") return msg;
// Stop the timer while we process the next letter
llSetTimerEvent(0.0);
string letter = llGetSubString(msg, 0, 0);
// Pick the correct lookup list to use
string lookup = LETTERS;
if (gMode) lookup = NUMBERS;
// Find out the index of the character, and play the appropriate animations
integer pos = llSubStringIndex(lookup, letter);
llMessageLinked(LINK_THIS, -1, llGetSubString(FLAG_POS, pos * 2, pos * 2 + 1), NULL_KEY);
// What is being broadcast
string broadcast = letter;
if (letter == "n") {
gMode = 1;
broadcast = "(numbers)";
}
else if (letter == "l") {
gMode = 0;
broadcast = "(letters)";
}
else if (letter == " ") {
broadcast = "(rest)";
}
llOwnerSay(broadcast);
public_msg(broadcast);
// Restart the timer
llSetTimerEvent(gInterval);
// Was that the last character? If not, strip off the letter and return the result
if (msg != letter) return llGetSubString(msg, 1, -1);
// otherwise return a blank string
else return "";
}
// clean_msg
// This function cleans the original message into a nice legal message string.
// We do this at the start to avoid unexpected delays during semaphoring.
string clean_msg(string msg)
{
msg = llToUpper(msg);
integer f = 0;
integer length = llStringLength(msg);
string newMsg = "";
string lastChar = "";
string letter = "";
integer mode = 0; // 0 = letters 1 = numbers
do {
letter = llGetSubString(msg, f, f);
// Do not allow:
// - Letters not in the legal character list
// - Multiple spaces
if (llSubStringIndex(LEGAL, letter) != -1 && !(letter == " " && (lastChar == " " || f == 0))) {
if (letter == lastChar) {
newMsg += " "; // Insert a break between identical letters
}
if (llSubStringIndex(LETTERS, letter) == -1 && mode == 0) {
// We have a number, when we are in letter mode, so change mode
newMsg += "n";
mode = 1;
}
else if (llSubStringIndex(NUMBERS, letter) == -1 && mode == 1) {
// Letter, when we are in number mode, change
newMsg += "l";
mode = 0;
}
newMsg += letter;
lastChar = letter;
}
} while (++f < length);
// Chop off any spaces at the start and end
if (llGetSubString(newMsg, -1, -1) == " ") {
newMsg = llGetSubString(newMsg, 0, -2);
}
if (llGetSubString(newMsg, 0, 0) == " ") {
newMsg = llGetSubString(newMsg, 1, -1);
}
return newMsg;
}
error()
{
llSetTimerEvent(0.0);
gMessage = "";
gMessageQueue = "";
llStartAnimation(ANIM_ERROR);
public_msg("(error)");
llMessageLinked(LINK_THIS, -2, "", NULL_KEY);
llSleep(2.0);
llStopAnimation(ANIM_ERROR);
llOwnerSay("All messages cancelled.");
}
help()
{
llGiveInventory(llGetOwner(), llGetInventoryName(INVENTORY_NOTECARD, 0));
}
//--------------------------------------------------------------------
// Main program
default
{
on_rez(integer p)
{
llResetScript();
}
state_entry()
{
llSetLinkAlpha(LINK_INT_BUTTON, 1.0, ALL_SIDES);
llSetLinkAlpha(LINK_ERROR, 1.0, ALL_SIDES);
update_display();
update_translate(gTranslating);
llListen(CHANNEL_PUBLIC, "", NULL_KEY, "");
// We need animation permission
llRequestPermissions(llGetOwner(), PERMISSION_TRIGGER_ANIMATION);
}
run_time_permissions(integer perms)
{
if (perms & PERMISSION_TRIGGER_ANIMATION) {
local_msg("active");
llSetLinkTexture(LINK_STATUS_DISPLAY, TEXTURE_ACTIVE, ALL_SIDES);
llListen(CHANNEL_INPUT, "", llGetOwner(), "");
llOwnerSay("Enter a message on channel " + (string)CHANNEL_INPUT + " to say it in semaphore, e.g. '/9 hello world'. Say '/9 !help' for full instructions.");
preload();
}
else {
llOwnerSay("Unfortunately you need to grant animation permissions for me to work.");
state disabled;
}
}
listen(integer c, string name, key id, string msg)
{
if (c == CHANNEL_INPUT) {
if (llGetSubString(msg, 0, 0) == "!") {
// This indicates a special voice command
if (msg == "!translate") {
update_translate(!gTranslating);
}
else if (msg == "!help") {
help();
}
else if (llGetSubString(msg, 0, 8) == "!interval") {
float interval = (float)llGetSubString(msg, 10, -1);
if (interval > 0.0 && interval < 10.0) {
gInterval = interval;
update_display();
}
else {
llOwnerSay("That was not a suitable interval parameter - try one above 0 and below 10.");
}
}
else {
llOwnerSay("That looked like some sort of special command, but unfortunately it wasn't one that I understood.");
}
}
else if (gMessage != "") {
gMessageQueue += " " + msg;
llOwnerSay("Your new message will be added to the current one.");
}
else {
llOwnerSay("Thinking...");
gMessage = "wait"; // set this to stop it adding another message
gFinalMsg = clean_msg(msg);
llOwnerSay("Beginning!");
gMessage = process_next_char(gFinalMsg);
}
}
else if (c == CHANNEL_PUBLIC && gTranslating) {
integer pos = llSubStringIndex(msg, "|");
string name = llGetSubString(msg, 0, pos - 1);
msg = llGetSubString(msg, pos + 1, -1);
if (llStringLength(msg) == 1 || llGetSubString(msg, 0, 0) == "(") {
llOwnerSay(name + " sends the signal '" + msg + "'");
}
else {
llOwnerSay(name + " has sent the message '" + msg + "'");
}
}
}
timer()
{
if (gMessage == "") {
llSetTimerEvent(0.0);
// Stop the current animations
llMessageLinked(LINK_THIS, -2, "", NULL_KEY);
public_msg(gFinalMsg);
gFinalMsg = "";
if (gMessageQueue == "") {
llOwnerSay("Finished!");
}
else {
llOwnerSay("Thinking...");
string msg = gMessageQueue;
gMessageQueue = "";
gMessage = "wait";
gFinalMsg = clean_msg(msg);
llOwnerSay("Beginning!");
gMessage = process_next_char(gFinalMsg);
}
}
else {
gMessage = process_next_char(gMessage);
}
}
touch_start(integer n)
{
if (llDetectedKey(0) != llGetOwner()) return;
llPlaySound(SOUND_CLICK, 1.0);
integer link = llDetectedLinkNumber(0);
if (link == LINK_INT_BUTTON) {
gInterval = gInterval * 2;
if (gInterval > 2.0) gInterval = 0.5;
update_display();
}
else if (link == LINK_ERROR) {
error();
}
else {
state disabled;
}
}
state_exit()
{
llSetTimerEvent(0.0);
llStopAnimation(ANIM_POSE);
}
}
state disabled
{
state_entry()
{
llMessageLinked(LINK_THIS, -2, "", NULL_KEY);
gMessage = "";
gMessageQueue = "";
gMode = 0;
local_msg("disabled");
llSetLinkTexture(LINK_STATUS_DISPLAY, TEXTURE_DISABLED, ALL_SIDES);
llMessageLinked(LINK_INT_DISPLAY, -3, "", NULL_KEY);
llSetLinkAlpha(LINK_INT_BUTTON, 0.5, ALL_SIDES);
llSetLinkAlpha(LINK_ERROR, 0.5, ALL_SIDES);
llOwnerSay("Switched off - touch me again to start");
}
on_rez(integer p)
{
llOwnerSay("Currently disabled - touch me to start semaphore");
}
touch_start(integer n)
{
if (llDetectedKey(0) != llGetOwner()) return;
llPlaySound(SOUND_CLICK, 1.0);
state default;
}
}