Table of Contents

Change Log

1 November 2014

  • Rewrote the script to use multiple notecards and include a configuration notecard.

Introduction

This is a simple town crier, probably best suited for role-play SIMs however it could be used for any location where updates and news have to be broadcasted on the SIM. The script uses a configuration file such that different parameters can be configured.

Setup

To set it up, you will need to create a notecard called configuration and drop it along with the script inside the same primitive. An example documented configuration notecard is the following:

########################### START CONFIGURATION ##############################

# the amount of time in seconds between the shout-outs
interval = 60
# whether to randomize the interval so that a speak can occur between [0.045, interval] seconds.
randomize = true

# list of avatars that are able to toggle the town crier on and off
access = Morgan LeFay, Frank Black

# sound level can be region, shout, say or whisper
level = say

# channel to say the message on (0 is local chat)
# note that if you set level to region, then the channel cannot be 0
channel = 0

########################### END CONFIGURATION ###############################

After that, you can create any number of notecards named sequentially, something like:

crier_1
crier_2

and inside those notecards you can add lines that the town crier will speak.

Code

town_crier.lsl
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
//  Please see: http://www.gnu.org/licenses/gpl.html for legal details,  //
//  rights of fair usage, the disclaimer and warranty conditions.        //
///////////////////////////////////////////////////////////////////////////
 
// for notecard reading
integer line = 0;
key request = NULL_KEY;
 
// key-value data will be read into this list
list tuples = [];
 
// town cries
list cards = [];
integer size = 0;
string card = "";
 
default {
    state_entry() {
        if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) {
            llSay(DEBUG_CHANNEL, "Sorry, could not find a configuration notecard in the inventory.");
            return;
        }
        request = llGetNotecardLine("configuration", line);
    }
    dataserver(key id, string data) {
        if(id != request) return;
        if(data == EOF) state read; // invariant, length(tuples) % 2 == 0
        if(data == "") jump continue;
        integer i = llSubStringIndex(data, "#");
        if(i != -1) data = llDeleteSubString(data, i, -1);
        list o = llParseString2List(data, ["="], []);
        string k = llStringTrim(llList2String(o, 0), STRING_TRIM);
        string v = llStringTrim(llList2String(o, 1), STRING_TRIM);
        if(k == "" || v == "") jump continue;
        tuples += k;
        tuples += v;
@continue;
        request = llGetNotecardLine("configuration", ++line);
    }
    on_rez(integer num) {
        llResetScript();
    }
    changed(integer change) {
        if(change & CHANGED_INVENTORY)
            llResetScript();
    }
}
 
state read {
    state_entry() {
        integer i = llGetInventoryNumber(INVENTORY_NOTECARD)-1;
        do {
            string name = llGetInventoryName(INVENTORY_NOTECARD, i);
            if(llList2String(llParseString2List(name, ["_"], []), 0) != "cries")
                jump continue;
            cards += name;
@continue;
        } while(--i>-1);
        if(llGetListLength(cards) == 0) {
            llSay(DEBUG_CHANNEL, "No cries notecards in the inventory.");
            return;
        }
        state sleep;
    }
    on_rez(integer num) {
        llResetScript();
    }
    changed(integer change) {
        if(change & CHANGED_INVENTORY)
            llResetScript();
    }
}
 
state sleep {
    state_entry() {
        integer time = llList2Integer(
            tuples, 
            llListFindList(
                tuples, 
                (list)"interval"
            ) + 1
        );
        if(
            llToLower(
                llList2String(
                    tuples, 
                    llListFindList(
                        tuples, 
                        (list)"randomize"
                    ) + 1
                )
            ) == "true") {
            llSetTimerEvent(0.045 + llFrand(time));
            return;
        }
        llSetTimerEvent(time);
    }
    timer() {
        llSetTimerEvent(0);
        state select;
    }
    touch_start(integer num) {
        string name = llToLower(llDetectedName(0));
        list access = llParseString2List(
            llList2String(
                tuples, 
                llListFindList(
                    tuples, 
                    (list)"access"
                )+1
            ), [","], []
        );
        do {
            if(llToLower(llStringTrim(llList2String(access, 0), STRING_TRIM)) == name) {
                // authorized
                jump menu;
            }
            access = llDeleteSubList(access, 0, 0);
        } while(llGetListLength(access) != 0);
        return;
@menu;
        key id = llDetectedKey(0);
        integer channel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6));
        llListen(channel, "", id, "");
        llDialog(id, "\n                            Town Crier.\n\nCreated in 2014 by Wizardry and Steamworks\n            1 November 2014: Version: 1.1", [  "◉ ON" ], channel);
    }
    listen(integer channel, string name, key id, string message) {
        if(message == "◉ ON") {
            llSay(0, "The Town Crier is now switched OFF.");
            state pause;
        }
    }
    on_rez(integer num) {
        llResetScript();
    }
    changed(integer change) {
        if(change & CHANGED_INVENTORY)
            llResetScript();
    }
}
 
state select {
    state_entry() {
        card = llList2String(
            cards, 
            (integer)llFrand(
                llGetListLength(
                    cards
                )
            )
        );
        request = llGetNumberOfNotecardLines(card);
    }
    dataserver(key id, string data) {
        if(id != request) return;
        if(data == "") state sleep;
        size = (integer)data;
        state cry;
    }
    on_rez(integer num) {
        llResetScript();
    }
    changed(integer change) {
        if(change & CHANGED_INVENTORY)
            llResetScript();
    }
}
 
state cry {
    state_entry() {
        request = llGetNotecardLine(card, (integer)llFrand(size));
    }
    dataserver(key id, string data) {
        if(id != request) return;
        if(data == "") state sleep;
        string level = llToLower(
            llList2String(
                tuples, 
                llListFindList(
                    tuples, 
                    (list)"level"
                )+1
            )
        );
        integer channel = llList2Integer(
            tuples, 
            llListFindList(
                tuples, 
                (list)"channel"
            )+1
        );
        if(level == "region") {
            if(channel != 0) {
                llRegionSay(channel, data);
                jump done;
            }
            llSay(DEBUG_CHANNEL, "Channel cannot be zero for region say.");
            jump done;
        }
        if(level == "shout") {
            llShout(0, data);
            jump done;
        }
        if(level == "say") {
            llSay(0, data);
            jump done;
        }
        if(level == "whisper") {
            llWhisper(0, data);
            jump done;
        }
@done;
        state sleep;
    }
    on_rez(integer num) {
        llResetScript();
    }
    changed(integer change) {
        if(change & CHANGED_INVENTORY)
            llResetScript();
    }
}
 
state pause {
    touch_start(integer num) {
        string name = llToLower(llDetectedName(0));
        list access = llParseString2List(
            llList2String(
                tuples, 
                llListFindList(
                    tuples, 
                    (list)"access"
                )+1
            ), [","], []
        );
        do {
            if(llToLower(llStringTrim(llList2String(access, 0), STRING_TRIM)) == name) {
                // authorized
                jump menu;
            }
            access = llDeleteSubList(access, 0, 0);
        } while(llGetListLength(access) != 0);
        return;
@menu;
        key id = llDetectedKey(0);
        integer channel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6));
        llListen(channel, "", id, "");
        llDialog(id, "\n                            Town Crier.\n\nCreated in 2014 by Wizardry and Steamworks\n            1 November 2014: Version: 1.1", [  "◯ OFF" ], channel);
    }
    listen(integer channel, string name, key id, string message) {
        if(message == "◯ OFF") {
            llSay(0, "The Town Crier is now switched ON.");
            state pause;
        }
    }
    on_rez(integer num) {
        llResetScript();
    }
    changed(integer change) {
        if(change & CHANGED_INVENTORY)
            llResetScript();
    }
}