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