AdvancedNotecardReader
Jump to navigation
Jump to search
// Multi-file configuration, v1a
// Lear Cale, 2007
// Public Domain. Feel free to delete header comments.
// This script reads all configuration notecards that match a given suffix,
// storing the settings in global variables. It rereads the notecards
// on any inventory change.
// It does not require a script reset on any event, but one is done for
// owner change on general principles. If your object needs not to be
// reset on owner change, you may delete those lines from the code.
// ==============================================================
// Places where you change the code are marked with "%%%"
// ==============================================================
// Example config variables -- replace these with meaningful stuff! %%%
// For this example, we allow the user to configure any number of "foos",
// with an integer parameter for each foo. There's also a float "bar"
// parameter.
// Replace these lines with your globals and try to save: you'll get
// errors in the places you need to edit.
list Foos; // %%% all foos configured
list FooVals; // %%% value for each foo configured
float Bar; // %%% a configurable parameter
// %%% Static parameters for reading card config: you may change these, but don't have to.
integer ConfigRequired = FALSE; // whether to fail if no config cards
string ConfigNotecardSuffix = ".cfg"; // string identifying config notecards
float ConfigTimeout = 60.0; // seconds to wait for slow server
// Globals for reading card config
integer ConfigLineIndex; // line number in notecard we're reading
key ConfigRequestID; // request we're waiting for
list ConfigCards; // list of names of config notecards
string ConfigCardName; // name of card being read
integer ConfigCardIndex; // index of next card to read
integer Debug; // Whether to print debug text
config_init()
{
// Initialize all configurable global variables here %%%
// Don't initialize your configurable globals at their
// declarations (above). Doing it here provides default
// behavior if the user deletes a config line from a notecard
// rather than retaining the previous value.
Foos = []; // %%%
Bar = 20.0; // %%%
}
// print the configuration, handy for debugging.
config_dump()
{
// Replace this example code with your config %%%.
say("Foos: " + llList2CSV(Foos)); // %%%
say("FooVals: " + llList2CSV(FooVals)); // %%%
say("Bar: " + (string) Bar); // %%%
}
// Example notecard line parser.
// This function is called for each configuration line.
// It treats all config notecards the same.
//
// For this example (which you can modify to suit your purposes)
// Comments lines begin with a slash
// Each line begins with a command followed by optional arguments
// An Equals-sign ("=") separates command and arguments (and args from each other)
// Unrecognized commands are ignored -- good for forwards-backwards notecard compatibility
// cardName and lineNum are in case you want to print error messages.
// This example doesn't allow spaces around the "=". If you want to allow
// and ignore them, use this instead of ["="] in llParsStringKeepNulls().
// [" = ", " = ", " = ", " = ", " =", "= ", "="]
//
// Example config file contents:
// foo=Lowell George=1979
// foo=Freddy Murcury=1991
// foo=Robert Johnson=1938
// bar=1.5
config_parse(string str, string cardName, integer lineNum)
{
str = llStringTrim(str, STRING_TRIM_HEAD); // delete leading spaces
// lines beginning with slash are comments -- ignore them
if (llGetSubString(str,0,0) == "/") {
return;
}
list ldata = llParseStringKeepNulls(str, ["="], [""]);
string cmd = llList2String(ldata,0);
string arg1 = llList2String(ldata,1);
string arg2 = llList2String(ldata,2);
// %%% Add more lines such as the above as needed for more arguments.
// %%% Process example commands -- replace this code with meaningful stuff! %%%
if (cmd == "foo") {
// another Foo configured: remember it
Foos += [arg1];
FooVals += [(integer) arg2];
} else if (cmd == "bar") {
Bar = (float) arg1;
}
// this one is a good one to keep
else if (cmd == "debug") {
Debug = (integer) arg1;
}
}
// Post-process any config, if necessary
config_done() {
if (Debug) {
config_dump();
}
say("Config done");
}
// ==== Utilities ====
// Say something, in this case to owner (%%% modify to whisper or whatever)
say(string str)
{
llOwnerSay(str);
}
// Say something if debug is enabled
debug(string str)
{
if (Debug) {
say(llGetScriptName() + ": " + str);
}
}
// Get the next notecard name, and
// return TRUE if there is one
integer next_card()
{
if (ConfigCardIndex >= llGetListLength(ConfigCards)) {
ConfigCards = [];
return (FALSE);
}
ConfigLineIndex = 0;
ConfigCardName = llList2String(ConfigCards, ConfigCardIndex);
ConfigCardIndex++;
ConfigRequestID = llGetNotecardLine(ConfigCardName, ConfigLineIndex);
say("Reading " + ConfigCardName);
return (TRUE);
}
States:
// Default state can do any init you need that doesn't require configuration.
default
{
state_entry() {
llSetText("", <1.0,1.0,1.0>, 1.0);
state s_config;
}
}
// This state is only used to get into s_config, because going from
// s_config to s_config won't redo it's state_entry. But we might
// not want to redo anything we might have put in default state entry.
state s_reconfig
{
state_entry() {
state s_config;
}
}
// Read card config
// Multiple notecard version - read all cards with the given extension
state s_config
{
state_entry() {
config_init();
string item;
ConfigCards = [];
integer n = llGetInventoryNumber(INVENTORY_NOTECARD);
while (n-- > 0) {
item = llGetInventoryName(INVENTORY_NOTECARD, n);
// Note: for simplicity, read cards with the "suffix" anywhere in the name
if (llSubStringIndex(item, ConfigNotecardSuffix) != -1) {
ConfigCards += [item];
}
}
ConfigCardIndex = 0;
if (next_card()) {
llSetTimerEvent(ConfigTimeout);
} else if (ConfigRequired) {
say("Configuration notecard missing.");
state s_configRetry;
} else {
state s_active;
}
}
dataserver(key query_id, string data) {
if (query_id == ConfigRequestID) {
if (data == EOF) {
if (! next_card()) {
config_done();
state s_active;
}
} else {
config_parse(data, ConfigCardName, ConfigLineIndex);
ConfigRequestID = llGetNotecardLine(ConfigCardName, ++ConfigLineIndex);
llSetTimerEvent(ConfigTimeout);
}
}
}
timer() {
say("Dataserver time out: touch to retry");
state s_configRetry;
}
on_rez(integer num) { state s_reconfig; }
changed(integer change) {
if (change & CHANGED_OWNER) { llResetScript(); }
if (change & CHANGED_INVENTORY) { state s_reconfig; }
}
state_exit() {
llSetTimerEvent(0);
}
}
state s_configRetry
{
touch_start(integer tot) {
if (llDetectedKey(0) == llGetOwner()) {
state s_config;
}
}
changed(integer change) {
if (change & CHANGED_OWNER) { llResetScript(); }
if (change & CHANGED_INVENTORY) { state s_config; }
}
}
// State to go into if notecard is required but missing.
// You can delete this and the code above that refers to it,
// or just set ConfigurationRequired to FALSE.
state s_unconfigured
{
state_entry() {
llSetText("Configuration missing", <1.0,1.0,1.0>, 1.0);
}
changed(integer change) {
if (change & CHANGED_OWNER) { llResetScript(); }
if (change & CHANGED_INVENTORY) { state s_reconfig; }
}
state_exit() {
llSetText("", <1.0,1.0,1.0>, 1.0);
}
}
// The fun starts here!
state s_active
{
// %%% Your code goes here!
// Every state should usually have this, or something like it.
changed(integer change) {
if (change & CHANGED_OWNER) { llResetScript(); }
if (change & CHANGED_INVENTORY) { state s_reconfig; }
}
}