Damian Zhaoying пре 2 година
комит
aa8a7bbfe5
100 измењених фајлова са 18378 додато и 0 уклоњено
  1. 47 0
      README.txt
  2. 3 0
      assets/README.txt
  3. 60 0
      assets/buttons/sloodle_open_in_browser.lslp
  4. 127 0
      assets/buttons/sloodle_reset_button.lslp
  5. 407 0
      assets/lang/en/sloodle_translation_en.lslp
  6. 212 0
      assets/misc/error_messages.lslp
  7. 397 0
      assets/misc/mem_array.lslp
  8. 132 0
      assets/misc/memory_controller.lslp
  9. 165 0
      assets/misc/skeleton_function.lslp
  10. 268 0
      assets/misc/sloodle_error_message_handler.lslp
  11. 71 0
      assets/misc/sloodle_multi_url_loader.lslp
  12. 525 0
      assets/misc/sloodle_setup_user.lslp
  13. 147 0
      assets/misc/sloodle_status_codes.lslp
  14. 85 0
      assets/sloodle_email_login_details.lslp
  15. 86 0
      assets/sloodle_hover_text_displayer.lslp
  16. 421 0
      assets/sloodle_layout_object.lslp
  17. 72 0
      assets/sloodle_parallel_url_loader_0.lslp
  18. 72 0
      assets/sloodle_parallel_url_loader_1.lslp
  19. 72 0
      assets/sloodle_parallel_url_loader_2.lslp
  20. 72 0
      assets/sloodle_parallel_url_loader_3.lslp
  21. 72 0
      assets/sloodle_parallel_url_loader_x.lslp
  22. 162 0
      assets/sloodle_setup_notecard.lslp
  23. 819 0
      assets/sloodle_setup_web.lslp
  24. 44 0
      assets/sloodle_url_loader.lslp
  25. BIN
      assets/textures/rezzer/rezzerclosed.png
  26. BIN
      assets/textures/rezzer/rezzerclosed.psd
  27. BIN
      assets/textures/rezzer/sloodle_rezzer-2.1.jpg
  28. BIN
      assets/textures/rezzer/sloodle_rezzer-2.2.jpg
  29. BIN
      assets/textures/rezzer/sloodle_rezzer-2.3.jpg
  30. BIN
      assets/textures/rezzer/sloodle_rezzer-2.4.jpg
  31. BIN
      assets/textures/rezzer/sloodle_rezzer-2.5.jpg
  32. BIN
      assets/textures/rezzer/sloodle_rezzer_inside.psd
  33. 321 0
      assets/xytext/xytext.lslp.1
  34. 320 0
      assets/xytext/xytext.lslp.10
  35. 320 0
      assets/xytext/xytext.lslp.11
  36. 321 0
      assets/xytext/xytext.lslp.12
  37. 321 0
      assets/xytext/xytext.lslp.13
  38. 320 0
      assets/xytext/xytext.lslp.14
  39. 320 0
      assets/xytext/xytext.lslp.15
  40. 320 0
      assets/xytext/xytext.lslp.16
  41. 320 0
      assets/xytext/xytext.lslp.17
  42. 320 0
      assets/xytext/xytext.lslp.18
  43. 320 0
      assets/xytext/xytext.lslp.19
  44. 320 0
      assets/xytext/xytext.lslp.2
  45. 321 0
      assets/xytext/xytext.lslp.20
  46. 320 0
      assets/xytext/xytext.lslp.21
  47. 321 0
      assets/xytext/xytext.lslp.22
  48. 320 0
      assets/xytext/xytext.lslp.23
  49. 320 0
      assets/xytext/xytext.lslp.24
  50. 320 0
      assets/xytext/xytext.lslp.3
  51. 321 0
      assets/xytext/xytext.lslp.4
  52. 321 0
      assets/xytext/xytext.lslp.5
  53. 320 0
      assets/xytext/xytext.lslp.6
  54. 321 0
      assets/xytext/xytext.lslp.7
  55. 320 0
      assets/xytext/xytext.lslp.8
  56. 320 0
      assets/xytext/xytext.lslp.9
  57. 47 0
      backup/moodle2/backup_sloodle_activity_task.class.php
  58. 27 0
      backup/moodle2/backup_sloodle_settingslib.php
  59. 209 0
      backup/moodle2/backup_sloodle_stepslib.php
  60. 167 0
      backup/moodle2/restore_sloodle_activity_task.class.php
  61. 289 0
      backup/moodle2/restore_sloodle_stepslib.php
  62. 234 0
      backuplib.php
  63. 52 0
      classroom/active_object_ping_linker.php
  64. 56 0
      classroom/auth_check_linker.php
  65. 143 0
      classroom/auth_object_linker.php
  66. 329 0
      classroom/configure_object.php
  67. 95 0
      classroom/confirm_active_objects.php
  68. 63 0
      classroom/course_info_linker.php
  69. 152 0
      classroom/httpin_config_linker.php
  70. 48 0
      classroom/httpin_url_update_linker.php
  71. 174 0
      classroom/loginzone.php
  72. 137 0
      classroom/notecard_configuration_form.php
  73. 174 0
      classroom/notecard_configuration_view.php
  74. 78 0
      classroom/object_config_linker.php
  75. 80 0
      classroom/object_configuration_form_template.php
  76. 114 0
      classroom/store_object_config.php
  77. 138 0
      config.html
  78. 193 0
      db/access.php
  79. 21 0
      db/install.php
  80. 410 0
      db/install.xml
  81. 788 0
      db/upgrade.php
  82. 52 0
      freemail/README.txt
  83. 249 0
      freemail/lib/email_processors/sloodle_freemail_sloodle_email_processor.php
  84. 468 0
      freemail/lib/freemail_email_processor.php
  85. 233 0
      freemail/lib/freemail_imap_message_handler.php
  86. 148 0
      freemail/lib/freemail_moodle_importer.php
  87. 131 0
      freemail/lib/moodle_importers/sloodle_freemail_blog_moodle_importer.php
  88. 56 0
      freemail/readmail.php
  89. 58 0
      freemail/settings.php
  90. 136 0
      freemail/view.php
  91. BIN
      icon.gif
  92. 146 0
      index.php
  93. 220 0
      init.php
  94. 12 0
      lang/en/help/sloodle/auto_enrolment.html
  95. 15 0
      lang/en/help/sloodle/auto_registration.html
  96. 1 0
      lang/en/help/sloodle/awards_confighelp.html
  97. 1 0
      lang/en/help/sloodle/awards_confighelp2.html
  98. 4 0
      lang/en/help/sloodle/awardsassignment.html
  99. 2 0
      lang/en/help/sloodle/icurrency.html
  100. 2 0
      lang/en/help/sloodle/maxpoints.html

+ 47 - 0
README.txt

@@ -0,0 +1,47 @@
+SLOODLE 2.x
+Edmund Edgar, Paul Preibisch, 2012-05-12
+
+This is the main server-side component for the SLOODLE system connecting Moodle the 3D virtual worlds of Second Life or OpenSim.
+See http://www.sloodle.org for details.
+
+You will also need a set of in-world objects in Second Life or OpenSim. These are contained in the SLOODLE Rezzer.
+Details of where to get these are available at sloodle.org. 
+You can also create an IAR for OpenSim from scratch using the code at:
+https://github.com/sloodle/sloodle_opensim_iar
+
+This is for SLOODLE 2.x. Previous versions are managed using Subversion, hosted at Google Code:
+http://code.google.com/p/sloodle/
+
+This module installs under your Moodle mod/ directory, like other Moodle modules.
+See http://slisapps.sjsu.edu/sl/index.php/Install_Sloodle for step-by-step instructions.
+
+Web Server requirements:
+* A web server running Moodle 1.9 or Moodle 2.x.
+* This has been tested mainly on Moodle 2.0. Cursory testing has been done on 1.9, 2.1, 2.2 and 2.3.
+* This code should run on either UNIX-like operating systems or Windows. However, the import functions of the presenter tool may misbehave on Windows, and the beanstalkd-based optimization won't be usable at all.
+
+Browser requirements:
+* Any regular web browser, with JavaScript turned on.
+* Presenter upload functionality uses Flash.
+
+Viewer requirements:
+* Some tools require Shared Media. This needs a viewer based on the Linden viewer, version 2.0 or higher.
+
+Grid requirements:
+* You can use the SLOODLE 2.x objects on the main Second Life grid or an OpenSim grid. However, your OpenSim grid and your Moodle server need to be able to send HTTP requests to each other. This means:
+ 1) Your Moodle server needs to accept traffic from SL/OpenSim on the same port as the normal Moodle website. This is normally not a problem, but some Moodle sites using external sign-on methods may prevent it.
+ 2) Your Moodle server needs to allow traffic out on the ports used by your grid for HTTP-in. For Second Life, these are 12046 and 12043. Some large Moodle hosting providers have firewalls that block outgoing traffic, or limit it to ports 80 and 443.
+ 3) Traffic needs to be routable from your Moodle server to your grid. This can be a problem if you are running a public Moodle server outside your firewall, but your own OpenSim server inside your firewall. Fixing it may require NAT settings to allow traffic coming from outside your firewall to be directed to your grid, and ports opening to allow HTTP-in traffic through to it.
+
+Optional components:
+* The presenter tool has a feature to import PDFs and convert them into slides. This needs ImageMagick, available either through the command line or using PHP bindings.
+* It is possible to optimize the sending of messages from the server to objects by adding a message queue, and running a process in the background called sloodled.php. This uses a separate piece of software called beanstalkd. See the comments at the top of sloodled.php for more details.
+
+Other SLOODLE components:
+* The primdrop tool for submitting in-world objects as homework uses the sloodleobject assignment plugin at https://github.com/sloodle/moodle-assignment_sloodleobject
+* An optional menu block is available showing students' avatar information and helping people navigate to SLOODLE tools in Moodle: https://github.com/sloodle/moodle-block_sloodle_menu
+* A separate menu block displays currency information related to the awards system: https://github.com/sloodle/moodle-block_sloodle_backpack
+All the above are included in the zip / tarball release. The latest version is available at http://download.avatarclassroom.com/sloodle/
+
+
+Please post any questions or comments on the forums at http://www.sloodle.org.

+ 3 - 0
assets/README.txt

@@ -0,0 +1,3 @@
+This directory is for LSL scripts, sounds and textures which are shared among multiple objects.
+Assets that are only used by a single type of object should saved in the mod/<modname>/objects/<objectcode>/assets directory.
+Assets that are shared, but only by object using the same mod/ should saved in the mod/<modname>/assets directory.

+ 60 - 0
assets/buttons/sloodle_open_in_browser.lslp

@@ -0,0 +1,60 @@
+//
+// The line above should be left blank to avoid script errors in OpenSim.
+
+/*
+*  Part of the Sloodle project (www.sloodle.org)
+*
+*  Copyright (c) 2011-06 contributors (see below)
+*  Released under the GNU GPL v3
+*  -------------------------------------------
+*
+*  This program is free software: you can redistribute it and/or modify
+*  it under the terms of the GNU General Public License as published by
+*  the Free Software Foundation, either version 3 of the License, or
+*  (at your option) any later version.
+*
+*
+*  This program is distributed in the hope that it will be useful,
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*  GNU General Public License for more details.
+*  You should have received a copy of the GNU General Public License
+*  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*
+*  All scripts must maintain this copyrite information, including the contributer information listed
+*
+*  Contributors:
+*   Paul Preibisch
+*   Edmund Edgar
+*
+*  DESCRIPTION
+*/
+
+
+string view_url;
+string admin_url;
+string sloodleserverroot = "";
+string paramstr;
+integer SLOODLE_CHANNEL_OPEN_IN_BROWSER= -1639277000;
+
+
+default{
+    on_rez(integer start_param) {
+        llResetScript();
+    }
+touch_start( integer total_number){
+    integer j;
+    for (j=0;j<total_number;j++){
+        if (llDetectedKey(j)!=llGetOwner())return;
+        llMessageLinked(LINK_ALL_OTHERS, SLOODLE_CHANNEL_OPEN_IN_BROWSER, "", llDetectedKey(j));    
+    }        
+    
+   
+        
+    }
+}
+
+
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: mod/scoreboard-1.0/objects/default/assets/sloodle_connect_button.lslp
+

+ 127 - 0
assets/buttons/sloodle_reset_button.lslp

@@ -0,0 +1,127 @@
+//
+// The line above should be left blank to avoid script errors in OpenSim.
+
+// LSL script generated: mod.set-1.0.rezzer_reset_btn.lslp Tue Nov 15 15:49:28 Tokyo Standard Time 2011
+/*
+*  Part of the Sloodle project (www.sloodle.org)
+*
+*  Copyright (c) 2011-06 contributors (see below)
+*  Released under the GNU GPL v3
+*  -------------------------------------------
+*
+*  This program is free software: you can redistribute it and/or modify
+*  it under the terms of the GNU General Public License as published by
+*  the Free Software Foundation, either version 3 of the License, or
+*  (at your option) any later version.
+*
+*
+*  This program is distributed in the hope that it will be useful,
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*  GNU General Public License for more details.
+*  You should have received a copy of the GNU General Public License
+*  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*
+*  All scripts must maintain this copyrite information, including the contributer information listed
+*
+*  Contributors:
+*  Edmund Edgar
+*  Paul Preibisch
+*
+*  DESCRIPTION
+*  This script sits inside of the reset button.  When clicked, a countdown is started.
+*  The countdown can be stopped, by clicking the button again as a toggle.
+*  If zero is reached by the counter, a linked message is sent to the link_set with a do:reset command
+*
+*/
+
+//reset
+//gets a vector from a string
+vector RED = <0.77278,4.391e-2,0.0>;
+vector YELLOW = <0.82192,0.86066,0.0>;
+vector WHITE = <1.0,1.0,1.0>;
+float BTN_RESET_OFFSET = 0.25;
+float BTN_CANCEL_OFFSET = -0.25;
+integer FACE = 4;
+integer counter = 0;
+integer TIME_LIMIT = 7;
+integer SLOODLE_CHANNEL_OBJECT_DIALOG = -3857343;
+list facilitators;
+integer toggle = -1;
+default {
+
+    on_rez(integer start_param) {
+        llResetScript();
+    }
+
+    state_entry() {
+        llSetText("",RED,1.0);
+        toggle = (-1);
+        llSetTexture("btn_reset_cancel",FACE);
+        llOffsetTexture(0,BTN_RESET_OFFSET,FACE);
+        llSetObjectName("btn:Reset");
+        facilitators += llStringTrim(llToLower(llKey2Name(llGetOwner())),STRING_TRIM);
+    }
+   touch_start(integer d) {
+       integer j;
+       for (j=0;j<d;j++){
+       		if (llDetectedKey(j)!=llGetOwner()){
+       			llSay(0,"Sorry, only "+llKey2Name(llGetOwner())+" can reset this device.");
+       			return;
+       		}
+	       	 llTriggerSound("click",1.0);
+	        if ((toggle == (-1))) {
+	            toggle *= (-1);
+	            llSetColor(YELLOW,FACE);
+	            llOffsetTexture(0,BTN_CANCEL_OFFSET,FACE);
+	            llSetObjectName("btn:Cancel");
+	            llSetTimerEvent(1);
+	        }
+	        else  {
+	            toggle *= (-1);
+	            llSetColor(WHITE,FACE);
+	            llOffsetTexture(0,BTN_RESET_OFFSET,FACE);
+	            llSetText("",RED,1.0);
+	            llSetTimerEvent(0);
+	            llSetObjectName("btn:Reset");
+	            counter = 0;
+	        }
+       }
+       
+    }
+
+  timer() {
+        counter++;
+        vector color;
+        if ((llGetColor(FACE) == YELLOW)) {
+            color = RED;
+        } else {
+            color = YELLOW;
+        }
+        llSetText((("(" + ((string)(TIME_LIMIT - counter))) + ")"),color,1.0);
+        llSetColor(color,FACE);
+        if ((counter >= TIME_LIMIT)) {
+            llSetTimerEvent(0.0);
+            llOffsetTexture(0,BTN_RESET_OFFSET,FACE);
+            llSetObjectName("btn:Reset");
+            toggle *= (-1);
+            llSetText("",RED,1.0);
+            llSetColor(WHITE,FACE);
+            llMessageLinked(LINK_SET,SLOODLE_CHANNEL_OBJECT_DIALOG,"do:reset",NULL_KEY);
+            counter = 0;
+        }
+        else  llTriggerSound("beepbeep",0.2);
+    }
+
+  changed(integer change) {
+        if ((change == CHANGED_INVENTORY)) {
+            llOffsetTexture(0,BTN_RESET_OFFSET,FACE);
+            llSetObjectName("btn:Reset");
+            llResetScript();
+        }
+    }
+}
+
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: mod/set-1.0/objects/default/assets/rezzer_reset_btn.lslp 
+

+ 407 - 0
assets/lang/en/sloodle_translation_en.lslp

@@ -0,0 +1,407 @@
+//
+// The line above should be left blank to avoid script errors in OpenSim.
+
+// Standard translation script for Sloodle.
+// Contains the common, re-usable words and phrases.
+//
+// The "locstrings" list is pairs of strings.
+// The first of each pair is the name, and second is the translation.
+//
+// This script is part of the Sloodle project.
+// Copyright (c) 2008-9 Sloodle (various contributors)
+// Released under the GNU GPL v3
+//
+// Contributors:
+//  Peter R. Bloomfield
+//
+
+// Note: where a translation string contains {{x}} (where x is a number),
+//  it means that a parameter can be inserted. Please make sure to include these
+//  parameters in the appropriate location in your translation.
+// It may be sensible to add comments after your string to indicate what its parameters mean.
+// NOTE: parameter numbering starts at 0 (unlike previous versions, which started at 1).
+
+// Translations can be requested by sending a link message on the SLOODLE_CHANNEL_TRANSLATION_REQUEST channel.
+// It is advisable simply to use the "sloodle_translation_request" function provided in this script.
+
+
+///// TRANSLATION /////
+
+// Localization batch - indicates the purpose of this file
+string mybatch = ""; // Blank - this is the common Sloodle batch
+
+
+// List of string names and translation pairs.
+// The name is the first of each pair, and should not be translated.
+// The second of each pair is the translation.
+// Additional comments are sometimes given afterward to aid translations.
+list locstrings = [
+    //  Common terms
+    "yes", "Yes",
+    "no", "No",
+    "on", "On",
+    "off", "Off",
+    "enabled", "Enabled",
+    "disabled", "Disabled",
+
+    //  Web-configuration
+    "webconfigmenu", "Sloodle Web-Configuration Menu\n\n{{0}} = Access web-configuration page\n{{1}} = Download configuration", // Parameters are button labels
+    "configlink", "Use this link to configure the object.",
+    "chatserveraddress", "Please chat the address of your Moodle site, without a trailing slash. For example: http://www.yoursite.blah/moodle",
+    "waitingforserveraddress", "Waiting for Moodle site address.\nPlease chat it on channel 0 or 1.",
+    "checkingserverat", "Checking Moodle site at:\n{{0}}", // Parameter gives the address of a Moodle site
+    "sendingconfig", "Sending configuration data...",
+    "touchforwebconfig", "Touch me to start web-configuration",
+    
+    // User-centric authorisation
+    "userauthurl", "Please login to Moodle with this URL to authorize the object for your own use.",
+
+    //  General connection and authorisation
+    "readynotconnected", "Ready\n[Not connected]",
+    "shutdown", "Shutdown",
+    "connected", "Connected successfully",
+    "readyconnectedto", "Ready\n[Connected to: {{0}}]", // Parameter should identify what is connected to (e.g. URL of website)
+    "readyconnectedto:sitecourse", "Ready\n[Site: {{0}}]\n[Course: {{1}}]", // Parameters: site address, course name
+    "connectionfailed", "Connection failed",
+    "httperror", "ERROR: HTTP request failed",
+    "httperror:code", "ERROR: HTTP request failed with code {{0}}",
+    "httpempty", "ERROR: HTTP response empty",
+    "httptimeout", "ERROR: HTTP request timed out.",
+    "servererror", "ERROR: server responded with status code {{0}}",
+    "notypeid", "ERROR: failed to identify object type ID",
+    "gottype", "Identified object type as {{0}}", // Parameter gives an object type ID
+    "failedcheckcompatibility", "ERROR: failed to check compatibility with site",
+    "badresponseformat", "ERROR: response from server was badly formatted",
+    "objectauthfailed:code", "ERROR: object authorisation failed with code {{0}}",
+    "objectconfigfailed:code", "ERROR: object configuration failed with code {{0}}",
+    "initobjectauth", "Initiating object authorisation...",
+    "autoreg:newaccount", "A new Moodle account has been automatically generated for you.\nWebsite: {{0}} \nUsername: {{1}}\nPassword: {{2}}", // Parameters: site address, username, password
+    "configurationreceived", "Ready",
+    "configdatamissing", "ERROR: some required data was missing from the configuration {{0}}",
+    "readingconfignotecard", "Reading configuration notecard...",
+    "checkingcourse", "Checking course...",
+    "errortouchtoreset", "ERROR\nTouch me to reset",
+    "notconfiguredyet", "Sorry {{0}}. I am not configured yet.", // Parameter: avatar name
+    "resetting", "Resetting...",
+    "noconfigavailable", "There is no configuration available to download. Please visit the configuration web-page first.",
+    "checkingauth", "Checking authorisation...",
+    "usedescriptioninstead", "If you can't see the shared media screen, please enter your Moodle URL (http://...) in the description field of the rezzer and press \"Reset\"",
+    "openrezzerurl", "Click the URL below to open the rezzer screen in your browser: \n{{0}}",
+
+    //  Sloodle installation/version
+    "sloodlenotinstalled", "ERROR: Sloodle is not installed on specified site.",
+    "sloodleversioninstalled", "Sloodle version installed on server: {{0}}", // Parameter gives a Sloodle version number
+    "sloodleversionrequired", "ERROR: you require at least Sloodle version {{0}}", // Parameter gives a Sloodle version number
+
+    //  Permissions
+    "nopermission:use", "Sorry {{0}}. You do not have permission to use this object.", // Parameter should be the name of an avatar
+    "nopermission:ctrl", "Sorry {{0}}. You do not have permission to control this object.", // Parameter should be the name of an avatar
+    "nopermission:authobjects", "Sorry {{0}}. You do not have permission to authorise objects on this course.", // Parameter should be the name of an avatar
+    
+    // Layout (duplicated deliberately)
+    "layout:failedretrying", "Failed to store layout position. Retrying...",
+    "layout:failedaborting", "Failed to store layout position. Aborting.",
+    "layout:toofar", "Failed to store layout position - too far from rezzer.",
+    "layout:storedobject", "Object stored in layout.",
+    
+    // General error
+    "sloodleerror", "SLOODLE error ({{0}}): please lookup SLOODLE wiki for error information", // Parameters: status code of error
+    "sloodleerror:desc", "SLOODLE error ({{0}}): {{1}}" // Parameters: status code of error, text description of error
+];
+
+///// ----------- /////
+
+
+// Link message channels
+integer SLOODLE_CHANNEL_TRANSLATION_REQUEST = -1928374651;
+integer SLOODLE_CHANNEL_TRANSLATION_RESPONSE = -1928374652;
+
+// Translation output methods
+string SLOODLE_TRANSLATE_LINK = "link";                     // No output parameters - simply returns the translation on SLOODLE_TRANSLATION_RESPONSE link message channel
+string SLOODLE_TRANSLATE_SAY = "say";                       // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_WHISPER = "whisper";               // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_SHOUT = "shout";                   // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_REGION_SAY = "regionsay";          // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_OWNER_SAY = "ownersay";            // No output parameters
+string SLOODLE_TRANSLATE_DIALOG = "dialog";                 // Recipient avatar should be identified in link message keyval. At least 2 output parameters: first the channel number for the dialog, and then 1 to 12 button label strings.
+string SLOODLE_TRANSLATE_LOAD_URL = "loadurl";              // Recipient avatar should be identified in link message keyval. 1 output parameter giving URL to load.
+string SLOODLE_TRANSLATE_LOAD_URL_PARALLEL = "loadurlpar";  // Recipient avatar should be identified in link message keyval. 1 output parameter giving URL to load.
+string SLOODLE_TRANSLATE_HOVER_TEXT = "hovertext";          // 2 output parameters: colour <r,g,b>, and alpha value
+string SLOODLE_TRANSLATE_HOVER_TEXT_LINKED_PRIM= "hovertext_linked_prim"; // 3 output parameters: colour <r,g,b>,  alpha value, link number
+string SLOODLE_TRANSLATE_IM = "instantmessage";             // Recipient avatar should be identified in link message keyval. No output parameters.
+string SLOODLE_TRANSLATE_TEXTBOX="textbox";//asks via a text box
+
+// Used for sending parallel URL loading messages
+integer SLOODLE_CHANNEL_OBJECT_LOAD_URL = -1639270041;
+
+///// FUNCTIONS /////
+
+
+// Send a translation request link message
+// (Here for reference only)
+// Parameter: output_method = should identify an output method, as given by the "SLOODLE_TRANSLATE_..." constants above
+// Parameter: output_params = a list of parameters which controls the output, such as chat channel or buttons for a dialog
+// Parameter: string_name = the name of the localization string to output
+// Parameter: string_params = a list of parameters which will be included in the translated string (or an empty list if none)
+// Parameter: keyval = a key to send in the link message
+// Parameter: batch = the name of the localization batch which should handle this request
+sloodle_translation_request(string output_method, list output_params, string string_name, list string_params, key keyval, string batch)
+{
+    llMessageLinked(LINK_THIS, SLOODLE_CHANNEL_TRANSLATION_REQUEST, output_method + "|" + llList2CSV(output_params) + "|" + string_name + "|" + llList2CSV(string_params) + "|" + batch, keyval);
+}
+
+// Send a translation response link message
+sloodle_translation_response(integer target, string name, string translation)
+{
+    llMessageLinked(target, SLOODLE_CHANNEL_TRANSLATION_RESPONSE, name + "|" + translation + "|" + mybatch, NULL_KEY);
+}
+
+// Get the translation of a particular string
+string sloodle_get_string(string name)
+{
+    // Attempt to find the string name
+    integer numstrings = llGetListLength(locstrings);
+    integer pos = llListFindList(locstrings, [name]);
+    
+    // IMPORTANT: we must be careful not to match a translations instead of a name.
+    // If this was an even number, then we have a string name.
+    if ((pos % 2) == 0) {
+        // Make sure there is a subsequent translation in the list
+        if ((pos + 1) < numstrings) return llList2String(locstrings, pos + 1);
+        // The translation is not there
+        pos = -1;
+    }
+    
+    // If we could not find the string, then return a placeholder
+    if (pos < 0) return "[[" + name + "]]";
+    
+    // If we got here, then we matched a translation instead of a string name.
+    // As such, we need to resort to searching through the list manually (which can be very slow).
+    // To saved time, we can start from the position just beyond where we got to.
+    // We advance by 2 each time to skip the translations completely.
+    //pos += 1;
+    for (pos += 1; pos < numstrings; pos += 2) {
+        // Do we have a match?
+        if (llList2String(locstrings, pos) == name) {
+            // Yes - make sure there is a translation following it
+            if ((pos + 1) < numstrings) return llList2String(locstrings, pos + 1);
+            // The translation is not there - skip the rest of the search
+            pos = numstrings;
+        }
+    }
+    
+    // If we reached this point, then the string is not available. Return a placeholder.
+    return "[[" + name + "]]";
+}
+
+// Send a debug link message
+sloodle_debug(string msg)
+{
+    llMessageLinked(LINK_THIS, DEBUG_CHANNEL, msg, NULL_KEY);
+}
+
+
+// Get a formatted translation of a string
+string sloodle_get_string_f(string name, list params)
+{
+    // Get the string itself
+    string str = sloodle_get_string(name);
+    // How many parameters do we have?
+    integer numparams = llGetListLength(params);
+    
+    // Go through each parameter we have been provided
+    integer curparamnum;
+    string curparamtok = "{{x}}";
+    integer curparamtoklength = 0;
+    string curparamstr = "";
+    integer tokpos = -1;
+    for (curparamnum=0; curparamnum < numparams; curparamnum++) {
+        // Construct this parameter token
+        curparamtok = "{{" + (string)(curparamnum) + "}}";
+        curparamtoklength = llStringLength(curparamtok);
+        // Fetch the parameter text
+        curparamstr = llList2String(params, curparamnum);
+        
+        // Ensure the parameter text does NOT contain double braces (this avoids an infinite loop!)
+        if (llSubStringIndex(curparamstr, "{{") < 0 && llSubStringIndex(curparamstr, "}}") < 0) {            
+            // Go through every instance of this parameter's token
+            while ((tokpos = llSubStringIndex(str, curparamtok)) >= 0) {
+                // Replace the token with the parameter string
+                str = llDeleteSubString(str, tokpos, tokpos + curparamtoklength - 1);
+                str = llInsertString(str, tokpos, curparamstr);
+            }
+        }
+    }
+    
+    return str;
+}
+
+
+///// STATES /////
+
+default
+{
+    state_entry()
+    {
+    }
+    
+    link_message(integer sender_num, integer num, string str, key id)
+    {
+        // Check the channel
+        if (num == SLOODLE_CHANNEL_TRANSLATION_REQUEST) {            
+            
+            // // PROCESS REQUEST // //
+        
+            // Split the incoming message into fields
+            list fields = llParseStringKeepNulls(str, ["|"], []);
+            integer numfields = llGetListLength(fields);
+            
+            // Extract and check the localization batch
+            string batch = "";
+            if (numfields > 4) batch = llList2String(fields, 4);
+            if (batch != mybatch) return;
+            
+            // We expect at least 3 fields
+            // ... or 4 if there are insertion parameters...
+            // ... anybody up for a 6th parameter? :)
+            if (numfields < 3) {
+                sloodle_debug("ERROR: Insufficient fields for translation of string.");
+                return;
+            }
+            
+            // Extract the key parts of the request
+            string output_method = llList2String(fields, 0);
+            list output_params = llCSV2List(llList2String(fields, 1));
+            integer num_output_params = llGetListLength(output_params);
+            string string_name = llList2String(fields, 2);
+            list string_params = [];
+            if (numfields > 3) {
+                // Extract the string parameters (extra text added to the output)
+                string string_param_text = llList2String(fields, 3);
+                if (string_param_text != "") string_params = llCSV2List(string_param_text);
+            }
+            
+            // // TRANSLATE STRING // //
+            
+            // This string will store the translation
+            string trans = "";
+            // Do nothing if the string name is empty
+            if (string_name != "") {
+                // If there are no string parameters, then it is only a basic translation
+                if (llGetListLength(string_params) == 0) {
+                    // Get the basic translation
+                    trans = sloodle_get_string(string_name);
+                } else {
+                    // Construct the formatted string
+                    trans = sloodle_get_string_f(string_name, string_params);
+                }
+            }
+            
+            // // OUTPUT STRING // //
+            
+            // Check what output method has been requested
+            if (output_method == SLOODLE_TRANSLATE_LINK) {
+                // Return the string via link message
+                sloodle_translation_response(sender_num, string_name, trans);
+                
+            } else if (output_method == SLOODLE_TRANSLATE_SAY) {
+                // Say the string
+                if (num_output_params > 0) llSay(llList2Integer(output_params, 0), trans);
+                else sloodle_debug("ERROR: Insufficient output parameters to say string \"" + string_name + "\".");
+                
+            } else if (output_method == SLOODLE_TRANSLATE_WHISPER) {
+                // Whisper the string
+                if (num_output_params > 0) llWhisper(llList2Integer(output_params, 0), trans);
+                else sloodle_debug("ERROR: Insufficient output parameters to whisper string \"" + string_name + "\".");
+                
+            } else if (output_method == SLOODLE_TRANSLATE_SHOUT) {
+                // Shout the string
+                if (num_output_params > 0) llShout(llList2Integer(output_params, 0), trans);
+                else sloodle_debug("ERROR: Insufficient output parameters to shout string \"" + string_name + "\".");
+                
+            } else if (output_method == SLOODLE_TRANSLATE_REGION_SAY) {
+                // RegionSay the string
+                if (num_output_params > 0) llRegionSay(llList2Integer(output_params, 0), trans);
+                else sloodle_debug("ERROR: Insufficient output parameters to region-say string \"" + string_name + "\".");
+                
+            } else if (output_method == SLOODLE_TRANSLATE_OWNER_SAY) {
+                // Ownersay the string
+                llOwnerSay(trans);
+                
+            } else if (output_method == SLOODLE_TRANSLATE_DIALOG) {
+                // Display a dialog - we need a valid key
+                if (id == NULL_KEY) {
+                    sloodle_debug("ERROR: Non-null key value required to show dialog with string \"" + string_name + "\".");
+                    return;
+                }
+                // We need at least 2 additional output parameters (channel, and at least 1 button)
+                if (num_output_params >= 2) {
+                    // Extract the channel number
+                    integer channel = llList2Integer(output_params, 0);
+                    // Extract up to 12 button values
+                    list buttons = llList2List(output_params, 1, 12);
+                    
+                    // Display the dialog
+                    llDialog(id, trans, buttons, channel);
+                    
+                } else sloodle_debug("ERROR: Insufficient output parameters to show dialog with string \"" + string_name + "\".");
+                
+            } else if (output_method == SLOODLE_TRANSLATE_LOAD_URL) {
+                // Display a URL - we need a valid key
+                if (id == NULL_KEY) {
+                    sloodle_debug("ERROR: Non-null key value required to load URL with string \"" + string_name + "\".");
+                    return;
+                }                
+                // We need 1 additional parameter, containing the URL to load
+                if (num_output_params >= 1) llLoadURL(id, trans, llList2String(output_params, 0));
+                else sloodle_debug("ERROR: Insufficient output parameters to load URL with string \"" + string_name + "\".");
+            
+            } else if (output_method == SLOODLE_TRANSLATE_LOAD_URL_PARALLEL) {
+                // Display a URL - we need a valid key
+                if (id == NULL_KEY) {
+                    sloodle_debug("ERROR: Non-null key value required to load URL with string \"" + string_name + "\".");
+                    return;
+                }
+                
+                // NOTE: currently the parallel URL loaders will drop any text.
+                // TODO: make parallel URL loaders use text as well.
+                
+                // We need 1 additional parameter, containing the URL to load
+                if (num_output_params >= 1) llMessageLinked(LINK_THIS, SLOODLE_CHANNEL_OBJECT_LOAD_URL, llList2String(output_params, 0), id);
+                else sloodle_debug("ERROR: Insufficient output parameters to load URL with string \"" + string_name + "\".");
+            
+            } else if (output_method == SLOODLE_TRANSLATE_HOVER_TEXT) {
+                //example usage: sloodle_translation_request(SLOODLE_TRANSLATE_HOVER_TEXT, [BLUE, 1.0], "quizname", [quiz_name], llGetOwner(), "quizzer");
+                // We need 1 additional parameter, containing the URL to load
+                if (num_output_params >= 2) llSetText(trans, (vector)llList2String(output_params, 0), (float)llList2String(output_params, 1));
+                else sloodle_debug("ERROR: Insufficient output parameters to show hover text with string \"" + string_name + "\".");
+            } else if (output_method == SLOODLE_TRANSLATE_HOVER_TEXT_LINKED_PRIM) {
+                //example usage: sloodle_translation_request(SLOODLE_TRANSLATE_HOVER_TEXT_LINKED_PRIM, [BLUE, 1.0,7], "quizname", [quiz_name], llGetOwner(), "quizzer");
+                // We need 1 additional parameter, containing the URL to load
+                if (num_output_params >= 3) {
+                    list rules = [ PRIM_TEXT, trans, (vector)llList2String(output_params, 0), (float)llList2String(output_params, 1) ];
+                    llSetLinkPrimitiveParamsFast( llList2Integer(output_params, 2), rules );
+                }
+                else sloodle_debug("ERROR: Insufficient output parameters to show hover text with string \"" + string_name + "\". Expecting translation text, color vector, alpha float.");
+            
+            }
+             else if (output_method == SLOODLE_TRANSLATE_IM) {
+                // Send an IM - we need a valid key
+                if (id == NULL_KEY) {
+                    sloodle_debug("ERROR: Non-null key value required to send IM with string \"" + string_name + "\".");
+                    return;
+                }                
+                // Send the IM
+                llInstantMessage(id, trans);
+
+            } else {
+                // Don't know the output method
+                sloodle_debug("ERROR: unrecognised output method \"" + output_method + "\".");
+            }
+        }
+    }
+}
+
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: assets/lang/en/sloodle_translation_en.lslp 
+

+ 212 - 0
assets/misc/error_messages.lslp

@@ -0,0 +1,212 @@
+//
+// The line above should be left blank to avoid script errors in OpenSim.
+
+/**********************************************************************************************
+*  error_messages.lsl
+*  Copyright (c) 2009 Paul Preibisch
+*  Released under the GNU GPL 3.0
+*  This script can be used in your scripts, but you must include this copyright header as per the GPL Licence
+*  For more information about GPL 3.0 - see: http://www.gnu.org/copyleft/gpl.html
+* 
+*
+*  This script is part of the SLOODLE Project see http://sloodle.org
+*
+*  Copywrite
+*  Paul G. Preibisch (Fire Centaur in SL)
+*  fire@b3dMultiTech.com  
+/**********************************************************************************************/
+integer SLOODLE_CHANNEL_ERROR_TRANSLATION_REQUEST=-1828374651;
+list errorMessages;
+
+// Translation output methods
+string SLOODLE_TRANSLATE_LINK = "link";                     // No output parameters - simply returns the translation on SLOODLE_TRANSLATION_RESPONSE link message channel
+string SLOODLE_TRANSLATE_SAY = "say";                       // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_WHISPER = "whisper";               // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_SHOUT = "shout";                   // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_REGION_SAY = "regionsay";          // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_OWNER_SAY = "ownersay";            // No output parameters
+string SLOODLE_TRANSLATE_DIALOG = "dialog";                 // Recipient avatar should be identified in link message keyval. At least 2 output parameters: first the channel number for the dialog, and then 1 to 12 button label strings.
+string SLOODLE_TRANSLATE_LOAD_URL = "loadurl";              // Recipient avatar should be identified in link message keyval. 1 output parameter giving URL to load.
+string SLOODLE_TRANSLATE_LOAD_URL_PARALLEL = "loadurlpar";  // Recipient avatar should be identified in link message keyval. 1 output parameter giving URL to load.
+string SLOODLE_TRANSLATE_HOVER_TEXT = "hovertext";          // 2 output parameters: colour <r,g,b>, and alpha value
+string SLOODLE_TRANSLATE_IM = "instantmessage";             // Recipient avatar should be identified in link message keyval. No output parameters.
+
+string sloodle_get_error_string(integer statusCode){
+    list found =fncFindStride(errorMessages, [statusCode],3);
+    integer index = llList2Integer(found,0);
+    list values;    
+    string trans;
+    if (llList2Integer(found,0)!=-1){
+         values = fncGetStride(errorMessages, index, 3);    //will return a stride of len 3 - ie:  [-213,"error message"]
+         trans = llList2String(values,1);
+    }
+    return trans; 
+}
+
+/****************************************************************
+* integer fncStrideCount(list source, integer stride)
+*
+* Returns the number of strides found within a list.
+* Source is the list to operate on, and stride is the length of each stride within the list.
+* list Source = ["A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", "C3"];
+* integer Result = fncStrideCount(Source, 3);
+* Returns 3
+* Returns number of Strides in a List
+*********************************************************************/
+integer fncStrideCount(list lstSource, integer intStride)
+{
+  return llGetListLength(lstSource) / intStride;
+}
+
+/*********************************************************************
+* list fncGetStride(list source, integer index, integer stride)
+* Returns a single stride from the list.
+* Source is the list to retrieve from, index is which stride to return (zero indexed), and stride is the length of each stride.
+* list Source = ["A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", "C3"];
+* 
+* list Result = fncGetStride(Source, 1, 3);
+* 
+*  Returns ["B1", "B2", "B3"]
+*  On failure returns an empty list
+**********************************************************************/
+list fncGetStride(list lstSource, integer intIndex, integer intStride)
+{
+  integer intNumStrides = fncStrideCount(lstSource, intStride);
+  
+  if (intNumStrides != 0 && intIndex < intNumStrides)
+  {
+    integer intOffset = intIndex * intStride;
+    return llList2List(lstSource, intOffset, intOffset + (intStride - 1));
+  }
+  return [];
+}
+/****************************************************************
+* list fncFindStride(list source, list item, integer stride)
+*
+* Returns the stride number and element number of an element within a stride, within a list. (zero indexed)
+* Source is the list to search, item is a single element list containing the item to search for, and stride is the length of each stride.
+* list Source = ["A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", "C3"];
+* list Item = ["B3"];
+* 
+* list Result = fncFindStride(Source, Item, 3);
+* Returns [1, 2].
+* The first number is which stride Item is in.
+* The second is which element of the stride is Item.
+* 
+* If item is not found, it will return [-1, -1]
+*********************************************************************/
+list fncFindStride(list lstSource, list lstItem, integer intStride)
+ {
+     integer intListIndex = llListFindList(lstSource, lstItem);
+   
+   if (intListIndex == -1) { return [-1, -1]; }
+   
+   integer intStrideIndex = intListIndex / intStride;
+   integer intSubIndex = intListIndex % intStride;
+   
+  return [intStrideIndex, intSubIndex];
+}
+default {
+    on_rez(integer start_param) {
+        llResetScript();
+    }
+    state_entry() {
+        errorMessages=[-1 ,"An unspecified error occured","MISC"];
+        errorMessages+=[-101,"An unknown system error occured","SYSTEM"];
+        errorMessages+=[-102,"Could not connect to the database.","SYSTEM"];
+        errorMessages+=[-103,"Could not fetch data we expected to be there. ","SYSTEM"];
+        errorMessages+=[-104,"Data formatting error ","SYSTEM"];
+        errorMessages+=[-105,"XMLRPC error","SYSTEM"];
+        errorMessages+=[-106,"The Sloodle module is not installed in Moodle ","SYSTEM"];
+        errorMessages+=[-111,"System is temporarily down for maintenance. ","SYSTEM"];
+        errorMessages+=[-121,"Failed to send required email ","SYSTEM"];
+        errorMessages+=[-131,"Failed to load plugins ","PLUGIN"];
+        errorMessages+=[-132,"The required plugin was not found ","PLUGIN"];
+        errorMessages+=[-201,"     There was an unspecified problem authenticating the object. ","OBJECT_AUTH"];
+        errorMessages+=[-212,"Object did not supply the necessary information to authenticate itself. ","OBJECT_AUTH"];
+        errorMessages+=[-213,"Object authentication key was invalid ","OBJECT_AUTH"];
+        errorMessages+=[-214,"Object not authorised in this context ","OBJECT_AUTH"];
+        errorMessages+=[-215,"Prim password access has been disabled for this Controller. ","OBJECT_AUTH"];
+        errorMessages+=[-301,"Object not found in database ","USER AUTH"];
+        errorMessages+=[-311,"Object did not supply the necessary information to authenticate the user. ","USER AUTH"];
+        errorMessages+=[-321,"User was not registered and we weren't allowed to register them automatically. ","USER_AUTH"];
+        errorMessages+=[-322,"User was not registered and we tried to register them but failed. ","USER_AUTH"];
+        errorMessages+=[-331,"User did not have permission to access the resources requested . ","USER_AUTH"];
+        errorMessages+=[-341,"Password cannot be reset via Sloodle, as user was not auto-registered (or has since registered an email account). ","USER_AUTH"];
+        errorMessages+=[-401,"There was an unspecified problem involving course enrolment. ","USER_ENROL"];
+        errorMessages+=[-421,"User was not enrolled on the course and we weren't allowed to enrol them automatically","USER_ENROL"];
+        errorMessages+=[-422,"User was not enrolled on the course and tried to enrol them but failed. ","USER_ENROL"];
+        errorMessages+=[-501,"There was an unspecified problem involving the course you ar trying to use. ","COURSE"];
+        errorMessages+=[-511,"The course you are trying to use was not specified. ","COURSE"];
+        errorMessages+=[-512,"The course you are trying to use was not found. ","COURSE"];
+        errorMessages+=[-513,"The course you are trying to use was inactive. ","COURSE"];
+        errorMessages+=[-514,"The course you are trying to use was forbidden from being used with Sloodle. ","COURSE"];
+        errorMessages+=[-601,"There was an unspecifed problem involving the module you're trying to use. ","MODULE"];
+        errorMessages+=[-611,"The module you are trying to use was not specified.","MODULE"];
+        errorMessages+=[-612,"The module you are trying to use was found. ","MODULE"];
+        errorMessages+=[-613,"The module you are trying to use was inactive. ","MODULE"];
+        errorMessages+=[-614,"The module you are trying to use was forbidden from being used by Sloodle. ","MODULE"];    
+        errorMessages+=[-701,"There was an unspecifed problem involving the module instance you're trying to use. ","MODULE_INSTANCE"];
+        errorMessages+=[-711,"The module instance you are trying to use was not specified. ","MODULE_INSTANCE"];
+        errorMessages+=[-712,"The module instance you are trying to use was not found. ","MODULE_INSTANCE"];
+        errorMessages+=[-713,"The module instance you are trying to use was inactive. ","MODULE_INSTANCE"];
+        errorMessages+=[-714,"The module instance you are trying to use was forbidden from being used by Sloodle. ","MODULE_INSTANCE"];
+        errorMessages+=[-801,"Requested script permanently deleted. ","REQUEST"];
+        errorMessages+=[-802,"Requested script moved (new location may be specified in the first data line) ","REQUEST"];
+        errorMessages+=[-811,"Request incomplete (not all required data present) ","REQUEST"];
+        errorMessages+=[-812,"Requested resource could not be found (although the request itself was handled successfully) ","REQUEST"];
+        errorMessages+=[-901,"Unknown profile error ","PROFILE"];
+        errorMessages+=[-902,"Profile does not exist ","PROFILE"];
+        errorMessages+=[-903,"Profile already exists ","PROFILE"];
+        errorMessages+=[-904,"Unknown profile command ","PROFILE"];
+        errorMessages+=[-777000,"Transaction for this user was not found","TRANSACTION"];
+        errorMessages+=[10001,"Obtained results successfully","CHOICE_QUERY"];
+        errorMessages+=[10011,"Added new choice selection for user ","CHOICE_SELECT"];
+        errorMessages+=[10012,"Updated choice selection for user ","CHOICE_SELECT"];
+        errorMessages+=[10013,"User had previously selected the same option ","CHOICE_SELECT"];
+        errorMessages+=[10101,"Successfully added a chat message into the database (commonly used as a side-effect code) ","CHAT_MESSAGE"];
+        errorMessages+=[-10001,"Unable to query choice status for unknown reason ","CHOICE_QUERY"];
+        errorMessages+=[-10011,"The user has already selected a choice, and choices cannot be updated ","CHOICE_SELECT"];
+        errorMessages+=[-10012,"Maximum number of selections for this choice already made ","CHOICE_SELECT"];
+        errorMessages+=[-10013,"Choice is not yet open for selections ","CHOICE_SELECT"];
+        errorMessages+=[-10014,"Choice has been closed ","CHOICE_SELECT"];
+        errorMessages+=[-10015,"Specified option not found in choice ","CHOICE_SELECT"];
+        errorMessages+=[-10016,"Unable to make choice selection for unknown reason (usually more data appended in data lines) ","CHOICE_SELECT"];
+        errorMessages+=[-10021,"Unable to query list of choice instances for unknown reason ","CHOICE_LIST_QUERY"];
+        errorMessages+=[-10022,"Not choice instances available in course ","CHOICE_LIST_QUERY"];
+        errorMessages+=[-10101,"Failed to add chat message to database ","CHAT_MESSAGE"];
+        errorMessages+=[-10201,"User is not authorised to submit assignments ","ASSIGNMENT"];
+        errorMessages+=[-10202,"Assignment is not yet open for submissions ","ASSIGNMENT"];
+        errorMessages+=[-10203,"Assignment due date has passed, and is closed for submissions ","ASSIGNMENT"];
+        errorMessages+=[-10204,"Assignment due date has passed, but is still accepting submissions (typically a side effect code) ","ASSIGNMENT"];
+        errorMessages+=[-10205,"Resubmissions are not permitted ","ASSIGNMENT"];
+        errorMessages+=[-10301,"Student has used all quiz attempts ","QUIZ"];
+        errorMessages+=[-10302,"Quiz requires password - this is not supported by Sloodle ","QUIZ"];
+        errorMessages+=[-10303,"      Error loading questions ","QUIZ"];        
+        errorMessages+=[-10401,"Blogging is disabled on this site. ","BLOG"];
+        errorMessages+=[-10402,"User does not have permission to write blogs ","BLOG"];
+        errorMessages+=[-10501,"There are no slides in this presentation. ","PRESENTER"];
+    }
+    link_message(integer sender_num, integer channel, string str, key id) {
+        if (channel==SLOODLE_CHANNEL_ERROR_TRANSLATION_REQUEST){
+            //message will be in the format
+            //status code|senderuuid;
+            list fields = llParseStringKeepNulls(str, ["|"], []);
+            
+            integer numfields = llGetListLength(fields);            
+            string method= llList2String(fields,0);
+            key senderUuid = llList2Key(fields,1);
+            integer statusCode = llList2Integer(fields,2);
+            
+            //TRANSLATE status code//
+            string   trans = sloodle_get_error_string(statusCode);
+            if (trans!=""){
+                if (method == SLOODLE_TRANSLATE_SAY)    llSay(0,"Error Message: "+trans);
+                else if (method==SLOODLE_TRANSLATE_IM) llInstantMessage(senderUuid,"Error Message: " + trans);
+            }else    llSay(0,"Error Message Translation not found - please view http://slisweb.sjsu.edu/sl/index.php/Sloodle_status_codes for more information: Error Code: "+(string)statusCode);      
+        }
+    }
+}
+
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: assets/misc/error_messages.lslp 

+ 397 - 0
assets/misc/mem_array.lslp

@@ -0,0 +1,397 @@
+//
+// The line above should be left blank to avoid script errors in OpenSim.
+
+/**********************************************************************************************
+*  mem_array.lsl
+*  Copyright (c) 2009 Paul Preibisch
+*  Released under the GNU GPL 3.0
+*  This script can be used in your scripts, but you must include this copyright header as per the GPL Licence
+*  For more information about GPL 3.0 - see: http://www.gnu.org/copyleft/gpl.html
+* 
+*
+*  This script is part of the SLOODLE Project see http://sloodle.org
+*
+*  Contributors:
+*  Paul G. Preibisch (Fire Centaur in SL)
+*  fire@b3dMultiTech.com  
+/**********************************************************************************************
+
+/**********************************************************************************************
+* INTENT
+*  This script was created in order to add a gaming element to the SLOODLE Educational Project
+*  See http://sloodle.org 
+*  I've used an earlier version of it, in my zombie attacks game here: 
+*  http://www.youtube.com/watch?v=LPowKJA6Tnw&feature=channel_page
+*
+*  By using this script, developers can store data in SL using linked messages without having to worry about
+*  storage limitations of LSL Lists (providing there are enough mem_array scripts in the prim)
+*
+*  Using this script, I am able to download student data from a mysql database directly into lsl memory
+*  This way, my scripts can respond faster to in-world events without having to perform HTTP requests during
+*  times which require faster responses.  Make sure you compile this script in mono to increase memory availability
+*  
+*  I hope this script is useful to you, and encourages you to make some exciting new scripts and help us all
+*  expand the virtual world - and maybe even make a few educational games!
+*  I'd also like to thank Linden Lab for developing a platform where 3d content can be created and shared in a 
+*  Massively Multiplayer environment.  I hope others will emerge soon and join the fun!
+*
+*  Sincerely, Paul Preibisch
+*
+*  PURPOSE
+*  The Purpose of this script is to increase the amount of data we can store using lsl lists.  Instead of 
+*  storing data in a local list in your script, you can instead issue a llMessageLinked message to store the data
+*  in the mem_array scripts.  These scripts work together in a chain like pattern.  Ie: commands are always 
+*  issued to
+*  the first mem_array script, then, go to mem_array 1, mem_array 2 etc, until the last mem_array script is reached.
+*  In this way, the calling script can operate independantly of the mem_array scriots, and doesnt need to keep track
+*  of how many mem_array scripts there are, it simply needs to issue
+*  commands, and wait for a response.
+* 
+*  I've modelled this script after a typical database system which has rows and columns.
+*  Therefore, you'll see that I've used lsl lists to represent "Columns" in a database table.  And 
+*  individual list items consititute a table row.  So, for example
+*  the entire database table in this script is comprised of:
+*  list column0;
+*  list column1;
+*
+*  To print Row 0 of the table would then be:  llSay(0,llList2String(column0,0) +", " + llList2String(column1,0);
+* 
+*  You'll also notice that I've made use of several constant values throughout the script
+*  I've designed this script in this way, in order to be easy to read as a developer.
+*  So, when sending a linked message, I don't just send the data, alone like:  INSERT|somedata|somedata
+*  But instead,  I preface the data with contextual dextriptions
+*  ie: COMMAND:INSERT|COLUMN0:some data|COLUMN1:somedata
+*  In this way, the developer can easily see what is being sent in the linked message.  This may seam unnecessary
+*  but since LindenLab, or another 3rd party hasn't developed a code stepper where
+*  we can watch the values of our variables etc
+*  during the debug stage, this can greatly help to see what is going on using a few llOwnerSay 
+*  commands in the linked_message event.
+*  --------------------------------------------------------------------------------------------------
+*  INSERTING DATA
+*  --------------------------------------------------------------------------------------------------
+*  To insert data into the chain, I've created a memory_controller script.  As mentioned, this script operates independantly
+*  of the mem_array scripts.  It simply needs to issue an insert command, and the mem_array scripts will handle the rest.
+*    
+*  llMessageLinked(LINK_SET, 0, "COMMAND:INSTERT|COLUMN0:some data|COLUMN1:some data", NULL_KEY);
+*
+*  WHAT HAPPENS:  
+*  Since link_num was set to 0 when this command was executed, only the first mem_array script will respond. 
+*  It will first check if it has any free memory, if it does, it will store the column0 data in a list called column0, and the column1 data in a list called column1
+*  If you need to save more than 2 data elements, simply modify this script by adding more lists - called column2,column3 etc
+*  
+*  If the mem_array script is getting full, instead of inserting the data in it's column lists, it will instead relay the INSERT message to the next
+*  mem_array script. (ie: if "mem_array 1" is full, it will pass the insert message to "mem_array 2".  If all mem_arrays are full, then the last
+*  mem_array script will report an error to the calling script, via a linked message.  Thus, all commands trickle down through the chain until
+*  the end of the chain.
+*  --------------------------------------------------------------------------------------------------  
+*  RETRIEVING DATA
+*  --------------------------------------------------------------------------------------------------
+*  To retrieve data, you simply need to issue the following linked message:
+*  
+*  llMessageLinked(LINK_SET,0,"COMMAND:GETDATA|COLUMN0:some data, NULL_KEY);
+*
+*  Example:  Maybe you are using these to store a list of visitor names.  Then you issue:
+*  llMessageLinked(LINK_SET,0,"COMMAND:GETDATA|COLUMN0:Fire Centaur, NULL_KEY);
+*
+*  WHAT HAPPENS:
+*  "mem_array" (the first script) would receive the command, search through column0, and if found, send a linked message 
+*  back to your script with the other columns appended.
+*  a typical response would be:
+*
+*  RESPONSE:FOUND SEARCHITEM0|COLUMN0:Fire Centaur|COLUMN1:other data which was stored
+*
+*  
+*  --------------------------------------------------------------------------------------------------  
+*  REMOVING A ROW
+*  --------------------------------------------------------------------------------------------------
+*  To remove a row, simply issue the following:
+* 
+*  llMessageLinked(LINK_SET,0,"COMMAND:REMOVE|COLUMN0:Fire Centaur", NULL_KEY);
+*  
+*  WHAT HAPPENS:
+*  "mem_array" (the first script) would receive the command, search through column0, and if found, 
+*  remove the data from column0, column1 etc
+*  If the data was found, then the mem_array will send back a sucess response to the memory controller (listed below). 
+*  If mem_array can't find the data to be removed, it will relay the remove command to the next mem_array script.
+*  If the command trickles down to the last link in the chain without finding the data to be removed
+*  an error response will be sent back to the memory controller script
+*
+*  RESPONSE:REMOVED ROW|COLUMN0:Fire Centaur|COLUMN1:other data 
+*
+*  or:
+*
+*  RESPONSE:NOT FOUND, REMOVE CANCELED|COLUMN0:Fire Centaur     
+*
+*  --------------------------------------------------------------------------------------------------  
+*  LISTING rows
+*  --------------------------------------------------------------------------------------------------
+*  For testing purposes, I added a LIST command that will simply print out all rows stored.
+* 
+*  llMessageLinked(LINK_SET,0,"COMMAND:LIST", NULL_KEY);
+*
+*  WHAT HAPPENS:
+*  "mem_array" (the first script) would receive the command, and simply iterate through the lists llSaying each data field
+*  When complete, the LIST command is sent to the next mem_array
+*
+*  --------------------------------------------------------------------------------------------------  
+*  COUNTING DATA ELEMENTS
+*  --------------------------------------------------------------------------------------------------
+*  To remove a retrieve a count of all the data elements stored in the mem_arrays, simply issue the following:
+* 
+*  llMessageLinked(LINK_SET,0,"COMMAND:COUNT|COLUMN0:0", NULL_KEY);
+*
+*  WHAT HAPPENS:
+*  "mem_array" (the first script) would receive the command, count the number of elements in column0
+*  and then relay this total to the next mem_array
+*  The next mem_array would add the amount sent to it's count, and relay the message to the next.
+*  This continues until the last link in the chain is reached, and then the grand total is sent back to the memory controller
+*
+*  RESPONSE:COUNT|number of rows stored 
+*  
+**********************************************************************************************/
+
+integer MIN_MEMORY_LIMIT=2000;
+integer COLUMN0=1; //a constant used in llList2String commands to make code more readable
+integer COLUMN1=2; //a constant used in llList2String commands to make code more readable 
+integer COMMAND=0; //a constant used in llList2String commands to make code more readable
+integer DATA0;//a constant used in llList2String commands to make code more readable
+integer DATA1;//a constant used in llList2String commands to make code more readable
+integer SEARCHITEM0=1; //a constant used in llList2String commands to make code more readable
+string searchItem0; //local var used when searching through columns
+integer count;     //used to get the cummulative total of all elements stored in the mem_arrays
+integer prevTotal; //used to get the cummulative total of all elements stored in the mem_arrays
+integer true=1;    //a constant used to make the code more readable
+integer false=-1;  //a constant used to make the code more readable
+integer MEMORY_CONTROLLER=-1; //MEMORY CONTROLLER USES 0 FOR link_num for linked messages
+integer myLinkNum; //This is used to identify the script
+list column0;      //this can be seen as column0 of a table row, to add more data elements, add column2,3,4 throughout the code
+list column1;       //this can be seen as column1 of a table row, to add more data elements, add column2,3,4 throughout the code
+string data;      //temporary vars
+string data0;      //temporary var, represents a single data field value in column0
+string data1;     //temporary vars, represents a single data field value in column1
+integer LAST_LINK_IN_MEM_ARRAY=-1; //switch var, which indicates if this is the last link in the chain.  Determined by script name
+list    mem_array_script_list; //used to count how many mem_array's exist in the prim
+integer s; //local var used in count_mem_array_scripts function to determin number of scripts in the prim
+integer numArrays; //local var used in count_mem_array_scripts function to determin number of scripts in the prim
+list message;  //local var used in linked_message events
+integer foundIndex;//local var used to search lists
+integer i; //local var used for for loops
+/***********************************************
+*  clean()
+*  Is used so that sending of linked messages is more readable by humans.  Ie: instead of sending a linked message as
+*  GETDATA|50091bcd-d86d-3749-c8a2-055842b33484 
+*  Context is added instead: COMMAND:GETDATA|PLAYERUUID:50091bcd-d86d-3749-c8a2-055842b33484
+*  By adding a context to the messages, the programmer can understand whats going on when debugging
+*  All this function does is strip off the text before the ":" char 
+***********************************************/
+string clean(string cmd){     
+     return llList2String(llParseString2List(cmd, [":"],[]),1);
+}
+
+/***********************************************
+*  count_mem_array_scripts()
+* 
+*  This function will count the number of mem_array scripts that exist, 
+*  and will check if this is the last mem_array script in the chain
+*
+***********************************************/
+integer count_mem_array_scripts()
+{
+    mem_array_script_list = [];
+    s = llGetInventoryNumber(INVENTORY_SCRIPT);
+    numArrays=0;     
+    while(s){
+        string scriptName = llGetSubString(llGetInventoryName(INVENTORY_SCRIPT, --s),0,8);        
+        if (scriptName=="mem_array") 
+            numArrays++;
+     
+    }
+    
+    if (myLinkNum == (numArrays-1))  
+        LAST_LINK_IN_MEM_ARRAY = 1; 
+        else LAST_LINK_IN_MEM_ARRAY = -1; 
+    return LAST_LINK_IN_MEM_ARRAY;
+}
+default
+{
+    state_entry()
+    {
+
+        llSetTextureAnim( ANIM_ON|LOOP|SMOOTH, ALL_SIDES, 1, 1, 0, 1, -0.05 );
+        myLinkNum = (integer)llGetSubString(llGetScriptName(), 9, -1);   
+        llSay(0,(string)llGetFreeMemory());  
+        count_mem_array_scripts();
+    }
+    /***********************************************
+    *  link_message
+    *  
+    *  SOURCES:
+    *  Messages come from memory_controller, or other mem_array
+    * 
+    *  MESSAGES:
+    *  COMMAND:GETUUID|SEARCHITEM0:someuuid        
+    ***********************************************/
+    link_message(integer sender_num, integer mem_array_link, string str, key id) {
+        
+       list message=llParseString2List(str, ["|"],[]);
+     
+        /*
+        * This script will only respond to this link_message event if the link_message is for us. This is determinted by looking at mem_array_link var, and comparing
+        * that with our script name.  If mem_array_link is the same as our script number, ie: if this is mem_array 1, then
+        * our script number is 1 
+        */
+        if (mem_array_link==myLinkNum){
+            /*
+            *     "GETPLAYER" is a search message sent from the MEMORY CONTROLLER
+            *   The message sent from the memory controller looks like this:
+            *   COMMAND:GETDATA|SEARCHITEM0:some data
+            */
+            if (clean(llList2String(message,COMMAND)) == "GETDATA")
+            {
+                
+                searchItem0=clean(llList2String(message,SEARCHITEM0)); //retrieve searchItem0 from the linked message             
+                foundIndex= llListFindList(column0,[searchItem0]); //search through column0 for searchItem0
+                //FOUND: If the SEARCHITEM0 was found, then we can send back data                
+                if (foundIndex != -1) 
+                {
+                    //This mem_array contains several lists.  Each list pertains to a particular field of data
+                    //in our case we are only storing two fields of data per row.
+                    //[COLUMN0][COLUMN1].  Therefore, index 0 of column0, and scoreboardUuidList represent one row of data
+                    //In order to hold more field values, simply add another list to this script. Example:
+                    //We could add another list called: playerPoints, then, a typical row of data would be:
+                    //[COLUMN0][COLUMN1][COLUMN2] 
+                    //We will now return COLUMN0, and COLUMN1 to the Memory_controller                                       
+                    llMessageLinked(LINK_SET, MEMORY_CONTROLLER, "COMMAND:FOUND SEARCHITEM0|COLUMN0:"+llList2String(column0,foundIndex)+"|COLUMN1:"+llList2String(column1,foundIndex), NULL_KEY);
+                    
+                }else { //NOT FOUND
+                    /*
+                    * The SEARCHITEM0 is NOT held in this mem_array. Therefore, we must tell the next mem_array in the chain to
+                    * search for the SEARCHITEM0.  If this is the last mem_array in the chain, then we must send a NOT FOUND message back
+                    * to the MEMORY_CONTROLLER
+                    */
+                    if (LAST_LINK_IN_MEM_ARRAY==true)
+                         llMessageLinked(LINK_SET, MEMORY_CONTROLLER, "RESPONSE:NOTFOUND|COLUMN0:"+searchItem0, NULL_KEY);
+                    else
+                        //If we aren't the last link of the mem_arrays, we need to tell the next mem_array to perform a search
+                        llMessageLinked(LINK_SET, myLinkNum+1, "COMMAND:GETDATA|COLUMN0:"+searchItem0, NULL_KEY);
+                    
+                    
+                }
+            }
+            /*
+            *     "INSERT" is a request sent from the MEMORY CONTROLLER to store data
+            *
+            *   The message sent from the memory controller looks like this:
+            *   COMMAND:INSERT|DATA0:some data|DATA1:some data
+            */
+            else if (clean(llList2String(message,COMMAND))=="INSERT"){
+                data0=clean(llList2String(message,1));
+                data1=clean(llList2String(message,2));
+                data="COLUMN0:"+data0+"|COLUMN1:"+data1;
+                
+                if (llGetFreeMemory()> MIN_MEMORY_LIMIT){
+                    
+                    column0+=data0;
+                    column1+=data1;
+                    llMessageLinked(LINK_SET, MEMORY_CONTROLLER, "RESPONSE:INSERT COMPLETE|"+data, NULL_KEY);
+                    llSay(0,llGetScriptName()+" inserted data");
+                }else{                     
+                    //the memory is full, so we need to pass insert command to the next mem_array, but if we are the last mem_array, we need to report an error
+                     if (LAST_LINK_IN_MEM_ARRAY==true){
+                         llRemoteLoadScriptPin(llGetKey(), "mem_array",5577, TRUE, 0);
+                         //llOwnerSay(llGetScriptName()+":LAST mem_array ********* mem error "+str);
+                         llMessageLinked(LINK_SET, MEMORY_CONTROLLER, "RESPONSE:MEMORY ERROR|"+data, NULL_KEY);
+                    }else{
+                        //llOwnerSay(llGetScriptName()+":********* REALYING INSERT "+str);
+                        //If we aren't the last link of the mem_arrays, we need to tell the next mem_array to perform a search and destroy
+                        //MESSAGE FORMAT: COMMAND:INSERT|DATA0:some data
+                        llMessageLinked(LINK_SET, myLinkNum+1, "COMMAND:INSERT|"+data, NULL_KEY);
+                    }                     
+                }
+            }
+            /*
+            *     "REMOVE" is a request sent from the MEMORY CONTROLLER to REMOVE a row data
+            *
+            *   The message sent from the memory controller looks like this:
+            *   COMMAND:REMOVE|DATA0:some data
+            */
+            else if (clean(llList2String(message,COMMAND))=="REMOVE"){     
+                llOwnerSay(llGetScriptName()+":in REMOVE "+str);                       
+                searchItem0=clean(llList2String(message,SEARCHITEM0)); //retrieve searchItem0 from the linked message
+                foundIndex= llListFindList(column0,[searchItem0]); //search through column0 for searchItem0               
+                //FOUND: If the SEARCHITEM0 was found, then delete it from this mem_array           
+                if (foundIndex != -1) 
+                {
+                    
+                    llMessageLinked(LINK_SET, MEMORY_CONTROLLER, "RESPONSE:REMOVED ROW|COLUMN0:"+llList2String(column0,foundIndex)+"|COLUMN1:"+llList2String(column1,foundIndex), NULL_KEY);
+                    column0 = llDeleteSubList(column0, foundIndex, foundIndex);
+                    column1 = llDeleteSubList(column1, foundIndex, foundIndex);
+                }else { //NOT FOUND
+                    /*
+                    * The SEARCHITEM0 is NOT held in this mem_array. Therefore, we must tell the next mem_array in the chain to
+                    * search for the SEARCHITEM0 and if found delete it. 
+                    *  If this is the last mem_array in the chain, then we must send a NOT FOUND message back
+                    *  to the MEMORY_CONTROLLER
+                    */
+                    if (LAST_LINK_IN_MEM_ARRAY==true){
+                         llMessageLinked(LINK_SET, MEMORY_CONTROLLER, "RESPONSE:NOT FOUND, REMOVE CANCELED|COLUMN0:"+searchItem0, NULL_KEY);
+                    }else{
+                        //If we aren't the last link of the mem_arrays, we need to tell the next mem_array to perform a search and destroy
+                        //MESSAGE FORMAT: COMMAND:REMOVE|DATA0:some data
+                        llMessageLinked(LINK_SET, myLinkNum+1, "COMMAND:REMOVE|COLUMN0:"+searchItem0, NULL_KEY);
+                    } 
+                    
+                }
+            }
+            /*
+            *     "LIST" is a request sent from the MEMORY CONTROLLER to LIST all data row data
+            *
+            *   The message sent from the memory controller looks like this:
+            *   COMMAND:LIST
+            */
+            else if (clean(llList2String(message,COMMAND))=="LIST"){
+                for (i=0;i<llGetListLength(column0);i++)
+                    llOwnerSay(llGetScriptName()+" "+(string)i+": "+llList2String(column0, i)+", "+llList2String(column1, i));
+                
+                llMessageLinked(ALL_SIDES, myLinkNum+1, "COMMAND:LIST", NULL_KEY);
+            } 
+            /*
+            *     "COUNT" is a message sent from the MEMORY CONTROLLER. It will go through each mem array and count
+            *   how many values are currently stored
+            *   The message sent from the memory controller looks like this:
+            *   COMMAND:COUNT
+            *   After counting, it will relay the count to the next mem_array, until the last mem_array is visited.
+            *   The cummulative total will be sent back to the memory controller
+            */
+            if (clean(llList2String(message,COMMAND)) == "COUNT"){
+                count = llGetListLength(column0);
+                prevTotal = (integer)clean(llList2String(message,COLUMN0));
+                count+= prevTotal; 
+                 if (LAST_LINK_IN_MEM_ARRAY==true){
+                         llMessageLinked(LINK_SET, MEMORY_CONTROLLER, "RESPONSE:COUNT|COLUMN0:"+(string)count, NULL_KEY);
+                    }else{
+                        //If we aren't the last link of the mem_arrays, we need to tell the next mem_array to perform a search and destroy
+                        //MESSAGE FORMAT: COMMAND:REMOVE|DATA0:some data
+                        llMessageLinked(LINK_SET, myLinkNum+1, "COMMAND:COUNT|COLUMN0:"+(string)count, NULL_KEY);
+                    } 
+            }
+
+        }
+}
+    
+    /***********************************************
+    *  changed event
+    *  
+    *  Every time the inventory changes, we must count the number of mem_array scripts to determin if this is the last
+    *  mem_array in the chain.  This is important because, if we ARE the last mem_array script, we are responsible for
+    *  sending NOT FOUND messages back to the memory controller if searches through the entire chain of mem_array scripts fail.
+    *        
+    ***********************************************/
+    changed(integer change) {
+     if (change ==CHANGED_INVENTORY){         
+         count_mem_array_scripts();
+     }
+    }
+}
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: assets/misc/mem_array.lslp 

+ 132 - 0
assets/misc/memory_controller.lslp

@@ -0,0 +1,132 @@
+//
+// The line above should be left blank to avoid script errors in OpenSim.
+
+/**********************************************************************************************
+*  memory_controller.lsl
+*  Copyright (c) 2009 Paul Preibisch
+*  Released under the GNU GPL 3.0
+*  This script can be used in your scripts, but you must include this copyright header as per the GPL Licence
+*  For more information about GPL 3.0 - see: http://www.gnu.org/copyleft/gpl.html
+
+*  Part of the SLOODLE Project see http://sloodle.org
+*
+*  Contributors:
+*  Paul G. Preibisch (Fire Centaur in SL)
+*  fire@b3dMultiTech.com  
+*
+* INTENT
+*  This script was created so that we can add a gaming element to the SLOODLE Educational Project
+*  See http://sloodle.org 
+*
+*  This script is an example of how to issue commands to store data in mem_array scripts.  See the documentation
+*  in the mem_array scripts for more information
+*
+*  I hope this script is useful to you, and encourages you to make some exciting new scripts and help us all
+*  expand the virtual world - and maybe even make a few educational games!
+*
+*  Sincerely, Paul Preibisch
+*/
+integer DATA0; //constant to make code more readible.  Used in llList2String commands to retrieve a data field in data0 
+integer DATA1; //constant to make code more readible.  Used in llList2String commands to retrieve a data field in column1 
+integer MEMORY_CONTROLLER=-1; //all messages to the MEMORY CONTROLLER should be sent on -1 FOR link_num in linked messages
+string command; //local var used to determin which command was sent
+integer i; //local var used in for loops
+string data0; //temporary var, represents a single data field value in column0
+string data1; //temporary var, represents a single data field value in column1
+string response; //local var to determine response received from mem_arrays
+string data; //local var used in linked messages
+list commandList; //local var used in linked messages
+/***********************************************
+*  clean()
+*  Is used so that sending of linked messages is more readable by humans.  Ie: instead of sending a linked message as
+*  PLAYSOUND|50091bcd-d86d-3749-c8a2-055842b33484|soundPlayer 3|0.8  we send it instead like this:
+*  COMMAND:PLAYSOUND|SOUND_UUID:50091bcd-d86d-3749-c8a2-055842b33484|SCRIPT_NAME:soundPlayer 3|VOLUME:0.8
+*  By adding a context to the messages, the programmer can understand whats going on when debugging
+*  All this function does is strip off the text before the ":" char 
+***********************************************/
+string clean(string cmd){     
+     return llList2String(llParseString2List(cmd, [":"],[]),1);
+}
+default {
+    state_entry() {
+        llListen(0, "",llGetOwner(),"");    
+    }
+    listen(integer channel, string name, key id, string message) {
+        llOwnerSay("Command Received");
+        commandList = llParseString2List(message,[" "],[]);
+        string command = llList2String(commandList, 0);
+        data0=llList2String(commandList,1);
+        data1=llList2String(commandList,2);
+        data="data0:"+data0+"|data1:"+data1;
+        if (command=="i"){//insert
+            llOwnerSay("Sending insert command: "+data);
+            llMessageLinked(LINK_SET,0,"COMMAND:INSERT|"+data, NULL_KEY);
+        }
+        else if (command=="r"){//remove
+            llMessageLinked(LINK_SET,0,"COMMAND:REMOVE|data0:"+data0, NULL_KEY);
+            llOwnerSay("Sending remove command: "+data);
+            }
+        else if (command=="l"){//list            
+            llMessageLinked(LINK_SET,0,"COMMAND:LIST", NULL_KEY);
+            llOwnerSay("Sending list command: "+data);
+        }
+        else if (command=="s"){//search
+            llMessageLinked(LINK_SET,0,"COMMAND:GETDATA|data0:"+data0, NULL_KEY);
+            llOwnerSay("Sending search command: "+data);
+        }
+        else if (command=="test"){//test
+             llOwnerSay("Sending test command: "+data);
+            for (i=0;i<1000;i++){
+                llMessageLinked(LINK_SET,0,"COMMAND:INSERT|data0:"+(string)llFrand(1000)+"|data1:"+(string)llFrand(1000), NULL_KEY);
+                
+            }
+        }
+        else if (command=="c"){//count
+             llOwnerSay("Sending count command: "+data);           
+                llMessageLinked(LINK_SET,0,"COMMAND:COUNT|data0:0", NULL_KEY);                
+            }
+        
+        
+    }
+
+
+    /***********************************************
+    *  link_message
+    *  
+    *  SOURCES:
+    *  Messages come from mem_array
+    * 
+    *  MESSAGES:
+    *  RESPONSE:FOUND SEARCHITEM0|data0:some data|data1:somedata     //this is a response from  COMMAND:GETDATA|data0:somedata
+    *  RESPONSE:NOTFOUND|data0:"+searchItem0                              //this is a response from  COMMAND:GETDATA|data0:somedata
+    *  RESPONSE:INSERT COMPLETE|data0:somedata|data1:somedata        //this is a response from  COMMAND:INSERT|data0:somedata|data1:somedata
+    *  RESPONSE:REMOVED ROW|data0:somedata|data1:somedata            //this is a response from  COMMAND:REMOVE|data0:somedata
+    *  RESPONSE:NOT FOUND, REMOVE CANCELED|data0:somedata                //this is a response from  COMMAND:REMOVE|data0:somedata
+    *  RESPONSE:COUNT|number of rows stored                                //this is a response from COMMAND:COUNT|0 sent to retrieve number of rows 
+    ***********************************************/
+    
+    link_message(integer sender_num, integer mem_array_channel, string str, key id) {
+        if (mem_array_channel==MEMORY_CONTROLLER){
+            commandList = llParseString2List(str,["|"],[]);
+            response = clean(llList2String(commandList,0));
+            data0 = clean(llList2String(commandList,1));
+            data1 = clean(llList2String(commandList,2));
+            if (response=="FOUND SEARCHITEM0"){
+                llSay(0,"Found: " + data0+", "+data1);
+            }else if (response=="NOTFOUND"){
+                llSay(0,"Not found: " + data0);
+            }else if (response=="INSERT COMPLETE"){
+               // llSay(0,"Insert complete: " + data0+", "+data1);
+            }else if (response=="REMOVED ROW"){
+                llSay(0,"Removed Row: " + data0+", "+data1);
+            }else if (response=="NOT FOUND, REMOVE CANCELED"){
+                llSay(0,"Error - remove canceled. Row not found: " + data0);
+            }else if (response=="COUNT"){
+                llSay(0,"Number of rows: " + data0);
+            }
+            llRemoteLoadScriptPin(llGetKey(), "mem_array",5577, TRUE, 0);
+        }
+    }
+}
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: assets/misc/memory_controller.lslp 

+ 165 - 0
assets/misc/skeleton_function.lslp

@@ -0,0 +1,165 @@
+//
+// The line above should be left blank to avoid script errors in OpenSim.
+
+/*
+*  Part of the Sloodle project (www.sloodle.org)
+*
+*  Copyright (c) 2011-06 contributors (see below)
+*  Released under the GNU GPL v3
+*  -------------------------------------------
+*
+*  This program is free software: you can redistribute it and/or modify
+*  it under the terms of the GNU General Public License as published by
+*  the Free Software Foundation, either version 3 of the License, or
+*  (at your option) any later version.
+*
+*
+*  This program is distributed in the hope that it will be useful,
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*  GNU General Public License for more details.
+*  You should have received a copy of the GNU General Public License
+*  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*
+*  All scripts must maintain this copyrite information, including the contributer information listed
+*
+*  Contributors:
+*  Paul Preibisch
+*  Edmund Edgar
+*
+*  DESCRIPTION
+*
+*/
+
+integer SLOODLE_CHANNEL_OBJECT_DIALOG = -3857343; // an arbitrary channel the sloodle scripts will use to talk to each other. Doesn't atter what it is, as long as the same thing is set in the sloodle_slave script. 
+string sloodleserverroot = "";
+integer sloodlecontrollerid = 0;
+string sloodlepwd = "";
+integer sloodlemoduleid = 0;
+integer sloodleobjectaccessleveluse = 0; // Who can use this object?
+integer sloodleobjectaccesslevelctrl = 0; // Who can control this object?
+integer sloodleserveraccesslevel = 0; // Who can use the server resource? (Value passed straight back to Moodle)
+integer isconfigured = FALSE; // Do we have all the configuration data we need?
+string SLOODLE_EOF = "sloodleeof";
+integer eof = FALSE; // Have we reached the end of the configuration data?
+integer SLOODLE_CHANNEL_QUIZ_MASTER_REQUEST= -1639277006;
+integer SLOODLE_CHANNEL_USER_TOUCH = -1639277002;//user touched object
+integer SLOODLE_CHANNEL_QUIZ_MASTER_RESPONSE= -1639277008;
+integer SLOODLE_CHANNEL_QUIZ_LOADING_QUIZ = -1639271109;
+integer SLOODLE_CHANNEL_TRANSLATION_REQUEST = -1928374651;
+integer SLOODLE_CHANNEL_ERROR_TRANSLATION_REQUEST=-1828374651;
+string SLOODLE_TRANSLATE_HOVER_TEXT = "hovertext";          // 2 output parameters: colour <r,g,b>, and alpha value
+string SLOODLE_TRANSLATE_WHISPER = "whisper";               // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_SAY = "say";               // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_OWNER_SAY = "ownersay";    // No output parameters
+string SLOODLE_TRANSLATE_DIALOG = "dialog";         // Recipient avatar should be identified in link message keyval. At least 2 output parameters: first the channel number for the dialog, and then 1 to 12 button label strings.
+string SLOODLE_TRANSLATE_LOAD_URL = "loadurl";      // Recipient avatar should be identified in link message keyval. 1 output parameter giving URL to load.
+string SLOODLE_TRANSLATE_IM = "instantmessage";     // Recipient avatar should be identified in link message keyval. No output parameters.
+integer SLOODLE_OBJECT_ACCESS_LEVEL_PUBLIC = 0;
+integer SLOODLE_OBJECT_ACCESS_LEVEL_OWNER = 1;
+integer SLOODLE_OBJECT_ACCESS_LEVEL_GROUP = 2;
+vector RED =<1.00000, 0.00000, 0.00000>;
+vector ORANGE=<1.00000, 0.43763, 0.02414>;
+vector YELLOW=<1.00000, 1.00000, 0.00000>;
+vector GREEN=<0.00000, 1.00000, 0.00000>;
+vector BLUE=<0.00000, 0.00000, 1.00000>;
+vector BABYBLUE=<0.00000, 1.00000, 1.00000>; 
+vector PINK=<1.00000, 0.00000, 1.00000>;
+vector PURPLE=<0.57338, 0.25486, 1.00000>;
+vector BLACK= <0.00000, 0.00000, 0.00000>;
+vector WHITE= <1.00000, 1.00000, 1.00000>;
+vector AVCLASSBLUE= <0.06274,0.247058,0.35294>;
+vector AVCLASSLIGHTBLUG=<0.8549,0.9372,0.9686>;//#daeff7
+sloodle_error_code(string method, key avuuid,integer statuscode, string msg){
+    llMessageLinked(LINK_SET, SLOODLE_CHANNEL_ERROR_TRANSLATION_REQUEST, method+"|"+(string)avuuid+"|"+(string)statuscode+"|"+(string)msg, NULL_KEY);
+}   
+sloodle_debug(string msg){
+    llMessageLinked(LINK_THIS, DEBUG_CHANNEL, msg, NULL_KEY);
+}  
+// Send a translation request link message
+sloodle_translation_request(string output_method, list output_params, string string_name, list string_params, key keyval, string batch){
+    llMessageLinked(LINK_THIS, SLOODLE_CHANNEL_TRANSLATION_REQUEST, output_method + "|" + llList2CSV(output_params) + "|" + string_name + "|" + llList2CSV(string_params) + "|" + batch, keyval);
+}        
+// Configure by receiving a linked message from another script in the object
+// Returns TRUE if the object has all the data it needs
+ integer sloodle_handle_command(string str){
+            list bits = llParseString2List(str,["|"],[]);
+            integer numbits = llGetListLength(bits);
+            string name = llList2String(bits,0);
+            string value1 = "";
+            string value2 = "";
+            if (numbits > 1) value1 = llList2String(bits,1);
+            if (numbits > 2) value2 = llList2String(bits,2);
+            if (name == "set:sloodleserverroot") sloodleserverroot = value1;
+            else if (name == "set:sloodlepwd") {
+                // The password may be a single prim password, or a UUID and a password
+                if (value2 != "") {
+                   sloodlepwd = value1 + "|" + value2;
+                }
+                else {
+                    sloodlepwd = value1;
+                }
+            }
+            else if (name == "set:sloodlecontrollerid") sloodlecontrollerid = (integer)value1;
+            else if (name == "set:sloodlemoduleid") sloodlemoduleid = (integer)value1;
+            else if (name == "set:sloodleobjectaccessleveluse") sloodleobjectaccessleveluse = (integer)value1;
+            else if (name == "set:sloodleserveraccesslevel") sloodleserveraccesslevel = (integer)value1;
+            else if (name == SLOODLE_EOF) eof = TRUE;
+            
+            return (sloodleserverroot != "" && sloodlepwd != "" && sloodlecontrollerid > 0 && sloodlemoduleid > 0);
+ }
+ debug (string message ){
+     list params = llGetPrimitiveParams ([PRIM_MATERIAL ]);
+     if (llList2Integer (params ,0)==PRIM_MATERIAL_FLESH){
+           llOwnerSay("memory: "+(string)llGetFreeMemory()+" Script name: "+llGetScriptName ()+": " +message );
+     }
+} 
+default {
+    on_rez(integer start_param) {
+        llResetScript();
+    }
+       state_entry(){
+        isconfigured = FALSE;
+        eof = FALSE;
+    }
+    link_message( integer sender_num, integer chan, string str, key user_key){
+        // Check the channel for configuration messages
+        if (chan == SLOODLE_CHANNEL_OBJECT_DIALOG) {
+            // Split the message into lines
+            list lines = llParseString2List(str, ["\n"], []);
+            integer numlines = llGetListLength(lines);
+            integer i = 0;
+            for (i=0; i < numlines; i++) {
+                isconfigured = sloodle_handle_command(llList2String(lines, i));
+            }
+            // If we've got all our data AND reached the end of the configuration data (eof), then move on
+            if (eof == TRUE) {
+                if (isconfigured == TRUE) {
+                    sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "configurationreceived", [], NULL_KEY, "");
+                    state ready;
+                } else {
+                    // Go all configuration but, it's not complete... request reconfiguration
+                    sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "configdatamissing", [llGetScriptName()], NULL_KEY, "");
+                    llMessageLinked(LINK_THIS, SLOODLE_CHANNEL_OBJECT_DIALOG, "do:reconfigure", NULL_KEY);
+                    eof = FALSE;
+                }
+            }
+        }
+    }
+}
+state ready{
+	on_rez(integer start_param) {
+		llResetScript();
+	}
+	state_entry() {
+	
+	}
+	touch_start(integer n) {
+	
+	}
+	link_message(integer sender_num, integer chan, string str, key id) {
+	
+	}
+	
+
+}

+ 268 - 0
assets/misc/sloodle_error_message_handler.lslp

@@ -0,0 +1,268 @@
+//
+// The line above should be left blank to avoid script errors in OpenSim.
+
+/**********************************************************************************************
+*  sloodle_error_message_handler.lsl
+*  Copyright (c) 2009 Paul Preibisch
+*  Released under the GNU GPL 3.0
+*  This script can be used in your scripts, but you must include this copyright header 
+*  as per the GPL Licence
+*  For more information about GPL 3.0 - see: http://www.gnu.org/copyleft/gpl.html
+* 
+*  This script is intended to translate error messages sent via linked messages from sloodle scripts.
+*  All Sloodle Status codes from our wiki at: http://slisweb.sjsu.edu/sl/index.php/Sloodle_status_codes 
+*  have been added to an LSL list.  I have also added standard HTTP error status's so we can give more
+*  detailed information to our lovely sloodlers during the confuration stage of Sloodle.
+*
+*  USE:  To use this script, simply include our premade function "sloodle_error_code" in your script
+*  sloodle_error_code(string method, key avuuid,integer statuscode){
+*       llMessageLinked(LINK_SET, SLOODLE_CHANNEL_ERROR_TRANSLATION_REQUEST, method+"|"+(string)avuuid+"|
+*       +(string)statuscode, NULL_KEY);
+*   }
+*  
+*  Then in your code, use it like: sloodle_error_code(SLOODLE_TRANSLATE_SAY, NULL_KEY,statuscode); 
+*
+*  All messages should be sent on this channel: 
+*  integer SLOODLE_CHANNEL_ERROR_TRANSLATION_REQUEST=-1828374651;
+*
+*  This script is part of the SLOODLE Project see http://sloodle.org
+*
+*  Copyright
+*  Paul G. Preibisch (Fire Centaur in SL) fire@b3dMultiTech.com  
+*  Edmund Edgar
+/**********************************************************************************************/
+integer SLOODLE_CHANNEL_ERROR_TRANSLATION_REQUEST=-1828374651;
+list errorMessages;
+
+// Translation output methods
+string SLOODLE_TRANSLATE_LINK = "link";                     // No output parameters - simply returns the translation on SLOODLE_TRANSLATION_RESPONSE link message channel
+string SLOODLE_TRANSLATE_SAY = "say";                       // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_WHISPER = "whisper";               // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_SHOUT = "shout";                   // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_REGION_SAY = "regionsay";          // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_OWNER_SAY = "ownersay";            // No output parameters
+string SLOODLE_TRANSLATE_DIALOG = "dialog";                 // Recipient avatar should be identified in link message keyval. At least 2 output parameters: first the channel number for the dialog, and then 1 to 12 button label strings.
+string SLOODLE_TRANSLATE_LOAD_URL = "loadurl";              // Recipient avatar should be identified in link message keyval. 1 output parameter giving URL to load.
+string SLOODLE_TRANSLATE_LOAD_URL_PARALLEL = "loadurlpar";  // Recipient avatar should be identified in link message keyval. 1 output parameter giving URL to load.
+string SLOODLE_TRANSLATE_HOVER_TEXT = "hovertext";          // 2 output parameters: colour <r,g,b>, and alpha value
+string SLOODLE_TRANSLATE_IM = "instantmessage";             // Recipient avatar should be identified in link message keyval. No output parameters.
+
+string sloodle_get_error_string(integer statusCode){
+    list found =fncFindStride(errorMessages, [statusCode],3);
+    integer index = llList2Integer(found,0);
+    list values;    
+    string trans;
+    if (llList2Integer(found,0)!=-1){
+         values = fncGetStride(errorMessages, index, 3);    //will return a stride of len 3 - ie:  [-213,"error message"]
+         trans = llList2String(values,1);
+    }
+    return trans; 
+}
+
+/****************************************************************
+* integer fncStrideCount(list source, integer stride)
+*
+* Returns the number of strides found within a list.
+* Source is the list to operate on, and stride is the length of each stride within the list.
+* list Source = ["A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", "C3"];
+* integer Result = fncStrideCount(Source, 3);
+* Returns 3
+* Returns number of Strides in a List
+*********************************************************************/
+integer fncStrideCount(list lstSource, integer intStride)
+{
+  return llGetListLength(lstSource) / intStride;
+}
+
+/*********************************************************************
+* list fncGetStride(list source, integer index, integer stride)
+* Returns a single stride from the list.
+* Source is the list to retrieve from, index is which stride to return (zero indexed), and stride is the length of each stride.
+* list Source = ["A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", "C3"];
+* 
+* list Result = fncGetStride(Source, 1, 3);
+* 
+*  Returns ["B1", "B2", "B3"]
+*  On failure returns an empty list
+**********************************************************************/
+list fncGetStride(list lstSource, integer intIndex, integer intStride)
+{
+  integer intNumStrides = fncStrideCount(lstSource, intStride);
+  
+  if (intNumStrides != 0 && intIndex < intNumStrides)
+  {
+    integer intOffset = intIndex * intStride;
+    return llList2List(lstSource, intOffset, intOffset + (intStride - 1));
+  }
+  return [];
+}
+/****************************************************************
+* list fncFindStride(list source, list item, integer stride)
+*
+* Returns the stride number and element number of an element within a stride, within a list. (zero indexed)
+* Source is the list to search, item is a single element list containing the item to search for, and stride is the length of each stride.
+* list Source = ["A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", "C3"];
+* list Item = ["B3"];
+* 
+* list Result = fncFindStride(Source, Item, 3);
+* Returns [1, 2].
+* The first number is which stride Item is in.
+* The second is which element of the stride is Item.
+* 
+* If item is not found, it will return [-1, -1]
+*********************************************************************/
+list fncFindStride(list lstSource, list lstItem, integer intStride)
+ {
+     integer intListIndex = llListFindList(lstSource, lstItem);
+   
+   if (intListIndex == -1) { return [-1, -1]; }
+   
+   integer intStrideIndex = intListIndex / intStride;
+   integer intSubIndex = intListIndex % intStride;
+   
+  return [intStrideIndex, intSubIndex];
+}
+default {
+    on_rez(integer start_param) {
+        llResetScript();
+    }
+    state_entry() {
+        errorMessages=[-1 ,"An unspecified error occured","MISC"];
+        errorMessages+=[-101,"An unknown system error occured","SYSTEM"];
+        errorMessages+=[-102,"Could not connect to the database.","SYSTEM"];
+        errorMessages+=[-103,"Could not fetch data we expected to be there. ","SYSTEM"];
+        errorMessages+=[-104,"Data formatting error ","SYSTEM"];
+        errorMessages+=[-105,"XMLRPC error","SYSTEM"];
+        errorMessages+=[-106,"The Sloodle module is not installed in Moodle ","SYSTEM"];
+        errorMessages+=[-111,"System is temporarily down for maintenance. ","SYSTEM"];
+        errorMessages+=[-121,"Failed to send required email ","SYSTEM"];
+        errorMessages+=[-131,"Failed to load plugins ","PLUGIN"];
+        errorMessages+=[-132,"The required plugin was not found ","PLUGIN"];
+        errorMessages+=[-201,"There was an unspecified problem authenticating the object. ","OBJECT_AUTH"];
+        errorMessages+=[-212,"Object did not supply the necessary information to authenticate itself. ","OBJECT_AUTH"];
+        errorMessages+=[-213,"Object authentication key was invalid ","OBJECT_AUTH"];
+        errorMessages+=[-214,"Object not authorised in this context ","OBJECT_AUTH"];
+        errorMessages+=[-215,"Prim password access has been disabled for this Controller. ","OBJECT_AUTH"];
+        errorMessages+=[-216,"Object not found in database","USER_AUTH"];
+        errorMessages+=[-217,"Could not save HTTP-in URL (v2.0 etc)","OBJECT_AUTH"];
+        errorMessages+=[-218,"Active object not found while trying to save HTTP-in URL (v2.0 etc)","OBJECT_AUTH"];
+        errorMessages+=[-219,"Sending configuration to object via HTTP-in URL failed","OBJECT_AUTH"];
+        errorMessages+=[-301,"There was an unspecified problem authenticating the user. ","USER_AUTH"];
+        errorMessages+=[-311,"Object did not supply the necessary information to authenticate the user. ","USER_AUTH"];
+        errorMessages+=[-321,"User was not registered and we weren't allowed to register them automatically. ","USER_AUTH"];
+        errorMessages+=[-322,"User was not registered and we tried to register them but failed. ","USER_AUTH"];
+        errorMessages+=[-331,"User did not have permission to access the resources requested . ","USER_AUTH"];
+        errorMessages+=[-341,"Password cannot be reset via Sloodle, as user was not auto-registered (or has since registered an email account). ","USER_AUTH"];
+        errorMessages+=[-401,"There was an unspecified problem involving course enrolment. ","USER_ENROL"];
+        errorMessages+=[-421,"User was not enrolled on the course and we weren't allowed to enrol them automatically","USER_ENROL"];
+        errorMessages+=[-422,"User was not enrolled on the course and tried to enrol them but failed. ","USER_ENROL"];
+        errorMessages+=[-501,"There was an unspecified problem involving the course you ar trying to use. ","COURSE"];
+        errorMessages+=[-511,"The course you are trying to use was not specified. ","COURSE"];
+        errorMessages+=[-512,"The course you are trying to use was not found. ","COURSE"];
+        errorMessages+=[-513,"The course you are trying to use was inactive. ","COURSE"];
+        errorMessages+=[-514,"The course you are trying to use was forbidden from being used with Sloodle. ","COURSE"];
+        errorMessages+=[-601,"There was an unspecifed problem involving the module you're trying to use. ","MODULE"];
+        errorMessages+=[-611,"The module you are trying to use was not specified.","MODULE"];
+        errorMessages+=[-612,"The module you are trying to use was found. ","MODULE"];
+        errorMessages+=[-613,"The module you are trying to use was inactive. ","MODULE"];
+        errorMessages+=[-614,"The module you are trying to use was forbidden from being used by Sloodle. ","MODULE"];    
+        errorMessages+=[-701,"There was an unspecifed problem involving the module instance you're trying to use. ","MODULE_INSTANCE"];
+        errorMessages+=[-711,"The module instance you are trying to use was not specified. ","MODULE_INSTANCE"];
+        errorMessages+=[-712,"The module instance you are trying to use was not found. ","MODULE_INSTANCE"];
+        errorMessages+=[-713,"The module instance you are trying to use was inactive. ","MODULE_INSTANCE"];
+        errorMessages+=[-714,"The module instance you are trying to use was forbidden from being used by Sloodle. ","MODULE_INSTANCE"];
+        errorMessages+=[-801,"Requested script permanently deleted. ","REQUEST"];
+        errorMessages+=[-802,"Requested script moved (new location may be specified in the first data line) ","REQUEST"];
+        errorMessages+=[-811,"Request incomplete (not all required data present) ","REQUEST"];
+        errorMessages+=[-812,"Requested resource could not be found (although the request itself was handled successfully) ","REQUEST"];
+        errorMessages+=[-901,"Unknown profile error ","PROFILE"];
+        errorMessages+=[-902,"Profile does not exist ","PROFILE"];
+        errorMessages+=[-903,"Profile already exists ","PROFILE"];
+        errorMessages+=[-904,"Unknown profile command ","PROFILE"];
+        errorMessages+=[-777000,"Transaction for this user was not found","TRANSACTION"];
+        errorMessages+=[10001,"Obtained results successfully","CHOICE_QUERY"];
+        errorMessages+=[10011,"Added new choice selection for user ","CHOICE_SELECT"];
+        errorMessages+=[10012,"Updated choice selection for user ","CHOICE_SELECT"];
+        errorMessages+=[10013,"User had previously selected the same option ","CHOICE_SELECT"];
+        errorMessages+=[10101,"Successfully added a chat message into the database (commonly used as a side-effect code) ","CHAT_MESSAGE"];
+        errorMessages+=[-10001,"Unable to query choice status for unknown reason ","CHOICE_QUERY"];
+        errorMessages+=[-10011,"The user has already selected a choice, and choices cannot be updated ","CHOICE_SELECT"];
+        errorMessages+=[-10012,"Maximum number of selections for this choice already made ","CHOICE_SELECT"];
+        errorMessages+=[-10013,"Choice is not yet open for selections ","CHOICE_SELECT"];
+        errorMessages+=[-10014,"Choice has been closed ","CHOICE_SELECT"];
+        errorMessages+=[-10015,"Specified option not found in choice ","CHOICE_SELECT"];
+        errorMessages+=[-10016,"Unable to make choice selection for unknown reason (usually more data appended in data lines) ","CHOICE_SELECT"];
+        errorMessages+=[-10021,"Unable to query list of choice instances for unknown reason ","CHOICE_LIST_QUERY"];
+        errorMessages+=[-10022,"Not choice instances available in course ","CHOICE_LIST_QUERY"];
+        errorMessages+=[-10101,"Failed to add chat message to database ","CHAT_MESSAGE"];
+        errorMessages+=[-10201,"User is not authorised to submit assignments ","ASSIGNMENT"];
+        errorMessages+=[-10202,"Assignment is not yet open for submissions ","ASSIGNMENT"];
+        errorMessages+=[-10203,"Assignment due date has passed, and is closed for submissions ","ASSIGNMENT"];
+        errorMessages+=[-10204,"Assignment due date has passed, but is still accepting submissions (typically a side effect code) ","ASSIGNMENT"];
+        errorMessages+=[-10205,"Resubmissions are not permitted ","ASSIGNMENT"];
+        errorMessages+=[-10301,"Student has used all quiz attempts ","QUIZ"];
+        errorMessages+=[-10302,"Quiz requires password - this is not supported by Sloodle ","QUIZ"];
+        errorMessages+=[-10303,"      Error loading questions ","QUIZ"];        
+        errorMessages+=[-10401,"Blogging is disabled on this site. ","BLOG"];
+        errorMessages+=[-10402,"User does not have permission to write blogs ","BLOG"];
+        errorMessages+=[-10501,"There are no slides in this presentation. ","PRESENTER"];
+        errorMessages+=[301 ,"HTTP Server error (301)\"Moved Permanently\" -  The page you are trying to connect to has moved","HTTP"];
+        string errMess= "HTTP Server error (400)\"Bad Request\" -  The Web server considers the data stream ";
+        errMess+= "sent by your Second Life object to be 'malformed' i.e. it has not sent a complete HTTP request. In such cases ";
+        errMess+= "the web server is unable to process the request. Have you recently added beta code to your server?";
+        errorMessages+=[400  ,errMess,"HTTP"];
+        errMess = "HTTP Server error (401) \"Unauthorized\" -  It seams Sloodle is trying to access a password protected directory on ";
+        errMess+= "your server. In such cases you should check whether you have specified the correct folder during installation.";
+        errorMessages+=[401  ,errMess,"HTTP"];
+        errMess = "HTTP Server error (403) \"Forbidden\" -  This error message means that Sloodle is trying to open a folder or a file ";
+        errMess += "it does not have access to. If you are receiving this error message on your website, you should check the .htaccess files in your ";
+        errMess += "web hosting account for any restrictive rules.";        
+        errorMessages+=[403  ,errMess,"HTTP"];
+        errMess = "HTTP Server error (404) \"Not Found\" -  It seams Sloodle is trying to access a non-existent page or a folder. ";
+        errMess += "In such cases you should check if the link you are trying to access is correct.";
+        errorMessages+=[404  ,errMess,"HTTP"];
+        errMess ="An internal server error has occured. This is a standard HTTP 500 error, Perhaps Sloodle is trying to access code that is broken.  Have you installed any beta software recently?  Make sure you make backups before upgrading your Sloodle.  ";
+        errMess +="Error 500 is a \'catch-all\' error generated by the Web server. Basically something has gone wrong, but the server can not be more specific about the error condition in its response to Sloodle.";
+        errorMessages+=[500  ,errMess,"HTTP"];
+        errMess = "An HTTP 503 error has occured.  This is a standard HTTP error that means \"Service Unavailable.\"";
+        errMess +="It can have nothing to do with Sloodle - but instead, an essential program on your server may have crashed";
+        errMess +=" You often see HTTP 503 when a website has exceeded its monthly bandwidth allowance";
+        errMess +=" It could also mean a connection to a database has failed or is misconfigured.  Most likely, you never typed the address of your sloodle website correctly during the Sloodle configuration stage.";
+        errMess +="Click the mouse on the object you are trying to configure, and try again with the correct address.";
+        errorMessages+=[503  ,errMess,"HTTP"];
+        
+        
+    }
+    link_message(integer sender_num, integer channel, string str, key id) {
+        if (channel==SLOODLE_CHANNEL_ERROR_TRANSLATION_REQUEST){
+            //message will be in the format
+            //status code|senderuuid;
+            list fields = llParseStringKeepNulls(str, ["|"], []);
+            
+            integer numfields = llGetListLength(fields);            
+            string method= llList2String(fields,0);
+            key senderUuid = llList2Key(fields,1);
+            integer statusCode = llList2Integer(fields,2);
+            
+            //TRANSLATE status code//
+            string   trans = sloodle_get_error_string(statusCode);
+            if (trans == "") {
+                // Error code missing.
+		// In Sloodle 2, the script sending the error may send us a message to say to the user.
+		// For example, if you don't have enough points to use an object, there may be a user-defined message telling you that.
+                // If we intended to produce a message in the right language on the server side, we leave the error string undefined on purpose.
+                if (llGetListLength(fields) > 3) {
+                    trans = llList2String(fields,3);
+                }
+            }
+
+            if (trans!=""){
+                if (method == SLOODLE_TRANSLATE_SAY)    llSay(0,"Error Message: "+trans);
+                else if (method==SLOODLE_TRANSLATE_IM) llInstantMessage(senderUuid,"Error Message: " + trans);
+            }else    llSay(0,"Error Message Translation not found - please view http://slisweb.sjsu.edu/sl/index.php/Sloodle_status_codes for more information: Error Code: "+(string)statusCode);      
+        }
+    }
+}
+
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: assets/misc/sloodle_error_message_handler.lslp

+ 71 - 0
assets/misc/sloodle_multi_url_loader.lslp

@@ -0,0 +1,71 @@
+//
+// The line above should be left blank to avoid script errors in OpenSim.
+
+// Sloodle multiple URL loader
+// Manages the parallelized loading of multiple (potentially) simultaneous URLs for different users
+// (NOTE: requires copies of the "sloodle_parallel_url_loader_x.lsl" script to be in the SAME prim)
+// Part of the Sloodle project (www.sloodle.org)
+//
+// Copyright (c) 2007 Sloodle (various contributors)
+// Released under the GNU GPL
+//
+// Contributors:
+//  Peter R. Bloomfield
+//
+
+//
+// When receiving an appropriate link message, this will forward it on an appropriate channel to be handled.
+// It will then move on to the next channel, allowing a sequence to be created, whereby URL loading can be parallelized.
+// NOTE: you may need to customize the "NUM_PARALLEL_SCRIPTS" value if you have fewer parallel scripts.
+
+
+///// DATA /////
+
+
+// The maximum number of parallel scripts available (should not be bigger than list below)
+integer NUM_PARALLEL_SCRIPTS = 10;
+
+
+
+// INCOMING link message number for load requests
+integer SLOODLE_CHANNEL_OBJECT_LOAD_URL = -1639270041;
+
+// OUTGOING link message numbers for parallel load requests
+list SLOODLE_CHANNEL_LIST_LOAD_URL_x =  [
+                                        -1699000001, //SLOODLE_CHANNEL_LOAD_URL_0
+                                        -1699000002, //SLOODLE_CHANNEL_LOAD_URL_1
+                                        -1699000003, //SLOODLE_CHANNEL_LOAD_URL_2
+                                        -1699000004, //SLOODLE_CHANNEL_LOAD_URL_3
+                                        -1699000005, //SLOODLE_CHANNEL_LOAD_URL_4
+                                        -1699000006, //SLOODLE_CHANNEL_LOAD_URL_5
+                                        -1699000007, //SLOODLE_CHANNEL_LOAD_URL_6
+                                        -1699000008, //SLOODLE_CHANNEL_LOAD_URL_7
+                                        -1699000009, //SLOODLE_CHANNEL_LOAD_URL_8
+                                        -1699000010  //SLOODLE_CHANNEL_LOAD_URL_9
+                                        ];
+
+// Current channel being used (corresponds to indices of above list)
+integer current_request_channel = 0;
+
+///// ---- /////
+
+default
+{
+    link_message( integer sender_num, integer msg_num, string str, key id )
+    {
+        // Is this the correct message number and are the variables non-empty?
+        if (msg_num == SLOODLE_CHANNEL_OBJECT_LOAD_URL && str != "" && id != NULL_KEY)
+        {
+            // Send the request to a parallel script
+            llMessageLinked(LINK_THIS, llList2Integer(SLOODLE_CHANNEL_LIST_LOAD_URL_x, current_request_channel), str, id);
+            current_request_channel += 1;
+            
+            // Wrap the request channel value round if necessary
+            integer num = llGetListLength(SLOODLE_CHANNEL_LIST_LOAD_URL_x);
+            if (NUM_PARALLEL_SCRIPTS < num) num = NUM_PARALLEL_SCRIPTS;
+            current_request_channel %= num;
+        }
+    }
+}
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: assets/misc/sloodle_multi_url_loader.lslp 

+ 525 - 0
assets/misc/sloodle_setup_user.lslp

@@ -0,0 +1,525 @@
+//
+// The line above should be left blank to avoid script errors in OpenSim.
+
+// Sloodle user-centric configuration script.
+// Allows users (e.g. students) to authorise and configure user-centric objects themselves.
+// Part of the Sloodle project (www.sloodle.org)
+//
+// Copyright (c) 2008 Sloodle
+// Released under the GNU GPL
+//
+// Contributors:
+//  Peter R. Bloomfield
+
+integer SLOODLE_CHANNEL_ERROR_TRANSLATION_REQUEST=-1828374651; // this channel is used to send status codes for translation to the error_messages lsl script
+integer SLOODLE_CHANNEL_OBJECT_DIALOG = -3857343;
+string SLOODLE_EOF = "sloodleeof";
+integer SLOODLE_CHANNEL_AVATAR_DIALOG = 1001;
+
+string SLOODLE_VERSION_LINKER = "/mod/sloodle/version_linker.php";
+string SLOODLE_USER_AUTH_LINKER = "/mod/sloodle/login/user_object_linker.php";
+string SLOODLE_USER_AUTH_INTERFACE = "/mod/sloodle/login/user_object_auth.php";
+string SLOODLE_AUTH_CHECKER = "/mod/sloodle/login/check_user_auth_linker.php";
+
+float SLOODLE_VERSION_MIN = 0.3; // Minimum required version of Sloodle
+
+key httpcheckmoodle = NULL_KEY;
+key httpauthobject = NULL_KEY;
+key httpcheckauth = NULL_KEY;
+
+string sloodleserverroot = "";
+string sloodlepwd = ""; // stores the object-specific session key (UUID|pwd)
+
+string sloodleauthurl = ""; // URL which will be used to complete the authorisation process
+
+string password = ""; // stores the self-generated part of the password
+
+
+
+
+
+///// TRANSLATION /////
+
+// Link message channels
+integer SLOODLE_CHANNEL_TRANSLATION_REQUEST = -1928374651;
+integer SLOODLE_CHANNEL_TRANSLATION_RESPONSE = -1928374652;
+
+// Translation output methods
+string SLOODLE_TRANSLATE_LINK = "link";             // No output parameters - simply returns the translation on SLOODLE_TRANSLATION_RESPONSE link message channel
+string SLOODLE_TRANSLATE_SAY = "say";               // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_WHISPER = "whisper";       // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_SHOUT = "shout";           // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_REGION_SAY = "regionsay";  // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_OWNER_SAY = "ownersay";    // No output parameters
+string SLOODLE_TRANSLATE_DIALOG = "dialog";         // Recipient avatar should be identified in link message keyval. At least 2 output parameters: first the channel number for the dialog, and then 1 to 12 button label strings.
+string SLOODLE_TRANSLATE_LOAD_URL = "loadurl";      // Recipient avatar should be identified in link message keyval. 1 output parameter, containing the URL.
+string SLOODLE_TRANSLATE_HOVER_TEXT = "hovertext";  // 2 output parameters: colour <r,g,b>, and alpha value
+string SLOODLE_TRANSLATE_IM = "instantmessage";             // Recipient avatar should be identified in link message keyval. No output parameters.
+
+
+//functions
+/******************************************************************************************************************************
+* sloodle_error_code - 
+* Author: Paul Preibisch
+* Description - This function sends a linked message on the SLOODLE_CHANNEL_ERROR_TRANSLATION_REQUEST channel
+* The error_messages script hears this, translates the status code and sends an instant message to the avuuid
+* Params: method - SLOODLE_TRANSLATE_SAY, SLOODLE_TRANSLATE_IM etc
+* Params:  avuuid - this is the avatar UUID to that an instant message with the translated error code will be sent to
+* Params: status code - the status code of the error as on our wiki: http://slisweb.sjsu.edu/sl/index.php/Sloodle_status_codes
+*******************************************************************************************************************************/
+sloodle_error_code(string method, key avuuid,integer statuscode){
+            llMessageLinked(LINK_SET, SLOODLE_CHANNEL_ERROR_TRANSLATION_REQUEST, method+"|"+(string)avuuid+"|"+(string)statuscode, NULL_KEY);
+}
+
+// Send a translation request link message
+sloodle_translation_request(string output_method, list output_params, string string_name, list string_params, key keyval, string batch)
+{
+    llMessageLinked(LINK_THIS, SLOODLE_CHANNEL_TRANSLATION_REQUEST, output_method + "|" + llList2CSV(output_params) + "|" + string_name + "|" + llList2CSV(string_params) + "|" + batch, keyval);
+}
+
+///// ----------- /////
+
+
+sloodle_tell_other_scripts(string msg)
+{
+    llMessageLinked(LINK_SET, SLOODLE_CHANNEL_OBJECT_DIALOG, msg, NULL_KEY);   
+}
+
+sloodle_debug(string msg)
+{
+    llMessageLinked(LINK_SET, DEBUG_CHANNEL, msg, NULL_KEY);
+}
+
+// Generate a random password string
+string sloodle_random_object_password()
+{
+    return (string)(10000 + (integer)llFrand(999989999)); // Gets a random integer between 10000 and 999999999
+}
+
+// Load the authorisation URL
+sloodle_load_auth_url(key av)
+{
+    sloodle_translation_request(SLOODLE_TRANSLATE_LOAD_URL, [sloodleauthurl], "userauthurl", [], av, "");
+}
+
+// Initiate an authorisation check.
+// Returns the key of the HTTP request.
+key sloodle_check_user_auth()
+{
+    string body = "sloodleuuid=" + (string)llGetOwner();
+    body += "&sloodlepwd=" + sloodlepwd;
+    return llHTTPRequest(sloodleserverroot + SLOODLE_AUTH_CHECKER, [HTTP_METHOD, "POST", HTTP_MIMETYPE, "application/x-www-form-urlencoded"], body);
+}
+
+///// STATES /////
+
+default
+{    
+    state_entry()
+    {
+        sloodle_debug("Setup user in default state.");
+        // Pause for a moment, in case all scripts were reset at the same time
+        llSleep(0.2);
+        // Reset our data
+        sloodleserverroot = "";
+        sloodlepwd = "";
+        sloodleauthurl = "";
+        password = "";
+    
+        // Check to see if the server URL is in the object description
+        string desc = llGetObjectDesc();
+        if (desc != "" && llSubStringIndex(desc, "http") == 0) sloodleserverroot = desc;
+        
+        // Did we get a server root?
+        if (sloodleserverroot == "") {
+            // No - ask the owner for it
+            llListen(0, "", llGetOwner(), "");
+            llListen(1, "", llGetOwner(), "");
+            
+            sloodle_translation_request(SLOODLE_TRANSLATE_IM, [0], "chatserveraddress", [], llGetOwner(), "");
+            sloodle_translation_request(SLOODLE_TRANSLATE_HOVER_TEXT, [<0.0,1.0,0.0>, 0.8], "waitingforserveraddress", [], NULL_KEY, "");
+        } else {
+            // Immediately start processing the Moodle site
+            state check_moodle;
+        }
+    }
+    
+    state_exit()
+    {
+        llSetText("", <0.0,0.0,0.0>, 0.0);
+    }
+    
+    listen(integer channel, string name, key id, string msg)
+    {        
+        // Check the channel
+        if (channel == 0 || channel == 1) {
+            // Ignore anybody but the owner
+            if (id != llGetOwner()) return;
+            // If the message starts with "http" then store it as the Moodle address
+            msg = llStringTrim(msg, STRING_TRIM);
+            if (llSubStringIndex(msg, "http") == 0) {
+                sloodleserverroot = msg;
+                state check_moodle;
+                return;
+            }
+        }
+    }
+    
+    touch_start(integer num_detected)
+    {
+        if (llDetectedKey(0) == llGetOwner()) {
+            // Remind the owner of what is needed
+            sloodle_translation_request(SLOODLE_TRANSLATE_IM, [0], "chatserveraddress", [], llGetOwner(), "");
+        }
+    }
+    
+    link_message(integer sender_num, integer num, string sval, key kval)
+    {
+        // Check the channel
+        if (num == SLOODLE_CHANNEL_OBJECT_DIALOG) {
+            // Is it a reset command?
+            if (sval == "do:reset") {
+                llResetScript();
+                return;
+            }
+        }
+    }
+    
+    on_rez(integer start_param)
+    {
+        llResetScript();
+    }
+    
+    attach( key av )
+    {
+        if (av != NULL_KEY)
+            llResetScript();
+    }
+}
+
+// Check that the Moodle site is valid
+state check_moodle
+{
+    state_entry()
+    {
+        sloodle_debug("Checking Moodle...");
+        sloodle_translation_request(SLOODLE_TRANSLATE_HOVER_TEXT, [<0.0,1.0,0.0>, 0.8], "checkingserverat", [sloodleserverroot], NULL_KEY, "");
+        httpcheckmoodle = llHTTPRequest(sloodleserverroot + SLOODLE_VERSION_LINKER, [HTTP_METHOD, "GET"], "");
+    }
+    
+    state_exit()
+    {
+        llSetText("", <0.0,0.0,0.0>, 0.0);
+        httpcheckmoodle = NULL_KEY;
+    }
+    
+    http_response(key id, integer status, list meta, string body)
+    {
+        // Make sure it's the response we're expecting
+        if (id != httpcheckmoodle) return;
+        httpcheckmoodle = NULL_KEY;
+        // Check the status code
+        if (status != 200) {
+            sloodle_translation_request(SLOODLE_TRANSLATE_OWNER_SAY, [0], "httperror:code", [status], NULL_KEY, "");
+            return;
+        }
+        
+        // Split the response into lines and get the status line info
+        list lines = llParseStringKeepNulls(body, ["\n"], []);
+        integer numlines = llGetListLength(lines);
+        list statusfields = llParseStringKeepNulls(llList2String(lines, 0), ["|"], []);
+        integer statuscode = (integer)llList2String(statusfields, 0);
+        
+        // Make sure the status code was OK
+        if (statuscode == -106) {
+            sloodle_debug("ERROR -106: the Sloodle module is not installed on the specified Moodle site (" + sloodleserverroot + ")");
+            sloodle_translation_request(SLOODLE_TRANSLATE_OWNER_SAY, [0], "sloodlenotinstalled", [], NULL_KEY, "");
+            return;
+        } else if (statuscode <= 0) {
+            sloodle_translation_request(SLOODLE_TRANSLATE_OWNER_SAY, [0], "failedcheckcompatibility", [], NULL_KEY, "");
+            sloodle_error_code(SLOODLE_TRANSLATE_SAY, NULL_KEY,statuscode); //send message to error_message.lsl
+            return;
+        }
+        
+        // Make sure we have enough other data
+        if (numlines < 2) {
+            sloodle_translation_request(SLOODLE_TRANSLATE_OWNER_SAY, [0], "badresponseformat", [], NULL_KEY, "");
+            return;
+        }
+        
+        // Extract the Sloodle version number
+        list datafields = llParseStringKeepNulls(llList2String(lines, 1), ["|"], []);
+        float installedversion = (float)llList2String(datafields, 0);
+        
+        // Check compatibility
+        if (installedversion < SLOODLE_VERSION_MIN) {
+            sloodle_translation_request(SLOODLE_TRANSLATE_OWNER_SAY, [0], "sloodleversioninstalled", [installedversion], NULL_KEY, "");
+            sloodle_translation_request(SLOODLE_TRANSLATE_OWNER_SAY, [0], "sloodleversionrequired", [SLOODLE_VERSION_MIN], NULL_KEY, "");
+            return;
+        }
+        
+        // Initiate object authorisation
+        state auth_object;
+    }
+    
+    touch_start(integer num_detected)
+    {
+        // Revert to the default state if the owner touched
+        if (llDetectedKey(0) != llGetOwner()) return;
+        state default;
+    }
+    
+    link_message(integer sender_num, integer num, string sval, key kval)
+    {
+        // Check the channel
+        if (num == SLOODLE_CHANNEL_OBJECT_DIALOG) {
+            // Is it a reset command?
+            if (sval == "do:reset") {
+                llResetScript();
+                return;
+            }
+        }
+    }
+    
+    on_rez(integer start_param)
+    {
+        llResetScript();
+    }
+    
+    attach( key av )
+    {
+        if (av != NULL_KEY)
+            llResetScript();
+    }
+}
+
+// Object authorisation
+state auth_object
+{
+    state_entry()
+    {
+        sloodle_debug("Object authorisation...");
+        sloodle_translation_request(SLOODLE_TRANSLATE_HOVER_TEXT, [<0.0, 1.0, 0.0>, 0.8], "initobjectauth", [], NULL_KEY, "");
+        
+        // Generate a random password
+        password = sloodle_random_object_password();
+        sloodlepwd = (string)llGetKey() + "|" + password;
+        // Initiate the object authorisation
+        string body = "sloodleobjuuid=" + (string)llGetKey();
+        body += "&sloodleobjname=" + llGetObjectName();
+        body += "&sloodleobjpwd=" + password;
+        body += "&sloodleuuid=" + (string)llGetOwner();
+        body += "&sloodleavname=" + llEscapeURL(llKey2Name(llGetOwner()));
+        httpauthobject = llHTTPRequest(sloodleserverroot + SLOODLE_USER_AUTH_LINKER, [HTTP_METHOD, "POST", HTTP_MIMETYPE, "application/x-www-form-urlencoded"], body);
+    }
+    
+    state_exit()
+    {
+        llSetText("", <0.0,0.0,0.0>, 0.0);
+        httpauthobject = NULL_KEY;
+    }
+    
+    http_response(key id, integer status, list meta, string body)
+    {
+        // Make sure this is the response we're expecting
+        if (id != httpauthobject) return;
+        if (status != 200) {
+            sloodle_translation_request(SLOODLE_TRANSLATE_OWNER_SAY, [0], "httperror:code", [status], NULL_KEY, "");
+            return;
+        }
+        
+        // Split the response into lines
+        list lines = llParseStringKeepNulls(body, ["\n"], []);
+        integer numlines = llGetListLength(lines);
+        list statusfields = llParseStringKeepNulls(llList2String(lines, 0), ["|"], []);
+        integer statuscode = (integer)llList2String(statusfields, 0);
+        
+        // Check the statuscode
+        if (statuscode <= 0) {
+            sloodle_translation_request(SLOODLE_TRANSLATE_OWNER_SAY, [0], "objectauthfailed:code", [statuscode], NULL_KEY, "");
+            sloodle_error_code(SLOODLE_TRANSLATE_SAY, NULL_KEY,statuscode); //send message to error_message.lsl
+            return;
+        }
+        
+        // Attempt to get the auth ID
+        if (numlines < 2) {
+            sloodle_translation_request(SLOODLE_TRANSLATE_OWNER_SAY, [0], "badresponseformat", [], NULL_KEY, "");
+            return;
+        }
+        
+        // The dataline will contain our URL
+        sloodleauthurl = llList2String(lines, 1);
+        sloodle_load_auth_url(llGetOwner());
+        
+        state check_auth;
+    }
+    
+    touch_start(integer num_detected)
+    {
+        // Revert to the default state if the owner touched
+        if (llDetectedKey(0) != llGetOwner()) return;
+        state default;
+    }
+    
+    link_message(integer sender_num, integer num, string sval, key kval)
+    {
+        // Check the channel
+        if (num == SLOODLE_CHANNEL_OBJECT_DIALOG) {
+            // Is it a reset command?
+            if (sval == "do:reset") {
+                llResetScript();
+                return;
+            }
+        }
+    }
+    
+    on_rez(integer start_param)
+    {
+        state default;
+    }
+    
+    attach( key av )
+    {
+        if (av != NULL_KEY)
+            state default;
+    }
+}
+
+
+// Check the object's authorisation status
+state check_auth
+{
+    state_entry()
+    {
+        sloodle_translation_request(SLOODLE_TRANSLATE_OWNER_SAY, [], "checkingauth", [], NULL_KEY, "");
+
+        // Check the authorisation regularly until it passes or we need to reset
+        llSetTimerEvent(0.0);
+        llSetTimerEvent(15.0);
+
+        // Initiate the authorisation check
+        httpcheckauth = sloodle_check_user_auth();
+    }
+    
+    timer()
+    {
+        // Initiate the authorisation check
+        httpcheckauth = sloodle_check_user_auth();
+    }
+    
+    state_exit()
+    {
+        llSetTimerEvent(0.0);
+        httpcheckauth = NULL_KEY;
+    }
+    
+    http_response(key id, integer status, list meta, string body)
+    {
+        // Make sure this is the response we're expecting
+        if (id != httpcheckauth) return;
+        httpcheckauth = NULL_KEY;
+        if (status != 200) {
+            sloodle_translation_request(SLOODLE_TRANSLATE_OWNER_SAY, [], "httperror", [status], NULL_KEY, "");
+            state idle;
+            return;
+        }
+        
+        // Split the response into lines
+        list lines = llParseStringKeepNulls(body, ["\n"], []);
+        integer numlines = llGetListLength(lines);
+        list statusfields = llParseStringKeepNulls(llList2String(lines, 0), ["|"], []);
+        integer statuscode = (integer)llList2String(statusfields, 0);
+        
+        // Check the statuscode.
+        // If it reports that the object hasn't been authorised yet, then keep checking.
+        // For any other error, go back to the start of the process.
+        if (statuscode == -214) {
+            sloodle_debug("Not authorised yet...");
+            return;
+        } else if (statuscode <= 0) {
+            //sloodle_translation_request(SLOODLE_TRANSLATE_OWNER_SAY, [], "servererror", [statuscode], NULL_KEY, "");
+            sloodle_error_code(SLOODLE_TRANSLATE_SAY, NULL_KEY,statuscode); //send message to error_message.lsl
+            state default;
+            return;
+        }
+        
+        // Looks like we're authorised OK
+        state send_config;
+    }
+    
+    touch_start(integer num_detected)
+    {
+        // Initiate the authorisation check
+        httpcheckauth = sloodle_check_user_auth();
+    }
+    
+    link_message(integer sender_num, integer num, string sval, key kval)
+    {
+        // Check the channel
+        if (num == SLOODLE_CHANNEL_OBJECT_DIALOG) {
+            // Is it a reset command?
+            if (sval == "do:reset") {
+                llResetScript();
+                return;
+            }
+        }
+    }
+    
+    on_rez(integer start_param)
+    {
+        state default;
+    }
+    
+    attach( key av )
+    {
+        if (av != NULL_KEY)
+            state default;
+    }
+}
+
+
+// Send the user to the authorisation page on the site.
+state send_config
+{
+    state_entry()
+    {
+        sloodle_debug("Sending configuration...");
+        // Send the configuration data to the other scripts
+        string config = "set:sloodleserverroot|" + sloodleserverroot;
+        config += "\nset:sloodlepwd|" + sloodlepwd;
+        config += "\n" + SLOODLE_EOF;
+        sloodle_tell_other_scripts(config);
+        
+        state idle;
+    }
+}
+
+
+// Configuration finished
+state idle
+{
+    state_entry()
+    {
+        sloodle_debug("Configuration finished.");
+    }
+    
+    link_message(integer sender_num, integer num, string sval, key kval)
+    {
+        // Check the channel
+        if (num == SLOODLE_CHANNEL_OBJECT_DIALOG) {
+            // Check the command type
+            if (sval == "do:reset") {
+                llResetScript();
+            } else if (sval == "do:requestconfig") {
+                sloodle_debug("Configuration requested.");
+                state check_auth;
+            } else if (sval == "do:reconfigure") {
+                sloodle_debug("Reconfiguration requested.");
+                state default;
+            }
+            return;
+        }
+    }
+}
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: assets/misc/sloodle_setup_user.lslp 

+ 147 - 0
assets/misc/sloodle_status_codes.lslp

@@ -0,0 +1,147 @@
+//
+// The line above should be left blank to avoid script errors in OpenSim.
+
+/* Sloodle Status Codes
+
+	Copyright (c) 2006-9 Sloodle (various contributors)
+    Released under the GNU GPL
+    
+
+	This files lists all the status codes we use for sloodle.
+	They have been written in LSL format so that you can plunk them into your source
+	code if needed. 
+
+	Contributors:
+    Edmund Edgar
+    Paul Preibisch
+    
+    http://lslplus.sourceforge.net/
+	
+*/
+
+integer SLOODLE_CHANNEL_HTTP_RESPONSE = -1639260101  // Tells the sloodle_rezzer_object script to send the contents as an http response, to the key specified as key
+
+integer SLOODLE_CHANNEL_SET_SET_SHARED_MEDIA_URL_OWNER = -1639270111; // set the main shared media panel to the specified URL, accessible to the owner
+integer SLOODLE_CHANNEL_SET_SET_SHARED_MEDIA_URL_GROUP = -1639270112; // set the main shared media panel to the specified URL, accessible to the group
+integer SLOODLE_CHANNEL_SET_SET_SHARED_MEDIA_URL_ANYONE = -1639270114; // set the main shared media panel to the specified URL, accessible to anyone
+integer SLOODLE_CHANNEL_SET_SET_BROWSER_URL_OWNER = -1639270121; // set the open browser button to url, accessible to owner
+integer SLOODLE_CHANNEL_SET_SET_BROWSER_URL_GROUP = -1639270122; // set the open browser button to url, accessible to group
+integer SLOODLE_CHANNEL_SET_SET_BROWSER_URL_ANYONE = -1639270124; // set the open browser button to url, accessible to anyone
+integer SLOODLE_CHANNEL_SET_CLEANUP_AND_DEREZ = -1639270131; // linked message to tell the object to derez if it has some object-specific cleanup tasks.
+
+integer SLOODLE_CHANNEL_QUIZ_START_FOR_AVATAR = -1639271102; //Tells us to start a quiz for the avatar, if possible.; Ordinary quiz chair will have a second script that detects and avatar sitting      on it and sends it. Awards-integrated version waits for a game ID to be set before doing this.
+integer SLOODLE_CHANNEL_QUIZ_STARTED_FOR_AVATAR = -1639271103; //Sent by main quiz script to tell UI scripts that quiz has started for avatar with key
+integer SLOODLE_CHANNEL_QUIZ_COMPLETED_FOR_AVATAR = -1639271104; //Sent by main quiz script to tell UI scripts that quiz has finished for avatar with key, with x/y correct in string
+integer SLOODLE_CHANNEL_QUESTION_ASKED_AVATAR = -1639271105; //Sent by main quiz script to tell UI scripts that question has been asked to avatar with key. String contains question ID + "|" + question text
+integer SLOODLE_CHANNEL_QUESTION_ANSWERED_AVATAR = -1639271106;  //Sent by main quiz script to tell UI scripts that question has been answered by avatar with key. String contains selected option ID + "|" + option text + "|"
+integer SLOODLE_CHANNEL_QUIZ_LOADING_QUESTION = -1639271107; 
+integer SLOODLE_CHANNEL_QUIZ_LOADED_QUESTION = -1639271108;
+integer SLOODLE_CHANNEL_QUIZ_LOADING_QUIZ = -1639271109;
+integer SLOODLE_CHANNEL_QUIZ_LOADED_QUIZ = -1639271110;
+integer SLOODLE_CHANNEL_QUIZ_GO_TO_STARTING_POSITION = -1639271111;
+integer SLOODLE_CHANNEL_QUIZ_ASK_QUESTION = -1639271112;             
+integer SLOODLE_CHANNEL_ANSWER_SCORE_FOR_AVATAR = -1639271113; // Tells anyone who might be interested that we scored the answer. Score in string, avatar in key.
+integer SLOODLE_CHANNEL_QUIZ_STATE_ENTRY_DEFAULT = -1639271114; //mod quiz script is in state DEFAULT
+integer SLOODLE_CHANNEL_QUIZ_STATE_ENTRY_READY = -1639271115; //mod quiz script is in state READY
+integer SLOODLE_CHANNEL_QUIZ_STATE_ENTRY_LOAD_QUIZ_FOR_USER = -1639271116; //mod quiz script is in state CHECK_QUIZ
+integer SLOODLE_CHANNEL_QUIZ_STATE_ENTRY_QUIZZING = -1639271117; //mod quiz script is in state quizzing
+integer SLOODLE_CHANNEL_QUIZ_NO_PERMISSION_USE= -1639271118; //user has tried to use the chair but doesnt have permission to do so.
+integer SLOODLE_CHANNEL_QUIZ_STOP_FOR_AVATAR = -1639271119; //Tells us to STOP a quiz for the avatar
+integer SLOODLE_CHANNEL_QUIZ_UNSEAT_AVATAR = -1639271120;  //sends a message to unseat an avatar
+integer SLOODLE_CHANNEL_QUIZ_ERROR_INVALID_QUESION = -1639271121;  //
+integer SLOODLE_CHANNEL_QUIZ_SUCCESS_NOTHING_MORE_TO_DO_WITH_AVATAR= -1639271122;
+integer SLOODLE_CHANNEL_QUIZ_ERROR_ATTEMPTS_LEFT= -1639271123;  //
+integer SLOODLE_CHANNEL_QUIZ_ERROR_NO_QUESTIONS= -1639271124;  //
+integer SLOODLE_CHANNEL_QUIZ_ASK_QUESTION_CHAT = -1639271125; // Tells the question handler scripts to ask the question with the ID in str to the avatar with key VIA CHAT.
+integer SLOODLE_CHANNEL_QUIZ_ASK_QUESTION_DIALOG = -1639271126; // Tells the question handler scripts to ask the question with the ID in str to the avatar with key VIA DIALOG.
+integer SLOODLE_CHANNEL_QUIZ_ASK_QUESTION_SHARED_MEDIA = -1639271127; // Tells the question handler scripts to ask the question with the ID in str to the avatar with key VIA SHARED MEDIA.
+integer SLOODLE_CHANNEL_SCOREBOARD_SHARED_MEDIA_SET_ADMIN_URL_CHANNEL= -1639271128; // This is the channel that the scoreboard shouts out its admin URL
+integer SLOODLE_CHANNEL_SCOREBOARD_SHARED_MEDIA_CHANGE_ADMIN_URL_CHANNEL= -1639271129; // This is the channel that the scoreboard shouts out its admin URL WHEN It has changed due to a region event (lost its url etc)
+integer SLOODLE_SCOREBOARD_CONNECT_HUD= -1639271130; // channel which gets sent a linked message by the connect a hud button when it is touched.
+integer SLOODLE_SCOREBOARD_CONNECT_HUD_REGION_SAY= -1639271131; // broadcast to the region by the scoreboard when a user presses the connect scoreboard button
+
+integer SLOODLE_CHANNEL_BACKPACK_SHARED_MEDIA_SET_STUDENT_URL_CHANNEL = -1639271161; // broadcast to the region by the hud giver 
+integer SLOODLE_CHANNEL_BACKPACK_SHARED_MEDIA_REQUEST_STUDENT_URL_CHANNEL = -1639271162; // broadcast to the region by the hud 
+integer SLOODLE_CHANNEL_BACKPACK_KNOWN_SITE_CONTROLLER_LIST = -1639271163; // list of available sites and controllers
+integer SLOODLE_CHANNEL_BACKPACK_SITE_CONTROLLER_TOGGLE_NEXT = -1639271164; // button to switch between controllers on the hud
+integer SLOODLE_CHANNEL_BACKPACK_SITE_CONTROLLER_TOGGLE_PREVIOUS = -1639271165; // button to switch between controllers on the hud
+integer SLOODLE_CHANNEL_BACKPACK_SITE_CONTROLLER_CURRENT_DISPLAY = -1639271166; // current site / controller for display 
+
+integer SLOODLE_OBJECT_INTERACTION= -1639271132; //channel interaction objects speak on
+integer SLOODLE_OBJECT_REGISTER_INTERACTION= -1639271133; //channel objects send interactions to the mod_interaction-1.0 script on to be forwarded to server
+integer SLOODLE_OBJECT_INTERACTION_SHOUT_COMMAND= -1639271134; //channel interaction object will shout commands to its consituent parts
+integer SLOODLE_OBJECT_BLINK= -1639271135; //linked message channel to tell a prim to blink
+integer SLOODLE_OBJECT_GIVE_POINTS= -1639271136; //linked message channel to tell an object to give points
+integer SLOODLE_LOAD_CURRENT_URL= -1639271137; //send message to shared media to open url that it is currently displaying in a browser.
+integer SLOODLE_CHANNEL_DISTRIBUTOR_REQUEST_GIVE_OBJECT = -1639271151; // start the process to give the specified object to the specified avatar, if they are allowed it.
+integer SLOODLE_CHANNEL_DISTRIBUTOR_DO_GIVE_OBJECT = 1639271152; // actually do give the specified object to the specified avatar
+integer SLOODLE_CHANNEL_SCOREBOARD_UPDATE_COMPLETE = 1639271140;
+integer SLOODLE_CHANNEL_SCOREBOARD_SCORES_CONFIG = -1639272000;
+integer  SLOODLE_CHANNEL_SCOREBOARD_SCORES_SET_CELL_INFO=-1639272001; 
+integer  SLOODLE_CHANNEL_SCOREBOARD_SCORES_REMAP_INDICIES=-1639272002;
+integer  SLOODLE_CHANNEL_SCOREBOARD_SCORES_RESET_INDICIES=-1639272003;
+integer  SLOODLE_CHANNEL_SCOREBOARD_SCORES_SET_THICKNESS=-1639272004;
+integer  SLOODLE_CHANNEL_SCOREBOARD_SCORES_SET_COLOR=-1639272005;
+integer SLOODLE_CHANNEL_SCOREBOARD_SCORES = 1639272100;
+integer SLOODLE_CHANNEL_SCOREBOARD_TEAM_CONFIG = -1639273000;
+integer SLOODLE_CHANNEL_SCOREBOARD_TEAM_SET_CELL_INFO = -1639273001;
+integer SLOODLE_CHANNEL_SCOREBOARD_TEAM_REMAP_INDICIES = -1639273002;
+integer SLOODLE_CHANNEL_SCOREBOARD_TEAM_RESET_INDICIES = -1639273003;
+integer SLOODLE_CHANNEL_SCOREBOARD_TEAM_SET_THICKNESS = -1639273004;
+integer SLOODLE_CHANNEL_SCOREBOARD_TEAM_SET_COLOR = -1639273005;
+integer SLOODLE_CHANNEL_SCOREBOARD_TEAM = 1639273100;
+integer SLOODLE_CHANNEL_SCOREBOARD_CURRENCY_CONFIG = -1639274000;
+integer SLOODLE_CHANNEL_SCOREBOARD_CURRENCY_SET_CELL_INFO  = -1639274001;
+integer SLOODLE_CHANNEL_SCOREBOARD_CURRENCY_REMAP_INDICIES  = -1639274002;
+integer SLOODLE_CHANNEL_SCOREBOARD_CURRENCY_RESET_INDICIES = -1639274003;
+integer SLOODLE_CHANNEL_SCOREBOARD_CURRENCY_SET_THICKNESS  = -1639274004;
+integer SLOODLE_CHANNEL_SCOREBOARD_CURRENCY_SET_COLOR = -1639274005;
+integer SLOODLE_CHANNEL_SCOREBOARD_CURRENCY = 1639274100;
+integer SLOODLE_CHANNEL_SCOREBOARD_TITLE_CONFIG = -1639275000;
+integer SLOODLE_CHANNEL_SCOREBOARD_TITLE_SET_CELL_INFO = -1639275001;
+integer SLOODLE_CHANNEL_SCOREBOARD_TITLE_REMAP_INDICIES = -1639275002;
+integer SLOODLE_CHANNEL_SCOREBOARD_TITLE_RESET_INDICIES = -1639275003;
+integer SLOODLE_CHANNEL_SCOREBOARD_TITLE_SET_THICKNESS = -1639275004;
+integer SLOODLE_CHANNEL_SCOREBOARD_TITLE_SET_COLOR = -1639275005;
+integer SLOODLE_CHANNEL_SCOREBOARD_TITLE= 1639275100;
+integer SLOODLE_CHANNEL_ZZTEXT_TEXTURE_CONFIG= -1639276000;
+integer SLOODLE_CHANNEL_POOL_BOUNDARIES_QUERY= -163928000;//sharks whisper on this channel requesting the boundaries from the shark pool
+integer SLOODLE_CHANNEL_POOL_BOUNDARIES_RESPONSE= -163928001;//pool whispers on this channel telling sharks the boundaries from the shark pool
+integer SLOODLE_CHANNEL_ENEMY_AIM = -163928665;//Channel to communicate on when enemy is aming at target, ie:delivering a laserbeam
+integer SLOODLE_CHANNEL_ENEMY_ATTACK= -163928666;//Channel to communicate on when attack occurs by an enemy
+integer SLOODLE_CHANNEL_MESSAGER_INSTANT_MESSAGE = 163290001;
+integer SLOODLE_CHANNEL_OPEN_IN_BROWSER = -1639277000;
+integer SLOODLE_CHANNEL_USER_TOUCH = -1639277002;//user touched object
+integer SLOODLE_CHANNEL_QUIZ_LOAD_QUIZ= -1639277003;//user touched object
+integer SLOODLE_CHANNEL_QUIZ_NOTIFY_SERVER_OF_RESPONSE= -1639277004;
+integer SLOODLE_CHANNEL_QUIZ_FEED_BACK_REQUEST= -1639277005;
+integer SLOODLE_CHANNEL_QUIZ_ASK_QUESTION_DIALOG0 = -170000; // Tells the question handler scripts to ask the question with the ID in str to the avatar with key VIA DIALOG.
+integer SLOODLE_CHANNEL_QUIZ_ASK_QUESTION_DIALOG1 = -1700001; // Tells the question handler scripts to ask the question with the ID in str to the avatar with key VIA DIALOG.
+integer SLOODLE_CHANNEL_QUIZ_ASK_QUESTION_DIALOG2 = -1700002; // Tells the question handler scripts to ask the question with the ID in str to the avatar with key VIA DIALOG.
+integer SLOODLE_CHANNEL_QUIZ_ASK_QUESTION_DIALOG3= -1700003; // Tells the question handler scripts to ask the question with the ID in str to the avatar with key VIA DIALOG.
+integer SLOODLE_CHANNEL_QUIZ_ASK_QUESTION_DIALOG4 = -1700004; // Tells the question handler scripts to ask the question with the ID in str to the avatar with key VIA DIALOG.
+integer SLOODLE_CHANNEL_QUIZ_ASK_QUESTION_DIALOG5 = -1700005; // Tells the question handler scripts to ask the question with the ID in str to the avatar with key VIA DIALOG.
+integer SLOODLE_CHANNEL_QUIZ_ASK_QUESTION_DIALOG6 = -1700006; // Tells the question handler scripts to ask the question with the ID in str to the avatar with key VIA DIALOG.
+integer SLOODLE_CHANNEL_QUIZ_MASTER_REQUEST= -1639277006;
+integer SLOODLE_CHANNEL_ANIM= -1639277007;
+integer SLOODLE_CHANNEL_QUIZ_MASTER_RESPONSE= -1639277008;
+integer SLOODLE_TRANSLATE_HOVER_TEXT_LINKED_PRIM= -1639277009;
+integer SLOODLE_SET_TEXTURE= -1639277010;
+integer SLOODLE_TIMER_START= -1639277011; //shoudl be used to starts the timer from its current position
+integer SLOODLE_TIMER_RESTART= -1639277012;//should be used to set the counter to 0 and begin counting down again
+integer SLOODLE_TIMER_STOP= -1639277013;//should stop the timer at its current position
+integer SLOODLE_TIMER_STOP_AND_RESET= -1639277014;//should stop the timer at its current position and reset count to 0
+integer SLOODLE_TIMER_RESET= -1639277015;//shoudl reset the count back to zero but not restart the timer
+integer SLOODLE_TIMER_TIMES_UP= -1639277016;//used to transmit the timer reached its time limit
+integer SLOODLE_SENSOR_ONCE= -1639277017;//issue an llSensor
+integer SLOODLE_REMOTE_LOAD_SCRIPT=1639277018;//remote loader channel used by scripts who can't afford to sleep but need to use llRemoteScriptLoadPin, use this channel to get a remote loader script to handle the loading instead of main script
+integer SLOODLE_BLINKER=1639277019;//used to send commands to a prim which should blink
+integer SLOODLE_HTTPIN_MESSAGE_RECEIVED=1639277020;//used to tell other scripts a message has been received
+integer SLOODLE_OBSTRUCTION=1639277021;
+integer SLOODLE_SENSOR_DETECTED_KEYS= -1639277022;//issue an llSensor
+integer SLOODLE_CHANNEL_CHILDREN=-1639277023;
+integer SLOODLE_CHANNEL_PARENT=-1639277024;
+integer SLOODLE_QUESTION_IMAGE=-1639277025;//used for in quiz scripts, when a question is recieved, send the image url|question dialog to an display
+integer SLOODLE_FEEDBACK_OUTPUT=-1639277026;//used for in quiz scripts, when a user answers a question, output the feedback to linked message so it can be displayed
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: assets/misc/sloodle_status_codes.lslp.lsl 

+ 85 - 0
assets/sloodle_email_login_details.lslp

@@ -0,0 +1,85 @@
+//
+// The line above should be left blank to avoid script errors in OpenSim.
+
+// How many seconds between each email check
+float EMAIL_CHECK_PERIOD = 30.0;
+
+// Subject of emails to check for
+string EMAIL_SUBJECT_LOGIN = "SLOODLE_LOGIN";
+
+
+///// TRANSLATION /////
+
+integer SLOODLE_CHANNEL_TRANSLATION_REQUEST = -1928374651;
+string SLOODLE_TRANSLATE_IM = "instantmessage";     // Recipient avatar should be identified in link message keyval. No output parameters.
+
+// Send a translation request link message
+sloodle_translation_request(string output_method, list output_params, string string_name, list string_params, key keyval, string batch)
+{
+    llMessageLinked(LINK_THIS, SLOODLE_CHANNEL_TRANSLATION_REQUEST, output_method + "|" + llList2CSV(output_params) + "|" + string_name + "|" + llList2CSV(string_params) + "|" + batch, keyval);
+}
+
+///// ----------- /////
+
+
+
+default
+{
+    state_entry()
+    {
+        // Start the timer for email checking
+        llSetTimerEvent(EMAIL_CHECK_PERIOD);
+    }
+    
+    timer()
+    {
+        // Check for new emails
+        llGetNextEmail("", EMAIL_SUBJECT_LOGIN);
+    }
+        
+    email(string time, string address, string subject, string msg, integer num_left)
+    {
+        //llOwnerSay("Received email from \"" + address + "\".\nSubject: \"" + subject + "\"\nMessage: \"" + msg + "\"");
+        
+        // Reset the timer (in case this was triggered by this event, not by the timer)
+        llSetTimerEvent(EMAIL_CHECK_PERIOD);
+        
+        // Check the subject line of the email
+        if (subject == EMAIL_SUBJECT_LOGIN) {
+            
+            // Split off the headers
+            list parts = llParseStringKeepNulls(msg, ["\n\n"], []);
+            string body = "";
+            if (llGetListLength(parts) >= 2) {
+                body = llList2String(parts, 1);
+            } else {
+                body = msg;
+            }
+            
+            // Parse the data fields
+            list fields = llParseStringKeepNulls(body, ["|"], []);
+            // Make sure we have enough
+            if (llGetListLength(fields) >= 3) {
+                // Extract the individual fields
+                key useruuid = (key)llList2String(fields, 0);
+                string website = llList2String(fields, 1);
+                string username = llList2String(fields, 2);
+                string password = llList2String(fields, 3);
+                
+                // Send the information to the user
+                if (useruuid != NULL_KEY) {
+                    sloodle_translation_request(SLOODLE_TRANSLATE_IM, [], "autoreg:newaccount", [website, username, password], useruuid, "");
+                }
+            }
+            
+        }
+        
+        // Are there more emails to check for?
+        if (num_left > 0) {
+            // Yes - get the next one
+            llGetNextEmail("", EMAIL_SUBJECT_LOGIN);
+        }
+    }
+}
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: assets/sloodle_email_login_details.lslp 

+ 86 - 0
assets/sloodle_hover_text_displayer.lslp

@@ -0,0 +1,86 @@
+//
+// The line above should be left blank to avoid script errors in OpenSim.
+
+/*
+*********************************************************************************************
+*  sloodle_hover_text_displayer.lsl
+*  Copyright (c) 2009 Paul Preibisch
+*  Released under the GNU GPL 3.0
+*  This script can be used in your scripts, but you must include this copyright header as per the GPL Licence
+*  For more information about GPL 3.0 - see: http://www.gnu.org/copyleft/gpl.html
+* 
+*
+*  This script is part of the SLOODLE Project see http://sloodle.org
+*  This script can be dropped into a linked prim, and messages can be sent to it on channel SETTEXT_CHANNEL=-776644 to set the text above 
+*  This way you can better position where setText get's printed
+*  You should name the linked prim a unique name, then send this command to it via a linked message
+*  example:  DISPLAY:top display|STRING:some string|COLOR:"+<0,0.5,0>
+*  example:  llMessageLinked(LINK_SET,SETTEXT_CHANNEL,"DISPLAY:top display|STRING:"+text+"|COLOR:"+(string)GREEN,NULL_KEY);
+*
+*  Copywrite 2009
+*  Paul G. Preibisch (Fire Centaur in SL)
+*  fire@b3dMultiTech.com  
+**********************************************************************************************
+*/
+
+integer SETTEXT_CHANNEL=-776644;
+list     commandList; //used for linked_message strings
+/***********************************************
+*  extractResponse()
+*  Is used so that sending of linked messages is more readable by humans.  Ie: instead of sending a linked message as
+*  GETDATA|50091bcd-d86d-3749-c8a2-055842b33484 
+*  Context is added instead: COMMAND:GETDATA|PLAYERUUID:50091bcd-d86d-3749-c8a2-055842b33484
+*  By adding a context to the messages, the programmer can understand whats going on when debugging
+*  All this function does is strip off the text before the ":" char 
+***********************************************/
+string extractResponse(string cmd){     
+     return llList2String(llParseString2List(cmd, ["::"],[]),1);
+}
+default {
+    /***********************************************
+    *  on_rez event
+    *  |--> Reset Script to ensure proper defaults on rez
+    ***********************************************/
+    on_rez(integer start_param) {
+        llResetScript();
+    }
+    /***********************************************
+    *  state_entry
+    *  |-->
+    ***********************************************/    
+    state_entry() {
+        llSetText("",<0,0,0>,1.0);        
+    }
+
+    
+    /***********************************************
+    *  changed event
+    *  |-->Every time the inventory changes, reset the script
+    *        
+    ***********************************************/
+    changed(integer change) {
+     if (change ==CHANGED_INVENTORY){   
+          llSetText("                                                               ",<0,0,0>,1.0);       
+         llResetScript();
+     }
+    }
+    
+    link_message(integer sender_num, integer channel, string str, key id) {
+    
+        if     (channel==SETTEXT_CHANNEL){
+            //llMessageLinked(LINK_SET,SETTEXT_CHANNEL,"DISPLAY:top display|STRING:"+text+"|COLOR:"+(string)GREEN,NULL_KEY);
+            commandList=llParseString2List(str, ["|"],[]);
+            if (extractResponse(llList2String(commandList,0))==llGetLinkName(llGetLinkNumber())){
+                string text = extractResponse(llList2String(commandList,1));
+                vector color = (vector)extractResponse(llList2String(commandList,2));
+                float alpha = (float)extractResponse(llList2String(commandList,3));
+                llSetText(text, color, alpha);
+            }
+        }
+    }
+
+}
+
+
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: assets/sloodle_hover_text_displayer.lslp 

+ 421 - 0
assets/sloodle_layout_object.lslp

@@ -0,0 +1,421 @@
+//
+// The line above should be left blank to avoid script errors in OpenSim.
+
+// Sloodle object layout script.
+// Allows individual objects to store themselves in a Sloodle layout.
+//
+// Part of the Sloodle project (www.sloodle.org)
+// Copyright (c) 2008 Sloodle
+// Released under the GNU GPL v3
+//
+// Contributors:
+//  Peter R. Bloomfield
+//  Edmund Edgar
+//  Paul Preibisch - Fire Centaur in SL
+//
+///// DATA /////
+integer SLOODLE_CHANNEL_ERROR_TRANSLATION_REQUEST=-1828374651; // this channel is used to send status codes for translation to the error_messages lsl script
+integer SLOODLE_CHANNEL_OBJECT_DIALOG = -3857343;
+integer SLOODLE_CHANNEL_AVATAR_DIALOG = 1001;
+string SLOODLE_LAYOUT_LINKER = "/mod/sloodle/mod/set-1.0/layout_linker.php";
+string SLOODLE_EOF = "sloodleeof";
+
+string sloodleserverroot = "";
+string sloodlepwd = "";
+integer sloodlecontrollerid = 0;
+key sloodlemyrezzer = NULL_KEY;
+
+integer isconfigured = FALSE; // Do we have all the configuration data we need?
+integer eof = FALSE; // Have we reached the end of the configuration data?
+
+key httpstore = NULL_KEY; // Request to store this object's location
+float HTTP_TIMEOUT = 8.0; // Time to wait for an HTTP response before failing
+
+integer attemptnum = 0; // Which attempt number is this?
+integer attemptmax = 2; // How many attempts should be allowed [0,max)
+
+float DELAY_MIN = 0.0; // Minimum delay before sending an HTTP request (seconds)
+float DELAY_RANGE = 3.5; // Added to DELAY_MIN gives the maximum delay time before sending HTTP request (seconds)
+
+key useruuid = NULL_KEY; // User agent requesting profile storage
+string layoutname = ""; // Name of the layout to save to
+vector layoutpos = <0.0,0.0,0.0>; // Relative position from the rezzer to this object
+
+
+///// TRANSLATION /////
+
+// Link message channels
+integer SLOODLE_CHANNEL_TRANSLATION_REQUEST = -1928374651;
+integer SLOODLE_CHANNEL_TRANSLATION_RESPONSE = -1928374652;
+
+// Translation output methods
+string SLOODLE_TRANSLATE_LINK = "link";             // No output parameters - simply returns the translation on SLOODLE_TRANSLATION_RESPONSE link message channel
+string SLOODLE_TRANSLATE_SAY = "say";               // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_WHISPER = "whisper";       // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_SHOUT = "shout";           // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_REGION_SAY = "regionsay";  // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_OWNER_SAY = "ownersay";    // No output parameters
+string SLOODLE_TRANSLATE_DIALOG = "dialog";         // Recipient avatar should be identified in link message keyval. At least 2 output parameters: first the channel number for the dialog, and then 1 to 12 button label strings.
+string SLOODLE_TRANSLATE_LOAD_URL = "loadurl";      // Recipient avatar should be identified in link message keyval. 1 output parameter giving URL to load.
+string SLOODLE_TRANSLATE_HOVER_TEXT = "hovertext";  // 2 output parameters: colour <r,g,b>, and alpha value
+string SLOODLE_TRANSLATE_IM = "instantmessage";     // Recipient avatar should be identified in link message keyval. No output parameters.
+
+// Send a translation request link message
+sloodle_translation_request(string output_method, list output_params, string string_name, list string_params, key keyval, string batch)
+{
+    llMessageLinked(LINK_THIS, SLOODLE_CHANNEL_TRANSLATION_REQUEST, output_method + "|" + llList2CSV(output_params) + "|" + string_name + "|" + llList2CSV(string_params) + "|" + batch, keyval);
+}
+
+///// ----------- /////
+
+
+///// FUNCTIONS /////
+/******************************************************************************************************************************
+* sloodle_error_code - 
+* Author: Paul Preibisch
+* Description - This function sends a linked message on the SLOODLE_CHANNEL_ERROR_TRANSLATION_REQUEST channel
+* The error_messages script hears this, translates the status code and sends an instant message to the avuuid
+* Params: method - SLOODLE_TRANSLATE_SAY, SLOODLE_TRANSLATE_IM etc
+* Params:  avuuid - this is the avatar UUID to that an instant message with the translated error code will be sent to
+* Params: status code - the status code of the error as on our wiki: http://slisweb.sjsu.edu/sl/index.php/Sloodle_status_codes
+*******************************************************************************************************************************/
+sloodle_error_code(string method, key avuuid,integer statuscode){
+            llMessageLinked(LINK_SET, SLOODLE_CHANNEL_ERROR_TRANSLATION_REQUEST, method+"|"+(string)avuuid+"|"+(string)statuscode, NULL_KEY);
+}
+
+// Send debug info
+sloodle_debug(string msg)
+{
+    llMessageLinked(LINK_THIS, DEBUG_CHANNEL, msg, NULL_KEY);
+}
+
+// Configure by receiving a linked message from another script in the object
+// Returns TRUE if the object has all the data it needs
+integer sloodle_handle_command(string str) 
+{
+    list bits = llParseString2List(str,["|"],[]);
+    integer numbits = llGetListLength(bits);
+    string name = llList2String(bits,0);
+    string value1 = "";
+    string value2 = "";
+    
+    if (numbits > 1) value1 = llList2String(bits,1);
+    if (numbits > 2) value2 = llList2String(bits,2);
+    
+    if (name == "set:sloodleserverroot") sloodleserverroot = value1;
+    else if (name == "set:sloodlepwd") {
+        // The password may be a single prim password, or a UUID and a password
+        if (value2 != "") sloodlepwd = value1 + "|" + value2;
+        else sloodlepwd = value1;
+        
+    } else if (name == "set:sloodlecontrollerid") sloodlecontrollerid = (integer)value1;
+    else if (name == "set:sloodlemyrezzer") sloodlemyrezzer = (key)value1;
+    else if (name == SLOODLE_EOF) eof = TRUE;
+    else if (name == "do:reset") llResetScript();
+    
+    return (sloodleserverroot != "" && sloodlepwd != "" && sloodlecontrollerid > 0);
+}
+
+// Generate a random delay time
+float random_delay()
+{
+    return (DELAY_MIN + llFrand(DELAY_RANGE));
+}
+
+
+///// STATES /////
+
+default
+{
+    state_entry()
+    {
+        // Starting again with a new configuration
+        isconfigured = FALSE;
+        eof = FALSE;
+        sloodleserverroot = "";
+        sloodlepwd = "";
+        sloodlecontrollerid = 0;
+    }
+    
+    link_message(integer sender_num, integer num, string str, key id)
+    {
+        // Check the channel
+        if (num == SLOODLE_CHANNEL_OBJECT_DIALOG) {
+            // Split the message into lines
+            list lines = llParseString2List(str, ["\n"], []);
+            integer numlines = llGetListLength(lines);
+            integer i;
+            for (i=0; i < numlines; i++) {
+                isconfigured = sloodle_handle_command(llList2String(lines, i));
+            }
+            
+            // If we've got all our data AND reached the end of the configuration data, then move on
+            if (eof == TRUE && isconfigured == TRUE) {
+                state ready;
+            }
+        }  
+    }
+}
+
+state ready
+{
+    state_entry()
+    {
+        // Reset our data
+        useruuid = NULL_KEY;
+        layoutname = "";
+        // Listen for chat messages on the object dialog channel
+        llListen(SLOODLE_CHANNEL_OBJECT_DIALOG, "", sloodlemyrezzer, "");
+    }
+    
+    listen(integer channel, string name, key id, string msg)
+    {
+        // Ignore anything if we don't know who our rezzer is
+        if (sloodlemyrezzer == NULL_KEY) return;
+        
+        // Ignore anything but object chat
+        if (channel != SLOODLE_CHANNEL_OBJECT_DIALOG) return;
+        // Ignore anything owned by a different agent
+        if (llGetOwnerKey(id) != llGetOwner()) return;
+        
+        // Parse the message
+        // We are expecting: cmd|rezzer|uuid|pos|layoutname
+        // "cmd" should be "do:storelayout"
+        // "rezzer" is the UUID of the Set whose items should be stored
+        // "uuid" is the UUID of the user agent storing the layout
+        // "pos" is the vector giving the position of the root of the rezzer
+        // "layoutname" should be the name of the layout to save to
+        list fields = llParseStringKeepNulls(msg, ["|"], []);
+        integer numfields = llGetListLength(fields);
+        if (numfields < 4) return;
+        // Get the command and UUID
+        string cmd = llList2String(fields, 0);
+        key rezzer = (key)llList2String(fields, 1);
+        useruuid = (key)llList2String(fields, 2);
+        vector rezzerpos = (vector)llList2String(fields, 3);
+        layoutname = llList2String(fields, 4);
+        
+        // Check that everything looks OK
+        if (cmd != "do:storelayout") return;
+        if (rezzer != sloodlemyrezzer || sloodlemyrezzer == NULL_KEY) return;
+        if (useruuid == NULL_KEY) return;
+        if (layoutname == "") return;
+        
+        // Calculate the relative position of us compared to the rezzer
+        layoutpos = llGetPos() - rezzerpos;
+        // If the distance is more than can be rezzed later on, then ignore it
+        if (llVecMag(layoutpos) > 10.0) {
+            sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "layout:toofar", [], NULL_KEY, "");
+            return;
+        }
+        
+        // Attempt to store the layout
+        attemptnum = 0;
+        state store_layout;
+    }
+    
+    link_message(integer sender_num, integer num, string sval, key kval)
+    {
+        // Check the channel
+        if (num == SLOODLE_CHANNEL_OBJECT_DIALOG) {
+            // Is it a reset command?
+            if (sval == "do:reset") {
+                llResetScript();
+                return;
+            }
+        }
+    }
+}
+
+// This is a dummy state... it can be used to start the store process again
+state store_layout
+{
+    state_entry()
+    {
+        // Move on immediately...
+        state delay;
+    }
+    
+    link_message(integer sender_num, integer num, string sval, key kval)
+    {
+        // Check the channel
+        if (num == SLOODLE_CHANNEL_OBJECT_DIALOG) {
+            // Is it a reset command?
+            if (sval == "do:reset") {
+                llResetScript();
+                return;
+            }
+        }
+    }
+}
+
+// Wait for a delay before actually sending the request
+state delay
+{
+    state_entry()
+    {
+        // Generate a random delay
+        llSetTimerEvent(random_delay());
+    }
+    
+    state_exit()
+    {
+        llSetTimerEvent(0.0);
+    }
+    
+    timer()
+    {
+        llSetTimerEvent(0.0);
+        // Send the storage request
+        attemptnum += 1;
+        state request;
+    }
+    
+    link_message(integer sender_num, integer num, string sval, key kval)
+    {
+        // Check the channel
+        if (num == SLOODLE_CHANNEL_OBJECT_DIALOG) {
+            // Is it a reset command?
+            if (sval == "do:reset") {
+                llResetScript();
+                return;
+            }
+        }
+    }
+}
+
+// Send a request to store the object data
+state request
+{
+    state_entry()
+    {
+        // Construct our request
+        string body = "sloodlecontrollerid=" + (string)sloodlecontrollerid;
+        body += "&sloodlepwd=" + sloodlepwd;
+        body += "&sloodlelayoutname=" + layoutname;
+        body += "&sloodleuuid=" + (string)useruuid;
+        body += "&sloodlelayoutentries=" + llGetObjectName() + "|" + (string)layoutpos + "|" + (string)llGetRot();
+        body += "&sloodleadd=true";
+        
+        httpstore = llHTTPRequest(sloodleserverroot + SLOODLE_LAYOUT_LINKER, [HTTP_METHOD, "POST", HTTP_MIMETYPE, "application/x-www-form-urlencoded"], body);
+    }
+    
+    state_exit()
+    {
+        llSetTimerEvent(0.0);
+    }
+    
+    timer()
+    {
+        llSetTimerEvent(0.0);
+        httpstore = NULL_KEY;
+        state failed;
+    }
+    
+    http_response(key id, integer status, list meta, string body)
+    {
+        // Ignore this response if it is not expected
+        if (id != httpstore) return;
+        llSetTimerEvent(0.0);
+        httpstore = NULL_KEY;
+        
+        // Check the HTTP status
+        if (status != 200) {
+            sloodle_error_code(SLOODLE_TRANSLATE_SAY, NULL_KEY,status); //send message to error_message.lsl
+            state failed;
+            return;
+        }
+        
+        // Check the body of the response
+        if (body == "") {
+            sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "httpempty", [], NULL_KEY, "");
+            state failed;
+            return;
+        }
+        
+        // Parse the response
+        list lines = llParseStringKeepNulls(body, ["\n"], []);
+        integer numlines = llGetListLength(lines);
+        list statusfields = llParseStringKeepNulls(llList2String(lines, 0), ["|"], []);
+        integer statuscode = (integer)llList2String(statusfields, 0);
+                
+        // Did an error occur?
+        if (statuscode <= 0) {
+            //sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "servererror", [statuscode], NULL_KEY, "");
+            sloodle_error_code(SLOODLE_TRANSLATE_SAY, NULL_KEY,statuscode); //send message to error_message.lsl
+            sloodle_debug("HTTP response: " + body);
+            state failed;
+            return;
+        }
+        
+        // Everything must be OK... send the data to the object. Format:
+        state success;
+    }
+    
+    link_message(integer sender_num, integer num, string sval, key kval)
+    {
+        // Check the channel
+        if (num == SLOODLE_CHANNEL_OBJECT_DIALOG) {
+            // Is it a reset command?
+            if (sval == "do:reset") {
+                llResetScript();
+                return;
+            }
+        }
+    }
+}
+
+// Update failed... try again?
+state failed
+{
+    state_entry()
+    {
+        // Do we have any attempts left?
+        if (attemptnum < attemptmax) {
+            sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "layout:failedretrying", [], NULL_KEY, "");
+            state store_layout;
+        } else {
+            sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "layout:failedaborting", [], NULL_KEY, "");
+            state ready;
+        }
+    }
+    
+    link_message(integer sender_num, integer num, string sval, key kval)
+    {
+        // Check the channel
+        if (num == SLOODLE_CHANNEL_OBJECT_DIALOG) {
+            // Is it a reset command?
+            if (sval == "do:reset") {
+                llResetScript();
+                return;
+            }
+        }
+    }
+}
+
+// Update successful
+state success
+{
+    state_entry()
+    {
+        // Done!
+        sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "layout:stored", [], NULL_KEY, "");
+        state ready;
+    }
+    
+    link_message(integer sender_num, integer num, string sval, key kval)
+    {
+        // Check the channel
+        if (num == SLOODLE_CHANNEL_OBJECT_DIALOG) {
+            // Is it a reset command?
+            if (sval == "do:reset") {
+                llResetScript();
+                return;
+            }
+        }
+    }
+}
+
+
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: assets/sloodle_layout_object.lslp 

+ 72 - 0
assets/sloodle_parallel_url_loader_0.lslp

@@ -0,0 +1,72 @@
+//
+// The line above should be left blank to avoid script errors in OpenSim.
+
+// Sloodle parallel URL loader
+// Allows the loading of a single URL on a specified channel.
+// (NOTE: should be in the same prim as a "sloodle_multi_url_loader.lsl" script)
+// Part of the Sloodle project (www.sloodle.org)
+//
+// Copyright (c) 2007 Sloodle (various contributors)
+// Released under the GNU GPL
+//
+// Contributors:
+//  Peter R. Bloomfield
+//
+
+//
+// When receiving a link message, this script will load a URL for a given user.
+// The URL should be in the string, and the user specified by their key.
+// The integer number in the link message should be SLOODLE_CHANNEL_OBJECT_LOAD_URL
+//
+// NOTE: you MUST customize the "MY_CHANNEL_INDEX" value to indicate which script number this is.
+//  Each of these scripts in a given object should have a unique channel index.
+//  This identifies where the object appears in the sequence.
+//  Channel indices should be numbered sequentially from 0.
+//  It is recommended that you replace the "x" in the script name to correspond to the channel index.
+
+
+///// DATA /////
+
+// The index of the channel this script will receive requests on
+// (NOTE: corresponds to indices in the list below)
+integer MY_CHANNEL_INDEX = 0;
+
+
+// OUTGOING link message numbers for parallel load requests
+list SLOODLE_CHANNEL_LIST_LOAD_URL_x =  [
+                                        -1699000001, //SLOODLE_CHANNEL_LOAD_URL_0
+                                        -1699000002, //SLOODLE_CHANNEL_LOAD_URL_1
+                                        -1699000003, //SLOODLE_CHANNEL_LOAD_URL_2
+                                        -1699000004, //SLOODLE_CHANNEL_LOAD_URL_3
+                                        -1699000005, //SLOODLE_CHANNEL_LOAD_URL_4
+                                        -1699000006, //SLOODLE_CHANNEL_LOAD_URL_5
+                                        -1699000007, //SLOODLE_CHANNEL_LOAD_URL_6
+                                        -1699000008, //SLOODLE_CHANNEL_LOAD_URL_7
+                                        -1699000009, //SLOODLE_CHANNEL_LOAD_URL_8
+                                        -1699000010  //SLOODLE_CHANNEL_LOAD_URL_9
+                                        ];
+
+///// ---- /////
+
+default
+{
+    state_entry()
+    {
+        if (MY_CHANNEL_INDEX < 0 || MY_CHANNEL_INDEX > llGetListLength(SLOODLE_CHANNEL_LIST_LOAD_URL_x)) {
+            llOwnerSay("WARNING: script \"" + llGetScriptName() + "\" has an invalid channel index. Please alter value \"MY_CHANNEL_INDEX\".");
+        }
+    }
+    
+    link_message( integer sender_num, integer msg_num, string str, key id )
+    {
+        // Ignore this message if the variables are empty
+        if (str == "" || id == NULL_KEY) return;
+        // If this message is not on the correct channel, then ignore it
+        if (msg_num != llList2Integer(SLOODLE_CHANNEL_LIST_LOAD_URL_x, MY_CHANNEL_INDEX)) return;
+        
+        // Load the URL
+        llLoadURL(id, "", str);
+    }
+}
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: assets/sloodle_parallel_url_loader_0.lslp 

+ 72 - 0
assets/sloodle_parallel_url_loader_1.lslp

@@ -0,0 +1,72 @@
+//
+// The line above should be left blank to avoid script errors in OpenSim.
+
+// Sloodle parallel URL loader
+// Allows the loading of a single URL on a specified channel.
+// (NOTE: should be in the same prim as a "sloodle_multi_url_loader.lsl" script)
+// Part of the Sloodle project (www.sloodle.org)
+//
+// Copyright (c) 2007 Sloodle (various contributors)
+// Released under the GNU GPL
+//
+// Contributors:
+//  Peter R. Bloomfield
+//
+
+//
+// When receiving a link message, this script will load a URL for a given user.
+// The URL should be in the string, and the user specified by their key.
+// The integer number in the link message should be SLOODLE_CHANNEL_OBJECT_LOAD_URL
+//
+// NOTE: you MUST customize the "MY_CHANNEL_INDEX" value to indicate which script number this is.
+//  Each of these scripts in a given object should have a unique channel index.
+//  This identifies where the object appears in the sequence.
+//  Channel indices should be numbered sequentially from 0.
+//  It is recommended that you replace the "x" in the script name to correspond to the channel index.
+
+
+///// DATA /////
+
+// The index of the channel this script will receive requests on
+// (NOTE: corresponds to indices in the list below)
+integer MY_CHANNEL_INDEX = 1;
+
+
+// OUTGOING link message numbers for parallel load requests
+list SLOODLE_CHANNEL_LIST_LOAD_URL_x =  [
+                                        -1699000001, //SLOODLE_CHANNEL_LOAD_URL_0
+                                        -1699000002, //SLOODLE_CHANNEL_LOAD_URL_1
+                                        -1699000003, //SLOODLE_CHANNEL_LOAD_URL_2
+                                        -1699000004, //SLOODLE_CHANNEL_LOAD_URL_3
+                                        -1699000005, //SLOODLE_CHANNEL_LOAD_URL_4
+                                        -1699000006, //SLOODLE_CHANNEL_LOAD_URL_5
+                                        -1699000007, //SLOODLE_CHANNEL_LOAD_URL_6
+                                        -1699000008, //SLOODLE_CHANNEL_LOAD_URL_7
+                                        -1699000009, //SLOODLE_CHANNEL_LOAD_URL_8
+                                        -1699000010  //SLOODLE_CHANNEL_LOAD_URL_9
+                                        ];
+
+///// ---- /////
+
+default
+{
+    state_entry()
+    {
+        if (MY_CHANNEL_INDEX < 0 || MY_CHANNEL_INDEX > llGetListLength(SLOODLE_CHANNEL_LIST_LOAD_URL_x)) {
+            llOwnerSay("WARNING: script \"" + llGetScriptName() + "\" has an invalid channel index. Please alter value \"MY_CHANNEL_INDEX\".");
+        }
+    }
+    
+    link_message( integer sender_num, integer msg_num, string str, key id )
+    {
+        // Ignore this message if the variables are empty
+        if (str == "" || id == NULL_KEY) return;
+        // If this message is not on the correct channel, then ignore it
+        if (msg_num != llList2Integer(SLOODLE_CHANNEL_LIST_LOAD_URL_x, MY_CHANNEL_INDEX)) return;
+        
+        // Load the URL
+        llLoadURL(id, "", str);
+    }
+}
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: assets/sloodle_parallel_url_loader_1.lslp 

+ 72 - 0
assets/sloodle_parallel_url_loader_2.lslp

@@ -0,0 +1,72 @@
+//
+// The line above should be left blank to avoid script errors in OpenSim.
+
+// Sloodle parallel URL loader
+// Allows the loading of a single URL on a specified channel.
+// (NOTE: should be in the same prim as a "sloodle_multi_url_loader.lsl" script)
+// Part of the Sloodle project (www.sloodle.org)
+//
+// Copyright (c) 2007 Sloodle (various contributors)
+// Released under the GNU GPL
+//
+// Contributors:
+//  Peter R. Bloomfield
+//
+
+//
+// When receiving a link message, this script will load a URL for a given user.
+// The URL should be in the string, and the user specified by their key.
+// The integer number in the link message should be SLOODLE_CHANNEL_OBJECT_LOAD_URL
+//
+// NOTE: you MUST customize the "MY_CHANNEL_INDEX" value to indicate which script number this is.
+//  Each of these scripts in a given object should have a unique channel index.
+//  This identifies where the object appears in the sequence.
+//  Channel indices should be numbered sequentially from 0.
+//  It is recommended that you replace the "x" in the script name to correspond to the channel index.
+
+
+///// DATA /////
+
+// The index of the channel this script will receive requests on
+// (NOTE: corresponds to indices in the list below)
+integer MY_CHANNEL_INDEX = 2;
+
+
+// OUTGOING link message numbers for parallel load requests
+list SLOODLE_CHANNEL_LIST_LOAD_URL_x =  [
+                                        -1699000001, //SLOODLE_CHANNEL_LOAD_URL_0
+                                        -1699000002, //SLOODLE_CHANNEL_LOAD_URL_1
+                                        -1699000003, //SLOODLE_CHANNEL_LOAD_URL_2
+                                        -1699000004, //SLOODLE_CHANNEL_LOAD_URL_3
+                                        -1699000005, //SLOODLE_CHANNEL_LOAD_URL_4
+                                        -1699000006, //SLOODLE_CHANNEL_LOAD_URL_5
+                                        -1699000007, //SLOODLE_CHANNEL_LOAD_URL_6
+                                        -1699000008, //SLOODLE_CHANNEL_LOAD_URL_7
+                                        -1699000009, //SLOODLE_CHANNEL_LOAD_URL_8
+                                        -1699000010  //SLOODLE_CHANNEL_LOAD_URL_9
+                                        ];
+
+///// ---- /////
+
+default
+{
+    state_entry()
+    {
+        if (MY_CHANNEL_INDEX < 0 || MY_CHANNEL_INDEX > llGetListLength(SLOODLE_CHANNEL_LIST_LOAD_URL_x)) {
+            llOwnerSay("WARNING: script \"" + llGetScriptName() + "\" has an invalid channel index. Please alter value \"MY_CHANNEL_INDEX\".");
+        }
+    }
+    
+    link_message( integer sender_num, integer msg_num, string str, key id )
+    {
+        // Ignore this message if the variables are empty
+        if (str == "" || id == NULL_KEY) return;
+        // If this message is not on the correct channel, then ignore it
+        if (msg_num != llList2Integer(SLOODLE_CHANNEL_LIST_LOAD_URL_x, MY_CHANNEL_INDEX)) return;
+        
+        // Load the URL
+        llLoadURL(id, "", str);
+    }
+}
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: assets/sloodle_parallel_url_loader_2.lslp 

+ 72 - 0
assets/sloodle_parallel_url_loader_3.lslp

@@ -0,0 +1,72 @@
+//
+// The line above should be left blank to avoid script errors in OpenSim.
+
+// Sloodle parallel URL loader
+// Allows the loading of a single URL on a specified channel.
+// (NOTE: should be in the same prim as a "sloodle_multi_url_loader.lsl" script)
+// Part of the Sloodle project (www.sloodle.org)
+//
+// Copyright (c) 2007 Sloodle (various contributors)
+// Released under the GNU GPL
+//
+// Contributors:
+//  Peter R. Bloomfield
+//
+
+//
+// When receiving a link message, this script will load a URL for a given user.
+// The URL should be in the string, and the user specified by their key.
+// The integer number in the link message should be SLOODLE_CHANNEL_OBJECT_LOAD_URL
+//
+// NOTE: you MUST customize the "MY_CHANNEL_INDEX" value to indicate which script number this is.
+//  Each of these scripts in a given object should have a unique channel index.
+//  This identifies where the object appears in the sequence.
+//  Channel indices should be numbered sequentially from 0.
+//  It is recommended that you replace the "x" in the script name to correspond to the channel index.
+
+
+///// DATA /////
+
+// The index of the channel this script will receive requests on
+// (NOTE: corresponds to indices in the list below)
+integer MY_CHANNEL_INDEX = 3;
+
+
+// OUTGOING link message numbers for parallel load requests
+list SLOODLE_CHANNEL_LIST_LOAD_URL_x =  [
+                                        -1699000001, //SLOODLE_CHANNEL_LOAD_URL_0
+                                        -1699000002, //SLOODLE_CHANNEL_LOAD_URL_1
+                                        -1699000003, //SLOODLE_CHANNEL_LOAD_URL_2
+                                        -1699000004, //SLOODLE_CHANNEL_LOAD_URL_3
+                                        -1699000005, //SLOODLE_CHANNEL_LOAD_URL_4
+                                        -1699000006, //SLOODLE_CHANNEL_LOAD_URL_5
+                                        -1699000007, //SLOODLE_CHANNEL_LOAD_URL_6
+                                        -1699000008, //SLOODLE_CHANNEL_LOAD_URL_7
+                                        -1699000009, //SLOODLE_CHANNEL_LOAD_URL_8
+                                        -1699000010  //SLOODLE_CHANNEL_LOAD_URL_9
+                                        ];
+
+///// ---- /////
+
+default
+{
+    state_entry()
+    {
+        if (MY_CHANNEL_INDEX < 0 || MY_CHANNEL_INDEX > llGetListLength(SLOODLE_CHANNEL_LIST_LOAD_URL_x)) {
+            llOwnerSay("WARNING: script \"" + llGetScriptName() + "\" has an invalid channel index. Please alter value \"MY_CHANNEL_INDEX\".");
+        }
+    }
+    
+    link_message( integer sender_num, integer msg_num, string str, key id )
+    {
+        // Ignore this message if the variables are empty
+        if (str == "" || id == NULL_KEY) return;
+        // If this message is not on the correct channel, then ignore it
+        if (msg_num != llList2Integer(SLOODLE_CHANNEL_LIST_LOAD_URL_x, MY_CHANNEL_INDEX)) return;
+        
+        // Load the URL
+        llLoadURL(id, "", str);
+    }
+}
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: assets/sloodle_parallel_url_loader_3.lslp 

+ 72 - 0
assets/sloodle_parallel_url_loader_x.lslp

@@ -0,0 +1,72 @@
+//
+// The line above should be left blank to avoid script errors in OpenSim.
+
+// Sloodle parallel URL loader
+// Allows the loading of a single URL on a specified channel.
+// (NOTE: should be in the same prim as a "sloodle_multi_url_loader.lsl" script)
+// Part of the Sloodle project (www.sloodle.org)
+//
+// Copyright (c) 2007 Sloodle (various contributors)
+// Released under the GNU GPL
+//
+// Contributors:
+//  Peter R. Bloomfield
+//
+
+//
+// When receiving a link message, this script will load a URL for a given user.
+// The URL should be in the string, and the user specified by their key.
+// The integer number in the link message should be SLOODLE_CHANNEL_OBJECT_LOAD_URL
+//
+// NOTE: you MUST customize the "MY_CHANNEL_INDEX" value to indicate which script number this is.
+//  Each of these scripts in a given object should have a unique channel index.
+//  This identifies where the object appears in the sequence.
+//  Channel indices should be numbered sequentially from 0.
+//  It is recommended that you replace the "x" in the script name to correspond to the channel index.
+
+
+///// DATA /////
+
+// The index of the channel this script will receive requests on
+// (NOTE: corresponds to indices in the list below)
+integer MY_CHANNEL_INDEX = -1;
+
+
+// OUTGOING link message numbers for parallel load requests
+list SLOODLE_CHANNEL_LIST_LOAD_URL_x =  [
+                                        -1699000001, //SLOODLE_CHANNEL_LOAD_URL_0
+                                        -1699000002, //SLOODLE_CHANNEL_LOAD_URL_1
+                                        -1699000003, //SLOODLE_CHANNEL_LOAD_URL_2
+                                        -1699000004, //SLOODLE_CHANNEL_LOAD_URL_3
+                                        -1699000005, //SLOODLE_CHANNEL_LOAD_URL_4
+                                        -1699000006, //SLOODLE_CHANNEL_LOAD_URL_5
+                                        -1699000007, //SLOODLE_CHANNEL_LOAD_URL_6
+                                        -1699000008, //SLOODLE_CHANNEL_LOAD_URL_7
+                                        -1699000009, //SLOODLE_CHANNEL_LOAD_URL_8
+                                        -1699000010  //SLOODLE_CHANNEL_LOAD_URL_9
+                                        ];
+
+///// ---- /////
+
+default
+{
+    state_entry()
+    {
+        if (MY_CHANNEL_INDEX < 0 || MY_CHANNEL_INDEX > llGetListLength(SLOODLE_CHANNEL_LIST_LOAD_URL_x)) {
+            llOwnerSay("WARNING: script \"" + llGetScriptName() + "\" has an invalid channel index. Please alter value \"MY_CHANNEL_INDEX\".");
+        }
+    }
+    
+    link_message( integer sender_num, integer msg_num, string str, key id )
+    {
+        // Ignore this message if the variables are empty
+        if (str == "" || id == NULL_KEY) return;
+        // If this message is not on the correct channel, then ignore it
+        if (msg_num != llList2Integer(SLOODLE_CHANNEL_LIST_LOAD_URL_x, MY_CHANNEL_INDEX)) return;
+        
+        // Load the URL
+        llLoadURL(id, "", str);
+    }
+}
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: assets/sloodle_parallel_url_loader_x.lslp 

+ 162 - 0
assets/sloodle_setup_notecard.lslp

@@ -0,0 +1,162 @@
+//
+// The line above should be left blank to avoid script errors in OpenSim.
+
+// Sloodle configuration notecard reader
+// Reads a configuration notecard and transmits the data via link messages to other scripts
+// If the notecard changes, then it automatically resets.
+//
+// Part of the Sloodle project (www.sloodle.org)
+// Copyright (c) 2007-8 Sloodle
+// Released under the GNU GPL v3
+//
+// Contributors:
+//  Edmund Edgar
+//  Peter R. Bloomfield
+
+
+integer SLOODLE_CHANNEL_OBJECT_DIALOG = -3857343;
+string SLOODLE_CONFIG_NOTECARD = "sloodle_config";
+string SLOODLE_EOF = "sloodleeof";
+
+key sloodle_notecard_key = NULL_KEY;
+integer sloodle_notecard_line = 0;
+
+string COMMENT_PREFIX = "//";
+
+key latestnotecard = NULL_KEY; // The most recently read notecard
+
+
+
+///// TRANSLATION /////
+
+// Link message channels
+integer SLOODLE_CHANNEL_TRANSLATION_REQUEST = -1928374651;
+integer SLOODLE_CHANNEL_TRANSLATION_RESPONSE = -1928374652;
+
+// Translation output methods
+string SLOODLE_TRANSLATE_LINK = "link";             // No output parameters - simply returns the translation on SLOODLE_TRANSLATION_RESPONSE link message channel
+string SLOODLE_TRANSLATE_SAY = "say";               // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_WHISPER = "whisper";       // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_SHOUT = "shout";           // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_REGION_SAY = "regionsay";  // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_OWNER_SAY = "ownersay";    // No output parameters
+string SLOODLE_TRANSLATE_DIALOG = "dialog";         // Recipient avatar should be identified in link message keyval. At least 2 output parameters: first the channel number for the dialog, and then 1 to 12 button label strings.
+string SLOODLE_TRANSLATE_LOAD_URL = "loadurl";      // Recipient avatar should be identified in link message keyval. 1 output parameter, containing the URL.
+string SLOODLE_TRANSLATE_HOVER_TEXT = "hovertext";  // 2 output parameters: colour <r,g,b>, and alpha value
+
+// Send a translation request link message
+sloodle_translation_request(string output_method, list output_params, string string_name, list string_params, key keyval, string batch)
+{
+    llMessageLinked(LINK_THIS, SLOODLE_CHANNEL_TRANSLATION_REQUEST, output_method + "|" + llList2CSV(output_params) + "|" + string_name + "|" + llList2CSV(string_params) + "|" + batch, keyval);
+}
+
+///// ----------- /////
+
+
+///// FUNCTIONS /////
+
+
+sloodle_tell_other_scripts(string msg)
+{
+    sloodle_debug("notecard sending message to other scripts: "+msg);
+    llMessageLinked(LINK_SET, SLOODLE_CHANNEL_OBJECT_DIALOG, msg, NULL_KEY);   
+}
+
+sloodle_debug(string msg)
+{
+    //llWhisper(0,msg);
+}
+
+sloodle_handle_command(string str) 
+{
+    list bits = llParseString2List(str,["|"],[]);
+    integer numbits = llGetListLength(bits);
+    
+    string name = llList2String(bits,0);
+    string value = "";
+    if (numbits >= 2) llList2String(bits,1);
+
+    // Check the command
+    if (name == "do:reset") {
+        // Reset
+        sloodle_debug("Resetting configuration notecard reader");
+        llResetScript();
+    } else if (name == "do:requestconfig") {
+        // Configuration request
+        sloodle_start_reading_notecard();
+    }
+}
+
+sloodle_start_reading_notecard()
+{
+    // Do we have a configuratio notecard?
+    if (llGetInventoryType(SLOODLE_CONFIG_NOTECARD) == INVENTORY_NOTECARD) {
+        // Start reading it
+        sloodle_translation_request(SLOODLE_TRANSLATE_HOVER_TEXT, [<0.0,1.0,0.0>, 1.0], "readingconfignotecard", [], NULL_KEY, "");
+        sloodle_debug("starting reading notecard");
+        sloodle_notecard_line = 0;
+        sloodle_notecard_key = llGetNotecardLine("sloodle_config", 0); // read the first line. The dataserver event will get the next one.
+        latestnotecard = llGetInventoryKey(SLOODLE_CONFIG_NOTECARD);
+    } else {
+        sloodle_debug("No notecard called "+SLOODLE_CONFIG_NOTECARD+" found - skipping notecard configuration");
+        latestnotecard = NULL_KEY;
+    }
+}
+
+
+default 
+{
+    on_rez(integer start_param)
+    {
+        llResetScript();
+    }
+    
+    state_entry() 
+    {
+        // Pause for a moment, in case all scripts were reset at the same time
+        llSleep(0.2);
+        // Go!
+        sloodle_start_reading_notecard();
+    }
+    
+    dataserver(key requested, string data)
+    {
+        if ( requested == sloodle_notecard_key )  // make sure we are getting the data we want
+        {
+            sloodle_notecard_key = NULL_KEY;
+            if ( data != EOF )
+            {
+                // If this is a comment line, then do not forward it
+                string trimmeddata = llStringTrim(data, STRING_TRIM_HEAD);
+                if (llSubStringIndex(trimmeddata, COMMENT_PREFIX) != 0) sloodle_tell_other_scripts(data);
+                // Advance to the next line
+                sloodle_notecard_line++;
+                sloodle_notecard_key = llGetNotecardLine("sloodle_config",sloodle_notecard_line);
+            } else {
+                // This is the end of the configuration data
+                llSleep(0.2);
+                sloodle_tell_other_scripts(SLOODLE_EOF);
+                llSetText("", <0.0,0.0,0.0>, 0.0);
+            }
+        }
+    }
+    
+    link_message(integer sender_num, integer num, string str, key id) {
+        // Ignore debug messages
+        if (num == DEBUG_CHANNEL) return;
+        // Is this an object dialog message?
+        if (num == SLOODLE_CHANNEL_OBJECT_DIALOG) {
+            sloodle_handle_command(str);
+        }
+    }
+    
+    changed(integer change) {
+        // If the inventory is changed, and we have a Sloodle config notecard, then use it to re-initialise
+        if (change & CHANGED_INVENTORY && llGetInventoryType(SLOODLE_CONFIG_NOTECARD) == INVENTORY_NOTECARD) {
+            // If the current notecard is not the same as the one we read most recently, then reset
+            if (llGetInventoryKey(SLOODLE_CONFIG_NOTECARD) != latestnotecard) llResetScript();
+        }
+    }
+}
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: assets/sloodle_setup_notecard.lslp 

+ 819 - 0
assets/sloodle_setup_web.lslp

@@ -0,0 +1,819 @@
+//
+// The line above should be left blank to avoid script errors in OpenSim.
+
+// Sloodle web-configuration script
+// In the absence of a configuration notecard, allows an object to be authorised/configured via the web
+// Part of the Sloodle project (www.sloodle.org)
+//
+// Copyright (c) 2008 Sloodle
+// Released under the GNU GPL
+//
+// Contributors:
+//  Peter R. Bloomfield
+//  Edmund Edgar
+//  Paul Preibisch - Fire Centaur in SL
+
+integer SLOODLE_CHANNEL_ERROR_TRANSLATION_REQUEST=-1828374651; // this channel is used to send status codes for translation to the error_messages lsl script
+integer SLOODLE_CHANNEL_OBJECT_DIALOG = -3857343;
+string SLOODLE_CONFIG_NOTECARD = "sloodle_config";
+string SLOODLE_EOF = "sloodleeof";
+integer SLOODLE_CHANNEL_AVATAR_DIALOG = 1001;
+
+string SLOODLE_VERSION_LINKER = "/mod/sloodle/version_linker.php";
+string SLOODLE_AUTH_LINKER = "/mod/sloodle/classroom/auth_object_linker.php";
+string SLOODLE_CONFIG_INTERFACE = "/mod/sloodle/classroom/configure_object.php";
+string SLOODLE_CONFIG_LINKER = "/mod/sloodle/classroom/object_config_linker.php";
+string SLOODLE_PING_LINKER = "/mod/sloodle/classroom/active_object_ping_linker.php";
+
+float PING_DELAY = 10800.0; // Number of seconds between pings (does not need to be very frequent)
+
+string SLOODLE_SCRIPT_PREFIX = "sloodle_mod_";
+
+float SLOODLE_VERSION_MIN = 0.3; // Minimum required version of Sloodle
+
+key httpcheckmoodle = NULL_KEY;
+key httpauthobject = NULL_KEY;
+key httpconfig = NULL_KEY;
+
+string sloodleserverroot = "";
+string sloodlepwd = ""; // stores the object-specific session key (UUID|pwd)
+string sloodleauthid = ""; // The ID which is passed to Moodle in the URL for the user authorisation step
+key sloodlemyrezzer = NULL_KEY; // Stores the UUID of the object that rezzed this one (if applicable)
+integer sloodlecontrollerid = 0; // So we know which Controller to access when PING-in the server later
+
+string password = ""; // stores the self-generated part of the password
+
+integer request_config = FALSE; // This is used when jumping from the idle state back to a configuration request
+integer show_config_url = TRUE; // By default, we will automatically show the config URL to the owner when config starts
+integer url_shown = FALSE; // At first we simply want to show the URL, but thereafter we can show the menu
+string SLOODLE_OBJECT_TYPE = "";
+
+
+
+
+///// TRANSLATION /////
+
+// Link message channels
+integer SLOODLE_CHANNEL_TRANSLATION_REQUEST = -1928374651;
+integer SLOODLE_CHANNEL_TRANSLATION_RESPONSE = -1928374652;
+
+// Translation output methods
+string SLOODLE_TRANSLATE_LINK = "link";             // No output parameters - simply returns the translation on SLOODLE_TRANSLATION_RESPONSE link message channel
+string SLOODLE_TRANSLATE_SAY = "say";               // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_WHISPER = "whisper";       // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_SHOUT = "shout";           // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_REGION_SAY = "regionsay";  // 1 output parameter: chat channel number
+string SLOODLE_TRANSLATE_OWNER_SAY = "ownersay";    // No output parameters
+string SLOODLE_TRANSLATE_DIALOG = "dialog";         // Recipient avatar should be identified in link message keyval. At least 2 output parameters: first the channel number for the dialog, and then 1 to 12 button label strings.
+string SLOODLE_TRANSLATE_LOAD_URL = "loadurl";      // Recipient avatar should be identified in link message keyval. 1 output parameter, containing the URL.
+string SLOODLE_TRANSLATE_HOVER_TEXT = "hovertext";  // 2 output parameters: colour <r,g,b>, and alpha value
+
+//FUNCTIONS
+/******************************************************************************************************************************
+* sloodle_error_code - 
+* Author: Paul Preibisch
+* Description - This function sends a linked message on the SLOODLE_CHANNEL_ERROR_TRANSLATION_REQUEST channel
+* The error_messages script hears this, translates the status code and sends an instant message to the avuuid
+* Params: method - SLOODLE_TRANSLATE_SAY, SLOODLE_TRANSLATE_IM etc
+* Params:  avuuid - this is the avatar UUID to that an instant message with the translated error code will be sent to
+* Params: status code - the status code of the error as on our wiki: http://slisweb.sjsu.edu/sl/index.php/Sloodle_status_codes
+*******************************************************************************************************************************/
+sloodle_error_code(string method, key avuuid,integer statuscode){
+            llMessageLinked(LINK_SET, SLOODLE_CHANNEL_ERROR_TRANSLATION_REQUEST, method+"|"+(string)avuuid+"|"+(string)statuscode, NULL_KEY);
+}
+
+// Send a translation request link message
+sloodle_translation_request(string output_method, list output_params, string string_name, list string_params, key keyval, string batch)
+{
+    llMessageLinked(LINK_THIS, SLOODLE_CHANNEL_TRANSLATION_REQUEST, output_method + "|" + llList2CSV(output_params) + "|" + string_name + "|" + llList2CSV(string_params) + "|" + batch, keyval);
+}
+
+///// ----------- /////
+
+
+sloodle_tell_other_scripts(string msg)
+{
+    sloodle_debug("Web configuration sending message to other scripts: "+msg);
+    llMessageLinked(LINK_SET, SLOODLE_CHANNEL_OBJECT_DIALOG, msg, NULL_KEY);   
+}
+
+sloodle_debug(string msg)
+{
+    //llWhisper(0,msg);
+}
+
+// Determines if the objet has a configuration notecard
+// Returns true if so, or false otherwise
+integer sloodle_has_config_notecard()
+{
+    return (llGetInventoryType(SLOODLE_CONFIG_NOTECARD) == INVENTORY_NOTECARD);
+}
+
+// Generate a random password string
+string sloodle_random_object_password()
+{
+    return (string)(10000 + (integer)llFrand(999989999)); // Gets a random integer between 10000 and 999999999
+}
+
+// Show a menu letting the user choose between configuring the object, and downloading the configuration into SL
+sloodle_show_config_menu(key av)
+{
+    sloodle_translation_request(SLOODLE_TRANSLATE_DIALOG, [SLOODLE_CHANNEL_AVATAR_DIALOG, "0", "1"], "webconfigmenu", ["0", "1"], av, "");
+    llListen(SLOODLE_CHANNEL_AVATAR_DIALOG, "", av, "0");
+    llListen(SLOODLE_CHANNEL_AVATAR_DIALOG, "", av, "1");
+}
+
+// Load the configuration URL
+sloodle_load_config_url(key av)
+{
+    string url = sloodleserverroot + SLOODLE_CONFIG_INTERFACE + "?sloodleauthid=" + sloodleauthid + "&sloodleobjtype=" + SLOODLE_OBJECT_TYPE;
+    sloodle_translation_request(SLOODLE_TRANSLATE_LOAD_URL, [url], "configlink", [], av, "");
+}
+
+// Check to see what type this script is to use
+// Returns the type/version identifier as a string, e.g. "chat-1.0"
+string sloodle_check_type()
+{
+    // Find out how many scripts there are
+    integer numscripts = llGetInventoryNumber(INVENTORY_SCRIPT);
+    string type = "";
+    string itemname = "";
+    
+    // Go through each item
+    integer i = 0;
+    for (i = 0; i < numscripts; i++) {
+        // Get the name of this item
+        itemname = llGetInventoryName(INVENTORY_SCRIPT, i);
+        // Does this have the necessary prefix?
+        if (llSubStringIndex(itemname, SLOODLE_SCRIPT_PREFIX) == 0) {
+            // Ignore the script if it's not running... unless we don't already have a type identified
+            if (llGetScriptState(itemname) == TRUE || type == "") {
+                // Looks like this is our type
+                type = llGetSubString(itemname, llStringLength(SLOODLE_SCRIPT_PREFIX), -1);
+            }
+        }
+    }
+    
+    return type;
+}
+
+
+default
+{    
+    state_entry() 
+    {
+        // Pause for a moment, in case all scripts were reset at the same time
+        llSleep(0.2);
+        sloodleserverroot = "";
+    
+        // Attempt to get the object type if we don't already have it
+        if (SLOODLE_OBJECT_TYPE == "") {
+            SLOODLE_OBJECT_TYPE = sloodle_check_type();
+            if (SLOODLE_OBJECT_TYPE == "") {
+                sloodle_translation_request(SLOODLE_TRANSLATE_WHISPER, [0], "notypeid", [], NULL_KEY, "");
+                
+            } else {
+                sloodle_translation_request(SLOODLE_TRANSLATE_WHISPER, [0], "gottype", [SLOODLE_OBJECT_TYPE], NULL_KEY, "");
+            }
+        }
+    
+        // Listen for anything on the object dialog channel
+        llListen(SLOODLE_CHANNEL_OBJECT_DIALOG, "", NULL_KEY, "");
+        request_config = FALSE;
+        
+        // Do we have a configuration notecard?
+        if (!sloodle_has_config_notecard()) {
+            // No - if we have no starting parameter, then invite the user to use web-configuration
+            if (llGetStartParameter() == 0) {
+                sloodle_translation_request(SLOODLE_TRANSLATE_HOVER_TEXT, [<0.0,1.0,0.0>, 1.0], "touchforwebconfig", [], NULL_KEY, "");
+            }
+        }
+    }
+    
+    state_exit()
+    {
+        llSetText("", <0.0,0.0,0.0>, 0.0);
+    }
+    
+    listen(integer channel, string name, key id, string msg)
+    {        
+        // Check the channel
+        if (channel == 0 || channel == 1) {
+            // Ignore anybody but the owner
+            if (id != llGetOwner()) return;
+            show_config_url = TRUE;
+            // If the message starts with "http" then store it as the Moodle address
+            msg = llStringTrim(msg, STRING_TRIM);
+            if (llSubStringIndex(msg, "http") == 0) {
+
+                // If the message ends with a slash, remove it.
+                if ( llGetSubString(msg, -1, -1) == "/" ) {
+                    msg = llGetSubString(msg, 0, -2);    
+                }            
+                
+                sloodleserverroot = msg;
+                state check_moodle;
+                return;
+            }
+            
+        } else if (channel == SLOODLE_CHANNEL_OBJECT_DIALOG) {
+        
+            // Check for standard messages, then for anything else
+            if (msg == "CLEANUP") {
+                // Did it come from the rezzer?
+                if (id == sloodlemyrezzer && id != NULL_KEY) {
+                    // Delete this object
+                    sloodle_debug("Received CLEANUP command from object \"" + name + "\".");
+                    llDie();
+                }
+            } else {
+                // Ignore anything not owned by the same person
+                if (llGetOwnerKey(id) != llGetOwner()) return;
+                // This should be a Sloodle initialisation message:
+                //  sloodle_init|<rezzer>|<target-uuid>|<moodle-address>|<authid>
+                // (the last part is optional)
+                list parts = llParseStringKeepNulls(msg, ["|"], []);
+                integer numparts = llGetListLength(parts);
+                string cmd = llList2String(parts, 0);
+                
+                // Check what the command is
+                if (cmd == "sloodle_init") {
+                    // Make sure we have enough parts in the message
+                    if (llGetListLength(parts) < 4) return;
+                    key rezzer = (key)llList2String(parts, 1);
+                    key target = (key)llList2String(parts, 2);
+                    string url = llList2String(parts, 3);
+                    string auth = "";
+                    string isconfigured = "0";
+                    if (numparts >= 5) auth = llList2String(parts, 4);
+                    if (numparts >= 6) isconfigured = llList2String(parts, 5);
+                                        
+                    // Make sure the command is correct, the UUIDs are OK, and that the URL looks valid
+                    if (rezzer == NULL_KEY) return;
+                    if (target != llGetKey()) return;
+                    url = llStringTrim(url, STRING_TRIM);
+                    if (llSubStringIndex(url, "http") != 0) return;
+                    
+                    // Store the settings
+                    sloodleserverroot = url;
+                    sloodleauthid = auth;
+                    sloodlemyrezzer = rezzer;
+                    
+                    // Do we have a password *and* an authorisation ID?
+                    if (llGetStartParameter() != 0 && (integer)auth > 0) {
+                        // Store the password
+                        password = (string)llGetStartParameter();
+                        sloodlepwd = (string)llGetKey() + "|" + password;
+                        
+                        if (isconfigured == "1") {
+                            request_config = TRUE; // we're already configured by a layout, so we can go right ahead and get our config off the server
+                        } 
+                        
+                        // Allow the user to configure the object
+                        show_config_url = FALSE;
+                        state configure_object;
+                    
+                        return;
+                    }
+                    
+                    // Begin self-authorisation
+                    state check_moodle;
+                }
+            }
+        }
+    }
+    
+    touch_start(integer num_detected)
+    {
+        // Only pay attention to the object owner
+        if (llDetectedKey(0) != llGetOwner()) return;
+        // Do nothing if there is a configuration script present
+        if (sloodle_has_config_notecard()) return;
+        
+        // We can do nothing without a server root
+        if (sloodleserverroot == "") {
+            llListen(0, "", llGetOwner(), "");
+            llListen(1, "", llGetOwner(), "");
+            
+            sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "chatserveraddress", [], NULL_KEY, "");
+            sloodle_translation_request(SLOODLE_TRANSLATE_HOVER_TEXT, [<0.0,1.0,0.0>, 0.8], "waitingforserveraddress", [], NULL_KEY, "");
+            return;
+        }
+        
+        // Start the configuration process
+        state check_moodle;
+    }
+    
+    link_message(integer sender_num, integer num, string sval, key kval)
+    {
+        // Check the channel
+        if (num == SLOODLE_CHANNEL_OBJECT_DIALOG) {
+            // Is it a reset command?
+            if (sval == "do:reset") {
+                llResetScript();
+                return;
+            }
+        }
+    }
+}
+
+// Check that the Moodle site is valid
+state check_moodle
+{
+    state_entry()
+    {
+        sloodle_translation_request(SLOODLE_TRANSLATE_HOVER_TEXT, [<0.0,1.0,0.0>, 0.8], "checkingserverat", [sloodleserverroot], NULL_KEY, "");
+        httpcheckmoodle = llHTTPRequest(sloodleserverroot + SLOODLE_VERSION_LINKER, [HTTP_METHOD, "GET"], "");
+        // Listen for chat messages from the rezzer
+        if (sloodlemyrezzer != NULL_KEY) llListen(SLOODLE_CHANNEL_OBJECT_DIALOG, "", NULL_KEY, "");
+    }
+    
+    state_exit()
+    {
+        llSetText("", <0.0,0.0,0.0>, 0.0);
+        httpcheckmoodle = NULL_KEY;
+    }
+    
+    http_response(key id, integer status, list meta, string body)
+    {
+        // Make sure it's the response we're expecting
+        if (id != httpcheckmoodle) return;
+        httpcheckmoodle = NULL_KEY;
+        // Check the status code
+        if (status != 200) {
+            sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "httperror:code", [status], NULL_KEY, "");
+            return;
+        }
+        
+        // Split the response into lines and get the status line info
+        list lines = llParseStringKeepNulls(body, ["\n"], []);
+        integer numlines = llGetListLength(lines);
+        list statusfields = llParseStringKeepNulls(llList2String(lines, 0), ["|"], []);
+        integer statuscode = (integer)llList2String(statusfields, 0);
+        
+        // Make sure the status code was OK
+        if (statuscode == -106) {
+            sloodle_debug("ERROR -106: the Sloodle module is not installed on the specified Moodle site (" + sloodleserverroot + ")");
+            sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "sloodlenotinstalled", [], NULL_KEY, "");
+            return;
+        } else if (statuscode <= 0) {
+            sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "failedcheckcompatibility", [], NULL_KEY, "");
+            sloodle_error_code(SLOODLE_TRANSLATE_SAY, NULL_KEY,statuscode); //send message to error_message.lsl
+            return;
+        }
+        
+        // Make sure we have enough other data
+        if (numlines < 2) {
+            sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "badresponseformat", [], NULL_KEY, "");
+            return;
+        }
+        
+        // Extract the Sloodle version number
+        list datafields = llParseStringKeepNulls(llList2String(lines, 1), ["|"], []);
+        float installedversion = (float)llList2String(datafields, 0);
+        
+        // Check compatibility
+        if (installedversion < SLOODLE_VERSION_MIN) {
+            sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "sloodleversioninstalled", [installedversion], NULL_KEY, "");
+            sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "sloodleversionrequired", [SLOODLE_VERSION_MIN], NULL_KEY, "");
+            return;
+        }
+        
+        // Initiate object authorisation
+        state auth_object_initial;
+    }
+    
+    touch_start(integer num_detected)
+    {
+        // Revert to the default state if the owner touched
+        if (llDetectedKey(0) != llGetOwner()) return;
+        state default;
+    }
+    
+    link_message(integer sender_num, integer num, string sval, key kval)
+    {
+        // Check the channel
+        if (num == SLOODLE_CHANNEL_OBJECT_DIALOG) {
+            // Is it a reset command?
+            if (sval == "do:reset") {
+                llResetScript();
+                return;
+            }
+        }
+    }
+    
+    listen(integer channel, string name, key id, string msg)
+    {
+        // Check the channel
+        if (channel == SLOODLE_CHANNEL_OBJECT_DIALOG) {
+            // Parse the message
+            list parts = llParseStringKeepNulls(msg, ["|"], []);
+            if (llGetListLength(parts) < 2) return;
+            string cmd = llList2String(parts, 0);
+            string val = llList2String(parts, 1);
+        
+            // Is it a recognised command?
+            if (cmd == "do:cleanup") {
+                // Did it come from our rezzer?
+                key kval = (key)val;
+                if (kval == sloodlemyrezzer && kval != NULL_KEY) {
+                    // Delete this object
+                    sloodle_debug("Received CLEANUP command from object \"" + name + "\".");
+                    llDie();
+                    return;
+                }
+            }
+        }
+    }
+    
+    on_rez(integer start_param)
+    {
+        llResetScript();
+    }
+}
+
+// Initial object authorisation (stores details in the database)
+state auth_object_initial
+{
+    state_entry()
+    {
+        sloodle_translation_request(SLOODLE_TRANSLATE_HOVER_TEXT, [<0.0, 1.0, 0.0>, 0.8], "initobjectauth", [], NULL_KEY, "");
+        
+        // Generate a random password
+        password = sloodle_random_object_password();
+        sloodlepwd = (string)llGetKey() + "|" + password;
+        // Initiate the object authorisation
+        string body = "sloodleobjuuid="+(string)llGetKey()+"&sloodleobjname="+llGetObjectName()+"&sloodleobjpwd="+password+"&sloodleobjtype="+SLOODLE_OBJECT_TYPE;
+        httpauthobject = llHTTPRequest(sloodleserverroot + SLOODLE_AUTH_LINKER, [HTTP_METHOD, "POST", HTTP_MIMETYPE, "application/x-www-form-urlencoded"], body);
+        
+        // Listen for chat messages from the rezzer
+        if (sloodlemyrezzer != NULL_KEY) llListen(SLOODLE_CHANNEL_OBJECT_DIALOG, "", NULL_KEY, "");
+    }
+    
+    state_exit()
+    {
+        llSetText("", <0.0,0.0,0.0>, 0.0);
+        httpauthobject = NULL_KEY;
+    }
+    
+    http_response(key id, integer status, list meta, string body)
+    {
+        // Make sure this is the response we're expecting
+        if (id != httpauthobject) return;
+        if (status != 200) {
+            sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "httperror:code", [status], NULL_KEY, "");
+            return;
+        }
+        
+        // Split the response into lines
+        list lines = llParseStringKeepNulls(body, ["\n"], []);
+        integer numlines = llGetListLength(lines);
+        list statusfields = llParseStringKeepNulls(llList2String(lines, 0), ["|"], []);
+        integer statuscode = (integer)llList2String(statusfields, 0);
+        
+        // Check the statuscode
+        if (statuscode <= 0) {
+            sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "objectauthfailed:code", [statuscode], NULL_KEY, "");
+            sloodle_error_code(SLOODLE_TRANSLATE_SAY, NULL_KEY,statuscode); //send message to error_message.lsl
+            return;
+        }
+        
+        // Attempt to get the auth ID
+        if (numlines < 2) {
+            sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "badresponseformat", [], NULL_KEY, "");
+            return;
+        }
+        sloodleauthid = llList2String(lines, 1);
+        
+        // Start the configuration
+        state configure_object;
+    }
+    
+    touch_start(integer num_detected)
+    {
+        // Revert to the default state if the owner touched
+        if (llDetectedKey(0) != llGetOwner()) return;
+        state default;
+    }
+    
+    link_message(integer sender_num, integer num, string sval, key kval)
+    {
+        // Check the channel
+        if (num == SLOODLE_CHANNEL_OBJECT_DIALOG) {
+            // Is it a reset command?
+            if (sval == "do:reset") {
+                llResetScript();
+                return;
+            }
+        }
+    }
+    
+    on_rez(integer start_param)
+    {
+        llResetScript();
+    }
+    
+    listen(integer channel, string name, key id, string msg)
+    {
+        // Check the channel
+        if (channel == SLOODLE_CHANNEL_OBJECT_DIALOG) {
+            // Parse the message
+            list parts = llParseStringKeepNulls(msg, ["|"], []);
+            if (llGetListLength(parts) < 2) return;
+            string cmd = llList2String(parts, 0);
+            string val = llList2String(parts, 1);
+        
+            // Is it a recognised command?
+            if (cmd == "do:cleanup") {
+                // Did it come from our rezzer?
+                key kval = (key)val;
+                if (kval == sloodlemyrezzer && kval != NULL_KEY) {
+                    // Delete this object
+                    sloodle_debug("Received CLEANUP command from object \"" + name + "\".");
+                    llDie();
+                    return;
+                }
+            }
+        }
+    }
+}
+
+
+// Send the user to the configuration page on the Moodle site.
+// (That page will present authorisation options as necessary).
+// If touched, ask the user if they want the URL again, or to download the configuration.
+state configure_object
+{
+    state_entry()
+    {
+        url_shown = FALSE;
+        sloodlecontrollerid = 0;
+        
+        // Has object configuration been requested?
+        if (request_config) {
+            request_config = FALSE;
+            llSetText("Requesting configuration...", <0.0,1.0,0.0>, 0.8);
+            httpconfig = llHTTPRequest(sloodleserverroot + SLOODLE_CONFIG_LINKER + "?sloodlepwd="+sloodlepwd+"&sloodleauthid="+sloodleauthid, [HTTP_METHOD, "GET"], "");
+        } else {
+            llSetText("Waiting for configuration.\nTouch me for a URL, or to download the configuration.", <0.0,1.0,0.0>, 0.8);
+            // Load the URL immediately 
+            if (show_config_url) {
+                sloodle_load_config_url(llGetOwner());
+                url_shown = TRUE;
+            }
+        }
+        
+        // Listen for chat messages from the rezzer
+        if (sloodlemyrezzer != NULL_KEY) llListen(SLOODLE_CHANNEL_OBJECT_DIALOG, "", NULL_KEY, "");
+    }
+    
+    state_exit()
+    {
+        llSetText("", <0.0,0.0,0.0>, 0.0);
+        httpconfig = NULL_KEY;
+    }
+    
+    touch_start(integer num_detected)
+    {
+        // Ignore anybody but the owner
+        if (llDetectedKey(0) != llGetOwner()) return;
+        // If the URL has already been shown, then display the menu.
+        // Otherwise, offer the URL
+        if (url_shown) sloodle_show_config_menu(llGetOwner());
+        else {
+            sloodle_load_config_url(llGetOwner());
+            url_shown = TRUE;
+        }
+    }
+    
+    listen(integer channel, string name, key id, string msg)
+    {
+        // Check the channel
+        if (channel == SLOODLE_CHANNEL_AVATAR_DIALOG)
+        {
+            // Check it's the owner talking to us
+            if (id != llGetOwner()) return;
+            // What was the message?
+            if (msg == "0") {
+                // Load the configuration URL
+                sloodle_load_config_url(llGetOwner());
+            } else if (msg == "1") {
+                // Download the configuration from the site
+                httpconfig = llHTTPRequest(sloodleserverroot + SLOODLE_CONFIG_LINKER + "?sloodlepwd="+sloodlepwd+"&sloodleauthid="+sloodleauthid, [HTTP_METHOD, "GET"], "");
+            }
+            
+        } else if (channel == SLOODLE_CHANNEL_OBJECT_DIALOG) {
+            // Parse the message
+            list parts = llParseStringKeepNulls(msg, ["|"], []);
+            if (llGetListLength(parts) < 2) return;
+            string cmd = llList2String(parts, 0);
+            string val = llList2String(parts, 1);
+        
+            // Is it a recognised command?
+            if (cmd == "do:cleanup") {
+                // Did it come from our rezzer?
+                key kval = (key)val;
+                if (kval == sloodlemyrezzer && kval != NULL_KEY) {
+                    // Delete this object
+                    sloodle_debug("Received CLEANUP command from object \"" + name + "\".");
+                    llDie();
+                    return;
+                }
+            }
+        }
+    }
+    
+    http_response(key id, integer status, list meta, string body)
+    {
+        // Is this the response we're expecting?
+        if (id == httpconfig) {
+            httpconfig = NULL_KEY;
+            // Check the return code
+            if (status != 200) {
+                sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "httperror:code", [status], NULL_KEY, "");
+                return;
+            }
+            
+            // Split the response into lines
+            list lines = llParseStringKeepNulls(body, ["\n"], []);
+            integer numlines = llGetListLength(lines);
+            // Fetch the status line
+            list statusfields = llParseStringKeepNulls(llList2String(lines, 0), ["|"], []);
+            integer statuscode = (integer)llList2String(statusfields, 0);
+            if (statuscode == -103) {
+                sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "noconfigavailable", [], NULL_KEY, "");
+                return;
+            } else if (statuscode <= 0) {
+                sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "objectconfigfailed:code", [statuscode], NULL_KEY, "");
+                sloodle_error_code(SLOODLE_TRANSLATE_SAY, NULL_KEY,statuscode); //send message to error_message.lsl
+                state default;
+                return;
+            }
+            
+            // Indicate that we are sending configuration data
+            sloodle_translation_request(SLOODLE_TRANSLATE_HOVER_TEXT, [<0.0, 1.0, 0.0>, 0.8], "sendingconfig", [], NULL_KEY, "");
+            
+            // This will be our buffer of configuration commands
+            string cmdbuffer = "";
+            integer maxbufferlength = 300;
+            integer cmdbufferlength = 0;
+            
+            // Add the server address and password in as the first commands. Also add the rezzer key if we have one
+            cmdbuffer = "set:sloodleserverroot|"+sloodleserverroot+"\nset:sloodlepwd|" + sloodlepwd + "\n";
+            if (sloodlemyrezzer != NULL_KEY) cmdbuffer += "set:sloodlemyrezzer|" + (string)sloodlemyrezzer + "\n";
+            cmdbufferlength = llStringLength(cmdbuffer);
+            
+            // Go through each data line
+            integer linenum = 1;
+            string cmd = "";
+            integer cmdlen = 0;
+            string curline = "";
+            for (linenum=0; linenum < numlines; linenum++) {
+                curline = llList2String(lines, linenum);
+                
+                // If this is a controller ID, then store the value.
+                // (Don't bother checking if we already have a controller id)
+                if (sloodlecontrollerid == 0 && llSubStringIndex(curline, "sloodlecontrollerid") == 0) {
+                    list parts = llParseStringKeepNulls(curline, ["|"], []);
+                    if (llGetListLength(parts) > 1) sloodlecontrollerid = (integer)llList2String(parts, 1);
+                }
+            
+                // This should be "name|value" format, so just prefix it with "set:"
+                cmd = "set:" + curline + "\n";
+                cmdlen = llStringLength(cmd);
+                // Ignore lengths of less than 5
+                if (cmdlen >= 5) {
+                    // If the addition of this command will overflow the buffer, then send the buffer first
+                    if ((cmdbufferlength + cmdlen) > maxbufferlength) {
+                        sloodle_tell_other_scripts(cmdbuffer);
+                        cmdbuffer = "";
+                        cmdbufferlength = 0;
+                    }
+                    // Add the current command to the buffer
+                    cmdbuffer += cmd;
+                    cmdbufferlength += cmdlen;
+                }
+            }
+            
+            // If there is anything left in the buffer, then send it
+            if (cmdbufferlength > 0) {
+                sloodle_tell_other_scripts(cmdbuffer);
+                cmdbuffer = "";
+                cmdbufferlength = 0;
+            }
+            
+            // After a brief pause, send the EOF command
+            llSleep(0.15);
+            sloodle_tell_other_scripts(SLOODLE_EOF);
+            
+            // We're now finished
+            state idle;
+        }
+    }
+    
+    link_message(integer sender_num, integer num, string sval, key kval)
+    {
+        // Ignore anything from this script
+        if (sender_num == llGetLinkNumber()) return;
+        
+        // Check the channel
+        if (num == SLOODLE_CHANNEL_OBJECT_DIALOG) {
+            // Is it a reset command?
+            if (sval == "do:reset") {
+                llResetScript();
+                return;
+            }
+        }
+    }
+    
+    on_rez(integer start_param)
+    {
+        llResetScript();
+    }
+}
+
+
+// In this state, the script has either finished, or been instructed not to execute
+// It will only respond to a reset command
+state idle
+{
+    state_entry()
+    {
+        // Listen for chat messages from the rezzer
+        if (sloodlemyrezzer != NULL_KEY) llListen(SLOODLE_CHANNEL_OBJECT_DIALOG, "", NULL_KEY, "");
+        
+        // Regularly ping the server to notify it that this object is still 'alive'.
+        // (But only if we have a valid controller id)
+        llSetTimerEvent(0.0);
+        if (sloodlecontrollerid > 0) llSetTimerEvent(PING_DELAY);
+    }
+    
+    timer()
+    {
+        // Send our ping request and ignore the response
+        string body = "sloodlecontrollerid=" + (string)sloodlecontrollerid;
+        body += "&sloodlepwd=" + sloodlepwd;
+        body += "&sloodleobjuuid=" + (string)llGetKey();
+        llHTTPRequest(sloodleserverroot + SLOODLE_PING_LINKER, [HTTP_METHOD, "POST", HTTP_MIMETYPE, "application/x-www-form-urlencoded"], body);
+    }
+    
+    listen(integer channel, string name, key id, string msg)
+    {
+        // Check the channel
+        if (channel == SLOODLE_CHANNEL_OBJECT_DIALOG) {
+            // Parse the message
+            list parts = llParseStringKeepNulls(msg, ["|"], []);
+            if (llGetListLength(parts) < 2) return;
+            string cmd = llList2String(parts, 0);
+            string val = llList2String(parts, 1);
+        
+            // Is it a recognised command?
+            if (cmd == "do:cleanup") {
+                // Did it come from our rezzer?
+                key kval = (key)val;
+                if (kval == sloodlemyrezzer && kval != NULL_KEY) {
+                    // Delete this object
+                    sloodle_debug("Received CLEANUP command from object \"" + name + "\".");
+                    llDie();
+                    return;
+                }
+            }
+        }
+    }
+    
+    link_message(integer sender_num, integer num, string sval, key kval)
+    {
+        // Check the channel
+        if (num == SLOODLE_CHANNEL_OBJECT_DIALOG) {
+            // Check the command type
+            if (sval == "do:reset") {
+                llResetScript();
+                return;
+            } else if (sval == "do:requestconfig") {
+                // Send the configuration data again, so long as there isn't a notecard
+                if (sloodle_has_config_notecard() == FALSE) {
+                    request_config = TRUE;
+                    state configure_object;
+                } else {
+                    state default;
+                }
+                return;
+            } else if (sval == "do:reconfigure") {
+                // Let the user re-configure from the start
+                show_config_url = TRUE;
+                state configure_object;
+            }
+        }
+    }
+    
+    on_rez(integer param)
+    {
+        // Temporary measure: request the existing configuration.
+        // (this allows a pre-configured object to be rezzed from inventory, assuming the active object entry has not yet expired)
+        request_config = TRUE;
+        state configure_object;
+    }
+}
+
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: assets/sloodle_setup_web.lslp 

+ 44 - 0
assets/sloodle_url_loader.lslp

@@ -0,0 +1,44 @@
+//
+// The line above should be left blank to avoid script errors in OpenSim.
+
+// Sloodle URL loader
+// Allows other scripts to transfer the loading of a *single* URL to another script
+// Part of the Sloodle project (www.sloodle.org)
+//
+// Copyright (c) 2007 Sloodle (various contributors)
+// Released under the GNU GPL
+//
+// Contributors:
+//  Peter R. Bloomfield
+//
+
+//
+// When receiving a link message, this script will load a URL for a given user.
+// The URL should be in the string, and the user specified by their key.
+// The integer number in the link message should be SLOODLE_CHANNEL_OBJECT_LOAD_URL
+//
+// The purpose of this script is to allow the continued execution of another script,
+// even after a link has been presented.
+
+
+///// DATA /////
+
+// The link message integer code identifying the message to load a URL
+integer SLOODLE_CHANNEL_OBJECT_LOAD_URL = -1639270041;
+
+///// ---- /////
+
+default
+{
+    link_message( integer sender_num, integer msg_num, string str, key id )
+    {
+        // Is this the correct message number and are the variables non-empty?
+        if (msg_num == SLOODLE_CHANNEL_OBJECT_LOAD_URL && str != "" && id != NULL_KEY)
+        {
+            // Load the URL
+            llLoadURL(id, "", str);
+        }
+    }
+}
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: assets/sloodle_url_loader.lslp 

BIN
assets/textures/rezzer/rezzerclosed.png


BIN
assets/textures/rezzer/rezzerclosed.psd


BIN
assets/textures/rezzer/sloodle_rezzer-2.1.jpg


BIN
assets/textures/rezzer/sloodle_rezzer-2.2.jpg


BIN
assets/textures/rezzer/sloodle_rezzer-2.3.jpg


BIN
assets/textures/rezzer/sloodle_rezzer-2.4.jpg


BIN
assets/textures/rezzer/sloodle_rezzer-2.5.jpg


BIN
assets/textures/rezzer/sloodle_rezzer_inside.psd


+ 321 - 0
assets/xytext/xytext.lslp.1

@@ -0,0 +1,321 @@
+////////////////////////////////////////////
+// XyText v1.2 Script (5 Face, Single Texture)
+//
+// Written by Xylor Baysklef
+//
+// Modified by Kermitt Quirk 19/01/2006
+// To add support for 5 face prim instead of 3
+//
+////////////////////////////////////////////
+
+/////////////// CONSTANTS ///////////////////
+// XyText Message Map.
+integer DISPLAY_STRING      = 204000;
+integer DISPLAY_EXTENDED    = 204001;
+integer REMAP_INDICES       = 204002;
+integer RESET_INDICES       = 204003;
+integer SET_CELL_INFO       = 204004;
+integer SET_FONT_TEXTURE    = 204005;
+integer SET_THICKNESS       = 204006;
+integer SET_COLOR           = 204007;
+
+// This is an extended character escape sequence.
+string  ESCAPE_SEQUENCE = "\\e";
+
+// This is used to get an index for the extended character.
+string  EXTENDED_INDEX  = "12345";
+
+// Face numbers.
+integer FACE_1          = 3;
+integer FACE_2          = 7;
+integer FACE_3          = 4;
+integer FACE_4          = 6;
+integer FACE_5          = 1;
+
+// Used to hide the text after a fade-out.
+//key     TRANSPARENT     = "701917a8-d614-471f-13dd-5f4644e36e3c";
+string      TRANSPARENT     = "Transparent";
+///////////// END CONSTANTS ////////////////
+
+///////////// GLOBAL VARIABLES ///////////////
+// This is the key of the font we are displaying.
+//key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
+string      gFontTexture    = "FontsTexture";
+// All displayable characters.  Default to ASCII order.
+string gCharIndex;
+// This is the channel to listen on while acting
+// as a cell in a larger display.
+integer gCellChannel        = 1;
+// This is the starting character position in the cell channel message
+// to render.
+integer gCellCharPosition   = 15;
+// This is whether or not to use the fade in/out special effect.
+integer gCellUseFading      = FALSE;
+// This is how long to display the text before fading out (if using
+// fading special effect).
+// Note: < 0  means don't fade out.
+float   gCellHoldDelay      = 1.0;
+/////////// END GLOBAL VARIABLES ////////////
+
+ResetCharIndex() {
+   gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
+   // \" <-- Fixes LSL syntax highlighting bug.
+   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
+   gCharIndex += "\n\n\n\n\n";
+}
+
+vector GetGridOffset(integer index) {
+   // Calculate the offset needed to display this character.
+   integer Row = index / 10;
+   integer Col = index % 10;
+
+   // Return the offset in the texture.
+   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
+}
+
+ShowChars(vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
+   // Set the primitive textures directly.
+   
+   // <-0.256, 0, 0>
+   // <0, 0, 0>
+   // <0.130, 0, 0>
+   // <0, 0, 0>
+   // <-0.74, 0, 0>
+   
+   llSetPrimitiveParams( [
+        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
+        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
+        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
+        ]);
+}
+
+RenderString(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+RenderWithEffects(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // First set the alpha to the lowest possible.
+   llSetAlpha(0.05, ALL_SIDES);
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.
+    float Alpha;
+    for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // See if we want to fade out as well.
+   if (gCellHoldDelay < 0.0)
+       // No, bail out. (Just keep showing the string at full strength).
+       return;
+          // Hold the text for a while.
+   llSleep(gCellHoldDelay);
+      // Now fade out.
+   for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // Make the text transparent to fully hide it.
+   llSetTexture(TRANSPARENT, ALL_SIDES);
+}
+
+RenderExtended(string str) {
+   // Look for escape sequences.
+   list Parsed       = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
+   integer ParsedLen = llGetListLength(Parsed);
+
+   // Create a list of index values to work with.
+   list Indices;
+   // We start with room for 5 indices.
+   integer IndicesLeft = 5;
+
+   integer i;
+   string Token;
+   integer Clipped;
+   integer LastWasEscapeSequence = FALSE;
+   // Work from left to right.
+   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {
+       Token = llList2String(Parsed, i);
+
+       // If this is an escape sequence, just set the flag and move on.
+       if (Token == ESCAPE_SEQUENCE) {
+           LastWasEscapeSequence = TRUE;
+       }
+       else { // Token != ESCAPE_SEQUENCE
+           // Otherwise this is a normal token.  Check its length.
+           Clipped = FALSE;
+           integer TokenLength = llStringLength(Token);
+           // Clip if necessary.
+           if (TokenLength > IndicesLeft) {
+               Token = llGetSubString(Token, 0, IndicesLeft - 1);
+               TokenLength = llStringLength(Token);
+               IndicesLeft = 0;
+               Clipped = TRUE;
+           }
+           else
+               IndicesLeft -= TokenLength;
+
+           // Was the previous token an escape sequence?
+           if (LastWasEscapeSequence) {
+               // Yes, the first character is an escape character, the rest are normal.
+
+               // This is the extended character.
+               Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
+
+               // These are the normal characters.
+               integer j;
+               for (j = 1; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+           else { // Normal string.
+               // Just add the characters normally.
+               integer j;
+               for (j = 0; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+
+           // Unset this flag, since this was not an escape sequence.
+           LastWasEscapeSequence = FALSE;
+       }
+   }
+
+   // Use the indices to create grid positions.
+   vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0));
+   vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
+   vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
+   vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
+   vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+integer ConvertIndex(integer index) {
+   // This converts from an ASCII based index to our indexing scheme.
+   if (index >= 32) // ' ' or higher
+       index -= 32;
+   else { // index < 32
+       // Quick bounds check.
+       if (index > 15)
+           index = 15;
+
+       index += 94; // extended characters
+   }
+
+   return index;
+}
+
+default {
+   state_entry() {
+       // Initialize the character index.
+       llSetTexture(TRANSPARENT, ALL_SIDES);
+       ResetCharIndex();
+   }
+ 
+   link_message(integer sender, integer channel, string data, key id) {
+       if (channel == DISPLAY_STRING) {
+           RenderString(data);
+           return;
+       }
+       if (channel == DISPLAY_EXTENDED) {
+           RenderExtended(data);
+           return;
+       }
+       if (channel == gCellChannel) {
+           // Extract the characters we are interested in, and use those to render.
+           string TextToRender = llGetSubString(data, gCellCharPosition, gCellCharPosition + 4);
+                      // See if we need to show special effects.
+           if (gCellUseFading)
+               RenderWithEffects( TextToRender );
+           else // !gCellUseFading
+               RenderString( TextToRender );
+           return;
+       }
+       if (channel == REMAP_INDICES) {
+           // Parse the message, splitting it up into index values.
+           list Parsed = llCSV2List(data);
+           integer i;
+           // Go through the list and swap each pair of indices.
+           for (i = 0; i < llGetListLength(Parsed); i += 2) {
+               integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
+               integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
+
+               // Swap these index values.
+               string Value1 = llGetSubString(gCharIndex, Index1, Index1);
+               string Value2 = llGetSubString(gCharIndex, Index2, Index2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
+               gCharIndex = llInsertString(gCharIndex, Index1, Value2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
+               gCharIndex = llInsertString(gCharIndex, Index2, Value1);
+           }
+           return;
+       }
+       if (channel == RESET_INDICES) {
+           // Restore the character index back to default settings.
+           ResetCharIndex();
+           return;
+       }
+       if (channel == SET_CELL_INFO) {
+           // Change the channel we listen to for cell commands, the
+           // starting character position to extract from, and
+           // special effect attributes.
+           list Parsed = llCSV2List(data);
+           gCellChannel        = (integer) llList2String(Parsed, 0);
+           gCellCharPosition   = (integer) llList2String(Parsed, 1);
+           gCellUseFading      = (integer) llList2String(Parsed, 2);
+           gCellHoldDelay      = (float)   llList2String(Parsed, 3);
+           return;
+       }
+       if (channel == SET_FONT_TEXTURE) {
+           // Use the new texture instead of the current one.
+           gFontTexture = id;
+           // Change the currently shown texture.
+           llSetTexture(gFontTexture, FACE_1);
+           llSetTexture(gFontTexture, FACE_2);
+           llSetTexture(gFontTexture, FACE_3);
+           llSetTexture(gFontTexture, FACE_4);
+           llSetTexture(gFontTexture, FACE_5);
+           return;
+       }
+       if (channel == SET_THICKNESS) {
+           // Set our z scale to thickness, while staying fixed
+           // in position relative the prim below us.
+           vector Scale    = llGetScale();
+           float Thickness = (float) data;
+                      // Reposition only if this isn't the root prim.
+           integer ThisLink = llGetLinkNumber();
+           if (ThisLink != 0 || ThisLink != 1) {
+               // This is not the root prim.
+               vector Up = llRot2Up(llGetLocalRot());
+               float DistanceToMove = Thickness / 2.0 - Scale.z / 2.0;
+               vector Pos = llGetLocalPos();
+               llSetPos(Pos + DistanceToMove * Up);
+           }
+                      // Apply the new thickness.
+           Scale.z = Thickness;
+           llSetScale(Scale);
+           return;
+       }
+       if (channel == SET_COLOR) {
+           vector newColor = (vector)data;
+           llSetColor(newColor, ALL_SIDES);
+       }
+   }
+} 
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: lsl/xytext/xytext.lsl.1

+ 320 - 0
assets/xytext/xytext.lslp.10

@@ -0,0 +1,320 @@
+////////////////////////////////////////////
+// XyText v1.2 Script (5 Face, Single Texture)
+//
+// Written by Xylor Baysklef
+//
+// Modified by Kermitt Quirk 19/01/2006
+// To add support for 5 face prim instead of 3
+//
+////////////////////////////////////////////
+
+/////////////// CONSTANTS ///////////////////
+// XyText Message Map.
+integer DISPLAY_STRING      = 204000;
+integer DISPLAY_EXTENDED    = 204001;
+integer REMAP_INDICES       = 204002;
+integer RESET_INDICES       = 204003;
+integer SET_CELL_INFO       = 204004;
+integer SET_FONT_TEXTURE    = 204005;
+integer SET_THICKNESS       = 204006;
+integer SET_COLOR           = 204007;
+
+// This is an extended character escape sequence.
+string  ESCAPE_SEQUENCE = "\\e";
+
+// This is used to get an index for the extended character.
+string  EXTENDED_INDEX  = "12345";
+
+// Face numbers.
+integer FACE_1          = 3;
+integer FACE_2          = 7;
+integer FACE_3          = 4;
+integer FACE_4          = 6;
+integer FACE_5          = 1;
+
+// Used to hide the text after a fade-out.
+//key     TRANSPARENT     = "701917a8-d614-471f-13dd-5f4644e36e3c";
+string      TRANSPARENT     = "Transparent";
+///////////// END CONSTANTS ////////////////
+
+///////////// GLOBAL VARIABLES ///////////////
+// This is the key of the font we are displaying.
+//key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
+string      gFontTexture    = "FontsTexture";
+// All displayable characters.  Default to ASCII order.
+string gCharIndex;
+// This is the channel to listen on while acting
+// as a cell in a larger display.
+integer gCellChannel        = 2;
+// This is the starting character position in the cell channel message
+// to render.
+integer gCellCharPosition   = 75;
+// This is whether or not to use the fade in/out special effect.
+integer gCellUseFading      = FALSE;
+// This is how long to display the text before fading out (if using
+// fading special effect).
+// Note: < 0  means don't fade out.
+float   gCellHoldDelay      = 1.0;
+/////////// END GLOBAL VARIABLES ////////////
+
+ResetCharIndex() {
+   gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
+   // \" <-- Fixes LSL syntax highlighting bug.
+   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
+   gCharIndex += "\n\n\n\n\n";
+}
+
+vector GetGridOffset(integer index) {
+   // Calculate the offset needed to display this character.
+   integer Row = index / 10;
+   integer Col = index % 10;
+
+   // Return the offset in the texture.
+   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
+}
+
+ShowChars(vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
+   // Set the primitive textures directly.
+   
+   // <-0.256, 0, 0>
+   // <0, 0, 0>
+   // <0.130, 0, 0>
+   // <0, 0, 0>
+   // <-0.74, 0, 0>
+   
+   llSetPrimitiveParams( [
+        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
+        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
+        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
+        ]);
+}
+
+RenderString(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+RenderWithEffects(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // First set the alpha to the lowest possible.
+   llSetAlpha(0.05, ALL_SIDES);
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.
+    float Alpha;
+    for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // See if we want to fade out as well.
+   if (gCellHoldDelay < 0.0)
+       // No, bail out. (Just keep showing the string at full strength).
+       return;
+          // Hold the text for a while.
+   llSleep(gCellHoldDelay);
+      // Now fade out.
+   for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // Make the text transparent to fully hide it.
+   llSetTexture(TRANSPARENT, ALL_SIDES);
+}
+
+RenderExtended(string str) {
+   // Look for escape sequences.
+   list Parsed       = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
+   integer ParsedLen = llGetListLength(Parsed);
+
+   // Create a list of index values to work with.
+   list Indices;
+   // We start with room for 5 indices.
+   integer IndicesLeft = 5;
+
+   integer i;
+   string Token;
+   integer Clipped;
+   integer LastWasEscapeSequence = FALSE;
+   // Work from left to right.
+   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {
+       Token = llList2String(Parsed, i);
+
+       // If this is an escape sequence, just set the flag and move on.
+       if (Token == ESCAPE_SEQUENCE) {
+           LastWasEscapeSequence = TRUE;
+       }
+       else { // Token != ESCAPE_SEQUENCE
+           // Otherwise this is a normal token.  Check its length.
+           Clipped = FALSE;
+           integer TokenLength = llStringLength(Token);
+           // Clip if necessary.
+           if (TokenLength > IndicesLeft) {
+               Token = llGetSubString(Token, 0, IndicesLeft - 1);
+               TokenLength = llStringLength(Token);
+               IndicesLeft = 0;
+               Clipped = TRUE;
+           }
+           else
+               IndicesLeft -= TokenLength;
+
+           // Was the previous token an escape sequence?
+           if (LastWasEscapeSequence) {
+               // Yes, the first character is an escape character, the rest are normal.
+
+               // This is the extended character.
+               Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
+
+               // These are the normal characters.
+               integer j;
+               for (j = 1; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+           else { // Normal string.
+               // Just add the characters normally.
+               integer j;
+               for (j = 0; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+
+           // Unset this flag, since this was not an escape sequence.
+           LastWasEscapeSequence = FALSE;
+       }
+   }
+
+   // Use the indices to create grid positions.
+   vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0));
+   vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
+   vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
+   vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
+   vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+integer ConvertIndex(integer index) {
+   // This converts from an ASCII based index to our indexing scheme.
+   if (index >= 32) // ' ' or higher
+       index -= 32;
+   else { // index < 32
+       // Quick bounds check.
+       if (index > 15)
+           index = 15;
+
+       index += 94; // extended characters
+   }
+
+   return index;
+}
+
+default {
+   state_entry() {
+       // Initialize the character index.
+       llSetTexture(TRANSPARENT, ALL_SIDES);
+       ResetCharIndex();
+   }
+ 
+   link_message(integer sender, integer channel, string data, key id) {
+       if (channel == DISPLAY_STRING) {
+           RenderString(data);
+           return;
+       }
+       if (channel == DISPLAY_EXTENDED) {
+           RenderExtended(data);
+           return;
+       }
+       if (channel == gCellChannel) {
+           // Extract the characters we are interested in, and use those to render.
+           string TextToRender = llGetSubString(data, gCellCharPosition, gCellCharPosition + 4);
+                      // See if we need to show special effects.
+           if (gCellUseFading)
+               RenderWithEffects( TextToRender );
+           else // !gCellUseFading
+               RenderString( TextToRender );
+           return;
+       }
+       if (channel == REMAP_INDICES) {
+           // Parse the message, splitting it up into index values.
+           list Parsed = llCSV2List(data);
+           integer i;
+           // Go through the list and swap each pair of indices.
+           for (i = 0; i < llGetListLength(Parsed); i += 2) {
+               integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
+               integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
+
+               // Swap these index values.
+               string Value1 = llGetSubString(gCharIndex, Index1, Index1);
+               string Value2 = llGetSubString(gCharIndex, Index2, Index2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
+               gCharIndex = llInsertString(gCharIndex, Index1, Value2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
+               gCharIndex = llInsertString(gCharIndex, Index2, Value1);
+           }
+           return;
+       }
+       if (channel == RESET_INDICES) {
+           // Restore the character index back to default settings.
+           ResetCharIndex();
+           return;
+       }
+       if (channel == SET_CELL_INFO) {
+           // Change the channel we listen to for cell commands, the
+           // starting character position to extract from, and
+           // special effect attributes.
+           list Parsed = llCSV2List(data);
+           gCellChannel        = (integer) llList2String(Parsed, 0);
+           gCellCharPosition   = (integer) llList2String(Parsed, 1);
+           gCellUseFading      = (integer) llList2String(Parsed, 2);
+           gCellHoldDelay      = (float)   llList2String(Parsed, 3);
+           return;
+       }
+       if (channel == SET_FONT_TEXTURE) {
+           // Use the new texture instead of the current one.
+           gFontTexture = id;
+           // Change the currently shown texture.
+           llSetTexture(gFontTexture, FACE_1);
+           llSetTexture(gFontTexture, FACE_2);
+           llSetTexture(gFontTexture, FACE_3);
+           llSetTexture(gFontTexture, FACE_4);
+           llSetTexture(gFontTexture, FACE_5);
+           return;
+       }
+       if (channel == SET_THICKNESS) {
+           // Set our z scale to thickness, while staying fixed
+           // in position relative the prim below us.
+           vector Scale    = llGetScale();
+           float Thickness = (float) data;
+                      // Reposition only if this isn't the root prim.
+           integer ThisLink = llGetLinkNumber();
+           if (ThisLink != 0 || ThisLink != 1) {
+               // This is not the root prim.
+               vector Up = llRot2Up(llGetLocalRot());
+               float DistanceToMove = Thickness / 2.0 - Scale.z / 2.0;
+               vector Pos = llGetLocalPos();
+               llSetPos(Pos + DistanceToMove * Up);
+           }
+                      // Apply the new thickness.
+           Scale.z = Thickness;
+           llSetScale(Scale);
+           return;
+       }
+       if (channel == SET_COLOR) {
+           vector newColor = (vector)data;
+           llSetColor(newColor, ALL_SIDES);
+       }
+   }
+} // Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: lsl/xytext/xytext.lsl.10

+ 320 - 0
assets/xytext/xytext.lslp.11

@@ -0,0 +1,320 @@
+////////////////////////////////////////////
+// XyText v1.2 Script (5 Face, Single Texture)
+//
+// Written by Xylor Baysklef
+//
+// Modified by Kermitt Quirk 19/01/2006
+// To add support for 5 face prim instead of 3
+//
+////////////////////////////////////////////
+
+/////////////// CONSTANTS ///////////////////
+// XyText Message Map.
+integer DISPLAY_STRING      = 204000;
+integer DISPLAY_EXTENDED    = 204001;
+integer REMAP_INDICES       = 204002;
+integer RESET_INDICES       = 204003;
+integer SET_CELL_INFO       = 204004;
+integer SET_FONT_TEXTURE    = 204005;
+integer SET_THICKNESS       = 204006;
+integer SET_COLOR           = 204007;
+
+// This is an extended character escape sequence.
+string  ESCAPE_SEQUENCE = "\\e";
+
+// This is used to get an index for the extended character.
+string  EXTENDED_INDEX  = "12345";
+
+// Face numbers.
+integer FACE_1          = 3;
+integer FACE_2          = 7;
+integer FACE_3          = 4;
+integer FACE_4          = 6;
+integer FACE_5          = 1;
+
+// Used to hide the text after a fade-out.
+//key     TRANSPARENT     = "701917a8-d614-471f-13dd-5f4644e36e3c";
+string      TRANSPARENT     = "Transparent";
+///////////// END CONSTANTS ////////////////
+
+///////////// GLOBAL VARIABLES ///////////////
+// This is the key of the font we are displaying.
+//key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
+string      gFontTexture    = "FontsTexture";
+// All displayable characters.  Default to ASCII order.
+string gCharIndex;
+// This is the channel to listen on while acting
+// as a cell in a larger display.
+integer gCellChannel        = 2;
+// This is the starting character position in the cell channel message
+// to render.
+integer gCellCharPosition   = 5;
+// This is whether or not to use the fade in/out special effect.
+integer gCellUseFading      = FALSE;
+// This is how long to display the text before fading out (if using
+// fading special effect).
+// Note: < 0  means don't fade out.
+float   gCellHoldDelay      = 1.0;
+/////////// END GLOBAL VARIABLES ////////////
+
+ResetCharIndex() {
+   gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
+   // \" <-- Fixes LSL syntax highlighting bug.
+   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
+   gCharIndex += "\n\n\n\n\n";
+}
+
+vector GetGridOffset(integer index) {
+   // Calculate the offset needed to display this character.
+   integer Row = index / 10;
+   integer Col = index % 10;
+
+   // Return the offset in the texture.
+   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
+}
+
+ShowChars(vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
+   // Set the primitive textures directly.
+   
+   // <-0.256, 0, 0>
+   // <0, 0, 0>
+   // <0.130, 0, 0>
+   // <0, 0, 0>
+   // <-0.74, 0, 0>
+   
+   llSetPrimitiveParams( [
+        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
+        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
+        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
+        ]);
+}
+
+RenderString(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+RenderWithEffects(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // First set the alpha to the lowest possible.
+   llSetAlpha(0.05, ALL_SIDES);
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.
+    float Alpha;
+    for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // See if we want to fade out as well.
+   if (gCellHoldDelay < 0.0)
+       // No, bail out. (Just keep showing the string at full strength).
+       return;
+          // Hold the text for a while.
+   llSleep(gCellHoldDelay);
+      // Now fade out.
+   for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // Make the text transparent to fully hide it.
+   llSetTexture(TRANSPARENT, ALL_SIDES);
+}
+
+RenderExtended(string str) {
+   // Look for escape sequences.
+   list Parsed       = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
+   integer ParsedLen = llGetListLength(Parsed);
+
+   // Create a list of index values to work with.
+   list Indices;
+   // We start with room for 5 indices.
+   integer IndicesLeft = 5;
+
+   integer i;
+   string Token;
+   integer Clipped;
+   integer LastWasEscapeSequence = FALSE;
+   // Work from left to right.
+   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {
+       Token = llList2String(Parsed, i);
+
+       // If this is an escape sequence, just set the flag and move on.
+       if (Token == ESCAPE_SEQUENCE) {
+           LastWasEscapeSequence = TRUE;
+       }
+       else { // Token != ESCAPE_SEQUENCE
+           // Otherwise this is a normal token.  Check its length.
+           Clipped = FALSE;
+           integer TokenLength = llStringLength(Token);
+           // Clip if necessary.
+           if (TokenLength > IndicesLeft) {
+               Token = llGetSubString(Token, 0, IndicesLeft - 1);
+               TokenLength = llStringLength(Token);
+               IndicesLeft = 0;
+               Clipped = TRUE;
+           }
+           else
+               IndicesLeft -= TokenLength;
+
+           // Was the previous token an escape sequence?
+           if (LastWasEscapeSequence) {
+               // Yes, the first character is an escape character, the rest are normal.
+
+               // This is the extended character.
+               Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
+
+               // These are the normal characters.
+               integer j;
+               for (j = 1; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+           else { // Normal string.
+               // Just add the characters normally.
+               integer j;
+               for (j = 0; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+
+           // Unset this flag, since this was not an escape sequence.
+           LastWasEscapeSequence = FALSE;
+       }
+   }
+
+   // Use the indices to create grid positions.
+   vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0));
+   vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
+   vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
+   vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
+   vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+integer ConvertIndex(integer index) {
+   // This converts from an ASCII based index to our indexing scheme.
+   if (index >= 32) // ' ' or higher
+       index -= 32;
+   else { // index < 32
+       // Quick bounds check.
+       if (index > 15)
+           index = 15;
+
+       index += 94; // extended characters
+   }
+
+   return index;
+}
+
+default {
+   state_entry() {
+       // Initialize the character index.
+       llSetTexture(TRANSPARENT, ALL_SIDES);
+       ResetCharIndex();
+   }
+ 
+   link_message(integer sender, integer channel, string data, key id) {
+       if (channel == DISPLAY_STRING) {
+           RenderString(data);
+           return;
+       }
+       if (channel == DISPLAY_EXTENDED) {
+           RenderExtended(data);
+           return;
+       }
+       if (channel == gCellChannel) {
+           // Extract the characters we are interested in, and use those to render.
+           string TextToRender = llGetSubString(data, gCellCharPosition, gCellCharPosition + 4);
+                      // See if we need to show special effects.
+           if (gCellUseFading)
+               RenderWithEffects( TextToRender );
+           else // !gCellUseFading
+               RenderString( TextToRender );
+           return;
+       }
+       if (channel == REMAP_INDICES) {
+           // Parse the message, splitting it up into index values.
+           list Parsed = llCSV2List(data);
+           integer i;
+           // Go through the list and swap each pair of indices.
+           for (i = 0; i < llGetListLength(Parsed); i += 2) {
+               integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
+               integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
+
+               // Swap these index values.
+               string Value1 = llGetSubString(gCharIndex, Index1, Index1);
+               string Value2 = llGetSubString(gCharIndex, Index2, Index2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
+               gCharIndex = llInsertString(gCharIndex, Index1, Value2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
+               gCharIndex = llInsertString(gCharIndex, Index2, Value1);
+           }
+           return;
+       }
+       if (channel == RESET_INDICES) {
+           // Restore the character index back to default settings.
+           ResetCharIndex();
+           return;
+       }
+       if (channel == SET_CELL_INFO) {
+           // Change the channel we listen to for cell commands, the
+           // starting character position to extract from, and
+           // special effect attributes.
+           list Parsed = llCSV2List(data);
+           gCellChannel        = (integer) llList2String(Parsed, 0);
+           gCellCharPosition   = (integer) llList2String(Parsed, 1);
+           gCellUseFading      = (integer) llList2String(Parsed, 2);
+           gCellHoldDelay      = (float)   llList2String(Parsed, 3);
+           return;
+       }
+       if (channel == SET_FONT_TEXTURE) {
+           // Use the new texture instead of the current one.
+           gFontTexture = id;
+           // Change the currently shown texture.
+           llSetTexture(gFontTexture, FACE_1);
+           llSetTexture(gFontTexture, FACE_2);
+           llSetTexture(gFontTexture, FACE_3);
+           llSetTexture(gFontTexture, FACE_4);
+           llSetTexture(gFontTexture, FACE_5);
+           return;
+       }
+       if (channel == SET_THICKNESS) {
+           // Set our z scale to thickness, while staying fixed
+           // in position relative the prim below us.
+           vector Scale    = llGetScale();
+           float Thickness = (float) data;
+                      // Reposition only if this isn't the root prim.
+           integer ThisLink = llGetLinkNumber();
+           if (ThisLink != 0 || ThisLink != 1) {
+               // This is not the root prim.
+               vector Up = llRot2Up(llGetLocalRot());
+               float DistanceToMove = Thickness / 2.0 - Scale.z / 2.0;
+               vector Pos = llGetLocalPos();
+               llSetPos(Pos + DistanceToMove * Up);
+           }
+                      // Apply the new thickness.
+           Scale.z = Thickness;
+           llSetScale(Scale);
+           return;
+       }
+       if (channel == SET_COLOR) {
+           vector newColor = (vector)data;
+           llSetColor(newColor, ALL_SIDES);
+       }
+   }
+} // Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: lsl/xytext/xytext.lsl.11

+ 321 - 0
assets/xytext/xytext.lslp.12

@@ -0,0 +1,321 @@
+////////////////////////////////////////////
+// XyText v1.2 Script (5 Face, Single Texture)
+//
+// Written by Xylor Baysklef
+//
+// Modified by Kermitt Quirk 19/01/2006
+// To add support for 5 face prim instead of 3
+//
+////////////////////////////////////////////
+
+/////////////// CONSTANTS ///////////////////
+// XyText Message Map.
+integer DISPLAY_STRING      = 204000;
+integer DISPLAY_EXTENDED    = 204001;
+integer REMAP_INDICES       = 204002;
+integer RESET_INDICES       = 204003;
+integer SET_CELL_INFO       = 204004;
+integer SET_FONT_TEXTURE    = 204005;
+integer SET_THICKNESS       = 204006;
+integer SET_COLOR           = 204007;
+
+// This is an extended character escape sequence.
+string  ESCAPE_SEQUENCE = "\\e";
+
+// This is used to get an index for the extended character.
+string  EXTENDED_INDEX  = "12345";
+
+// Face numbers.
+integer FACE_1          = 3;
+integer FACE_2          = 7;
+integer FACE_3          = 4;
+integer FACE_4          = 6;
+integer FACE_5          = 1;
+
+// Used to hide the text after a fade-out.
+//key     TRANSPARENT     = "701917a8-d614-471f-13dd-5f4644e36e3c";
+string      TRANSPARENT     = "Transparent";
+///////////// END CONSTANTS ////////////////
+
+///////////// GLOBAL VARIABLES ///////////////
+// This is the key of the font we are displaying.
+//key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
+string      gFontTexture    = "FontsTexture";
+// All displayable characters.  Default to ASCII order.
+string gCharIndex;
+// This is the channel to listen on while acting
+// as a cell in a larger display.
+integer gCellChannel        = 2;
+// This is the starting character position in the cell channel message
+// to render.
+integer gCellCharPosition   = 50;
+// This is whether or not to use the fade in/out special effect.
+integer gCellUseFading      = FALSE;
+// This is how long to display the text before fading out (if using
+// fading special effect).
+// Note: < 0  means don't fade out.
+float   gCellHoldDelay      = 1.0;
+/////////// END GLOBAL VARIABLES ////////////
+
+ResetCharIndex() {
+   gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
+   // \" <-- Fixes LSL syntax highlighting bug.
+   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
+   gCharIndex += "\n\n\n\n\n";
+}
+
+vector GetGridOffset(integer index) {
+   // Calculate the offset needed to display this character.
+   integer Row = index / 10;
+   integer Col = index % 10;
+
+   // Return the offset in the texture.
+   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
+}
+
+ShowChars(vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
+   // Set the primitive textures directly.
+   
+   // <-0.256, 0, 0>
+   // <0, 0, 0>
+   // <0.130, 0, 0>
+   // <0, 0, 0>
+   // <-0.74, 0, 0>
+   
+   llSetPrimitiveParams( [
+        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
+        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
+        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
+        ]);
+}
+
+RenderString(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+RenderWithEffects(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // First set the alpha to the lowest possible.
+   llSetAlpha(0.05, ALL_SIDES);
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.
+    float Alpha;
+    for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // See if we want to fade out as well.
+   if (gCellHoldDelay < 0.0)
+       // No, bail out. (Just keep showing the string at full strength).
+       return;
+          // Hold the text for a while.
+   llSleep(gCellHoldDelay);
+      // Now fade out.
+   for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // Make the text transparent to fully hide it.
+   llSetTexture(TRANSPARENT, ALL_SIDES);
+}
+
+RenderExtended(string str) {
+   // Look for escape sequences.
+   list Parsed       = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
+   integer ParsedLen = llGetListLength(Parsed);
+
+   // Create a list of index values to work with.
+   list Indices;
+   // We start with room for 5 indices.
+   integer IndicesLeft = 5;
+
+   integer i;
+   string Token;
+   integer Clipped;
+   integer LastWasEscapeSequence = FALSE;
+   // Work from left to right.
+   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {
+       Token = llList2String(Parsed, i);
+
+       // If this is an escape sequence, just set the flag and move on.
+       if (Token == ESCAPE_SEQUENCE) {
+           LastWasEscapeSequence = TRUE;
+       }
+       else { // Token != ESCAPE_SEQUENCE
+           // Otherwise this is a normal token.  Check its length.
+           Clipped = FALSE;
+           integer TokenLength = llStringLength(Token);
+           // Clip if necessary.
+           if (TokenLength > IndicesLeft) {
+               Token = llGetSubString(Token, 0, IndicesLeft - 1);
+               TokenLength = llStringLength(Token);
+               IndicesLeft = 0;
+               Clipped = TRUE;
+           }
+           else
+               IndicesLeft -= TokenLength;
+
+           // Was the previous token an escape sequence?
+           if (LastWasEscapeSequence) {
+               // Yes, the first character is an escape character, the rest are normal.
+
+               // This is the extended character.
+               Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
+
+               // These are the normal characters.
+               integer j;
+               for (j = 1; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+           else { // Normal string.
+               // Just add the characters normally.
+               integer j;
+               for (j = 0; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+
+           // Unset this flag, since this was not an escape sequence.
+           LastWasEscapeSequence = FALSE;
+       }
+   }
+
+   // Use the indices to create grid positions.
+   vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0));
+   vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
+   vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
+   vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
+   vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+integer ConvertIndex(integer index) {
+   // This converts from an ASCII based index to our indexing scheme.
+   if (index >= 32) // ' ' or higher
+       index -= 32;
+   else { // index < 32
+       // Quick bounds check.
+       if (index > 15)
+           index = 15;
+
+       index += 94; // extended characters
+   }
+
+   return index;
+}
+
+default {
+   state_entry() {
+       // Initialize the character index.
+       llSetTexture(TRANSPARENT, ALL_SIDES);
+       ResetCharIndex();
+   }
+ 
+   link_message(integer sender, integer channel, string data, key id) {
+       if (channel == DISPLAY_STRING) {
+           RenderString(data);
+           return;
+       }
+       if (channel == DISPLAY_EXTENDED) {
+           RenderExtended(data);
+           return;
+       }
+       if (channel == gCellChannel) {
+           // Extract the characters we are interested in, and use those to render.
+           string TextToRender = llGetSubString(data, gCellCharPosition, gCellCharPosition + 4);
+                      // See if we need to show special effects.
+           if (gCellUseFading)
+               RenderWithEffects( TextToRender );
+           else // !gCellUseFading
+               RenderString( TextToRender );
+           return;
+       }
+       if (channel == REMAP_INDICES) {
+           // Parse the message, splitting it up into index values.
+           list Parsed = llCSV2List(data);
+           integer i;
+           // Go through the list and swap each pair of indices.
+           for (i = 0; i < llGetListLength(Parsed); i += 2) {
+               integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
+               integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
+
+               // Swap these index values.
+               string Value1 = llGetSubString(gCharIndex, Index1, Index1);
+               string Value2 = llGetSubString(gCharIndex, Index2, Index2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
+               gCharIndex = llInsertString(gCharIndex, Index1, Value2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
+               gCharIndex = llInsertString(gCharIndex, Index2, Value1);
+           }
+           return;
+       }
+       if (channel == RESET_INDICES) {
+           // Restore the character index back to default settings.
+           ResetCharIndex();
+           return;
+       }
+       if (channel == SET_CELL_INFO) {
+           // Change the channel we listen to for cell commands, the
+           // starting character position to extract from, and
+           // special effect attributes.
+           list Parsed = llCSV2List(data);
+           gCellChannel        = (integer) llList2String(Parsed, 0);
+           gCellCharPosition   = (integer) llList2String(Parsed, 1);
+           gCellUseFading      = (integer) llList2String(Parsed, 2);
+           gCellHoldDelay      = (float)   llList2String(Parsed, 3);
+           return;
+       }
+       if (channel == SET_FONT_TEXTURE) {
+           // Use the new texture instead of the current one.
+           gFontTexture = id;
+           // Change the currently shown texture.
+           llSetTexture(gFontTexture, FACE_1);
+           llSetTexture(gFontTexture, FACE_2);
+           llSetTexture(gFontTexture, FACE_3);
+           llSetTexture(gFontTexture, FACE_4);
+           llSetTexture(gFontTexture, FACE_5);
+           return;
+       }
+       if (channel == SET_THICKNESS) {
+           // Set our z scale to thickness, while staying fixed
+           // in position relative the prim below us.
+           vector Scale    = llGetScale();
+           float Thickness = (float) data;
+                      // Reposition only if this isn't the root prim.
+           integer ThisLink = llGetLinkNumber();
+           if (ThisLink != 0 || ThisLink != 1) {
+               // This is not the root prim.
+               vector Up = llRot2Up(llGetLocalRot());
+               float DistanceToMove = Thickness / 2.0 - Scale.z / 2.0;
+               vector Pos = llGetLocalPos();
+               llSetPos(Pos + DistanceToMove * Up);
+           }
+                      // Apply the new thickness.
+           Scale.z = Thickness;
+           llSetScale(Scale);
+           return;
+       }
+       if (channel == SET_COLOR) {
+           vector newColor = (vector)data;
+           llSetColor(newColor, ALL_SIDES);
+       }
+   }
+} 
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: lsl/xytext/xytext.lsl.12

+ 321 - 0
assets/xytext/xytext.lslp.13

@@ -0,0 +1,321 @@
+////////////////////////////////////////////
+// XyText v1.2 Script (5 Face, Single Texture)
+//
+// Written by Xylor Baysklef
+//
+// Modified by Kermitt Quirk 19/01/2006
+// To add support for 5 face prim instead of 3
+//
+////////////////////////////////////////////
+
+/////////////// CONSTANTS ///////////////////
+// XyText Message Map.
+integer DISPLAY_STRING      = 204000;
+integer DISPLAY_EXTENDED    = 204001;
+integer REMAP_INDICES       = 204002;
+integer RESET_INDICES       = 204003;
+integer SET_CELL_INFO       = 204004;
+integer SET_FONT_TEXTURE    = 204005;
+integer SET_THICKNESS       = 204006;
+integer SET_COLOR           = 204007;
+
+// This is an extended character escape sequence.
+string  ESCAPE_SEQUENCE = "\\e";
+
+// This is used to get an index for the extended character.
+string  EXTENDED_INDEX  = "12345";
+
+// Face numbers.
+integer FACE_1          = 3;
+integer FACE_2          = 7;
+integer FACE_3          = 4;
+integer FACE_4          = 6;
+integer FACE_5          = 1;
+
+// Used to hide the text after a fade-out.
+//key     TRANSPARENT     = "701917a8-d614-471f-13dd-5f4644e36e3c";
+string      TRANSPARENT     = "Transparent";
+///////////// END CONSTANTS ////////////////
+
+///////////// GLOBAL VARIABLES ///////////////
+// This is the key of the font we are displaying.
+//key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
+string      gFontTexture    = "FontsTexture";
+// All displayable characters.  Default to ASCII order.
+string gCharIndex;
+// This is the channel to listen on while acting
+// as a cell in a larger display.
+integer gCellChannel        = 2;
+// This is the starting character position in the cell channel message
+// to render.
+integer gCellCharPosition   = 60;
+// This is whether or not to use the fade in/out special effect.
+integer gCellUseFading      = FALSE;
+// This is how long to display the text before fading out (if using
+// fading special effect).
+// Note: < 0  means don't fade out.
+float   gCellHoldDelay      = 1.0;
+/////////// END GLOBAL VARIABLES ////////////
+
+ResetCharIndex() {
+   gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
+   // \" <-- Fixes LSL syntax highlighting bug.
+   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
+   gCharIndex += "\n\n\n\n\n";
+}
+
+vector GetGridOffset(integer index) {
+   // Calculate the offset needed to display this character.
+   integer Row = index / 10;
+   integer Col = index % 10;
+
+   // Return the offset in the texture.
+   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
+}
+
+ShowChars(vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
+   // Set the primitive textures directly.
+   
+   // <-0.256, 0, 0>
+   // <0, 0, 0>
+   // <0.130, 0, 0>
+   // <0, 0, 0>
+   // <-0.74, 0, 0>
+   
+   llSetPrimitiveParams( [
+        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
+        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
+        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
+        ]);
+}
+
+RenderString(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+RenderWithEffects(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // First set the alpha to the lowest possible.
+   llSetAlpha(0.05, ALL_SIDES);
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.
+    float Alpha;
+    for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // See if we want to fade out as well.
+   if (gCellHoldDelay < 0.0)
+       // No, bail out. (Just keep showing the string at full strength).
+       return;
+          // Hold the text for a while.
+   llSleep(gCellHoldDelay);
+      // Now fade out.
+   for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // Make the text transparent to fully hide it.
+   llSetTexture(TRANSPARENT, ALL_SIDES);
+}
+
+RenderExtended(string str) {
+   // Look for escape sequences.
+   list Parsed       = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
+   integer ParsedLen = llGetListLength(Parsed);
+
+   // Create a list of index values to work with.
+   list Indices;
+   // We start with room for 5 indices.
+   integer IndicesLeft = 5;
+
+   integer i;
+   string Token;
+   integer Clipped;
+   integer LastWasEscapeSequence = FALSE;
+   // Work from left to right.
+   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {
+       Token = llList2String(Parsed, i);
+
+       // If this is an escape sequence, just set the flag and move on.
+       if (Token == ESCAPE_SEQUENCE) {
+           LastWasEscapeSequence = TRUE;
+       }
+       else { // Token != ESCAPE_SEQUENCE
+           // Otherwise this is a normal token.  Check its length.
+           Clipped = FALSE;
+           integer TokenLength = llStringLength(Token);
+           // Clip if necessary.
+           if (TokenLength > IndicesLeft) {
+               Token = llGetSubString(Token, 0, IndicesLeft - 1);
+               TokenLength = llStringLength(Token);
+               IndicesLeft = 0;
+               Clipped = TRUE;
+           }
+           else
+               IndicesLeft -= TokenLength;
+
+           // Was the previous token an escape sequence?
+           if (LastWasEscapeSequence) {
+               // Yes, the first character is an escape character, the rest are normal.
+
+               // This is the extended character.
+               Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
+
+               // These are the normal characters.
+               integer j;
+               for (j = 1; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+           else { // Normal string.
+               // Just add the characters normally.
+               integer j;
+               for (j = 0; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+
+           // Unset this flag, since this was not an escape sequence.
+           LastWasEscapeSequence = FALSE;
+       }
+   }
+
+   // Use the indices to create grid positions.
+   vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0));
+   vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
+   vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
+   vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
+   vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+integer ConvertIndex(integer index) {
+   // This converts from an ASCII based index to our indexing scheme.
+   if (index >= 32) // ' ' or higher
+       index -= 32;
+   else { // index < 32
+       // Quick bounds check.
+       if (index > 15)
+           index = 15;
+
+       index += 94; // extended characters
+   }
+
+   return index;
+}
+
+default {
+   state_entry() {
+       // Initialize the character index.
+       llSetTexture(TRANSPARENT, ALL_SIDES);
+       ResetCharIndex();
+   }
+ 
+   link_message(integer sender, integer channel, string data, key id) {
+       if (channel == DISPLAY_STRING) {
+           RenderString(data);
+           return;
+       }
+       if (channel == DISPLAY_EXTENDED) {
+           RenderExtended(data);
+           return;
+       }
+       if (channel == gCellChannel) {
+           // Extract the characters we are interested in, and use those to render.
+           string TextToRender = llGetSubString(data, gCellCharPosition, gCellCharPosition + 4);
+                      // See if we need to show special effects.
+           if (gCellUseFading)
+               RenderWithEffects( TextToRender );
+           else // !gCellUseFading
+               RenderString( TextToRender );
+           return;
+       }
+       if (channel == REMAP_INDICES) {
+           // Parse the message, splitting it up into index values.
+           list Parsed = llCSV2List(data);
+           integer i;
+           // Go through the list and swap each pair of indices.
+           for (i = 0; i < llGetListLength(Parsed); i += 2) {
+               integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
+               integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
+
+               // Swap these index values.
+               string Value1 = llGetSubString(gCharIndex, Index1, Index1);
+               string Value2 = llGetSubString(gCharIndex, Index2, Index2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
+               gCharIndex = llInsertString(gCharIndex, Index1, Value2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
+               gCharIndex = llInsertString(gCharIndex, Index2, Value1);
+           }
+           return;
+       }
+       if (channel == RESET_INDICES) {
+           // Restore the character index back to default settings.
+           ResetCharIndex();
+           return;
+       }
+       if (channel == SET_CELL_INFO) {
+           // Change the channel we listen to for cell commands, the
+           // starting character position to extract from, and
+           // special effect attributes.
+           list Parsed = llCSV2List(data);
+           gCellChannel        = (integer) llList2String(Parsed, 0);
+           gCellCharPosition   = (integer) llList2String(Parsed, 1);
+           gCellUseFading      = (integer) llList2String(Parsed, 2);
+           gCellHoldDelay      = (float)   llList2String(Parsed, 3);
+           return;
+       }
+       if (channel == SET_FONT_TEXTURE) {
+           // Use the new texture instead of the current one.
+           gFontTexture = id;
+           // Change the currently shown texture.
+           llSetTexture(gFontTexture, FACE_1);
+           llSetTexture(gFontTexture, FACE_2);
+           llSetTexture(gFontTexture, FACE_3);
+           llSetTexture(gFontTexture, FACE_4);
+           llSetTexture(gFontTexture, FACE_5);
+           return;
+       }
+       if (channel == SET_THICKNESS) {
+           // Set our z scale to thickness, while staying fixed
+           // in position relative the prim below us.
+           vector Scale    = llGetScale();
+           float Thickness = (float) data;
+                      // Reposition only if this isn't the root prim.
+           integer ThisLink = llGetLinkNumber();
+           if (ThisLink != 0 || ThisLink != 1) {
+               // This is not the root prim.
+               vector Up = llRot2Up(llGetLocalRot());
+               float DistanceToMove = Thickness / 2.0 - Scale.z / 2.0;
+               vector Pos = llGetLocalPos();
+               llSetPos(Pos + DistanceToMove * Up);
+           }
+                      // Apply the new thickness.
+           Scale.z = Thickness;
+           llSetScale(Scale);
+           return;
+       }
+       if (channel == SET_COLOR) {
+           vector newColor = (vector)data;
+           llSetColor(newColor, ALL_SIDES);
+       }
+   }
+} 
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: lsl/xytext/xytext.lsl.13

+ 320 - 0
assets/xytext/xytext.lslp.14

@@ -0,0 +1,320 @@
+////////////////////////////////////////////
+// XyText v1.2 Script (5 Face, Single Texture)
+//
+// Written by Xylor Baysklef
+//
+// Modified by Kermitt Quirk 19/01/2006
+// To add support for 5 face prim instead of 3
+//
+////////////////////////////////////////////
+
+/////////////// CONSTANTS ///////////////////
+// XyText Message Map.
+integer DISPLAY_STRING      = 204000;
+integer DISPLAY_EXTENDED    = 204001;
+integer REMAP_INDICES       = 204002;
+integer RESET_INDICES       = 204003;
+integer SET_CELL_INFO       = 204004;
+integer SET_FONT_TEXTURE    = 204005;
+integer SET_THICKNESS       = 204006;
+integer SET_COLOR           = 204007;
+
+// This is an extended character escape sequence.
+string  ESCAPE_SEQUENCE = "\\e";
+
+// This is used to get an index for the extended character.
+string  EXTENDED_INDEX  = "12345";
+
+// Face numbers.
+integer FACE_1          = 3;
+integer FACE_2          = 7;
+integer FACE_3          = 4;
+integer FACE_4          = 6;
+integer FACE_5          = 1;
+
+// Used to hide the text after a fade-out.
+//key     TRANSPARENT     = "701917a8-d614-471f-13dd-5f4644e36e3c";
+string      TRANSPARENT     = "Transparent";
+///////////// END CONSTANTS ////////////////
+
+///////////// GLOBAL VARIABLES ///////////////
+// This is the key of the font we are displaying.
+//key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
+string      gFontTexture    = "FontsTexture";
+// All displayable characters.  Default to ASCII order.
+string gCharIndex;
+// This is the channel to listen on while acting
+// as a cell in a larger display.
+integer gCellChannel        = 2;
+// This is the starting character position in the cell channel message
+// to render.
+integer gCellCharPosition   = 25;
+// This is whether or not to use the fade in/out special effect.
+integer gCellUseFading      = FALSE;
+// This is how long to display the text before fading out (if using
+// fading special effect).
+// Note: < 0  means don't fade out.
+float   gCellHoldDelay      = 1.0;
+/////////// END GLOBAL VARIABLES ////////////
+
+ResetCharIndex() {
+   gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
+   // \" <-- Fixes LSL syntax highlighting bug.
+   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
+   gCharIndex += "\n\n\n\n\n";
+}
+
+vector GetGridOffset(integer index) {
+   // Calculate the offset needed to display this character.
+   integer Row = index / 10;
+   integer Col = index % 10;
+
+   // Return the offset in the texture.
+   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
+}
+
+ShowChars(vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
+   // Set the primitive textures directly.
+   
+   // <-0.256, 0, 0>
+   // <0, 0, 0>
+   // <0.130, 0, 0>
+   // <0, 0, 0>
+   // <-0.74, 0, 0>
+   
+   llSetPrimitiveParams( [
+        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
+        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
+        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
+        ]);
+}
+
+RenderString(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+RenderWithEffects(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // First set the alpha to the lowest possible.
+   llSetAlpha(0.05, ALL_SIDES);
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.
+    float Alpha;
+    for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // See if we want to fade out as well.
+   if (gCellHoldDelay < 0.0)
+       // No, bail out. (Just keep showing the string at full strength).
+       return;
+          // Hold the text for a while.
+   llSleep(gCellHoldDelay);
+      // Now fade out.
+   for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // Make the text transparent to fully hide it.
+   llSetTexture(TRANSPARENT, ALL_SIDES);
+}
+
+RenderExtended(string str) {
+   // Look for escape sequences.
+   list Parsed       = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
+   integer ParsedLen = llGetListLength(Parsed);
+
+   // Create a list of index values to work with.
+   list Indices;
+   // We start with room for 5 indices.
+   integer IndicesLeft = 5;
+
+   integer i;
+   string Token;
+   integer Clipped;
+   integer LastWasEscapeSequence = FALSE;
+   // Work from left to right.
+   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {
+       Token = llList2String(Parsed, i);
+
+       // If this is an escape sequence, just set the flag and move on.
+       if (Token == ESCAPE_SEQUENCE) {
+           LastWasEscapeSequence = TRUE;
+       }
+       else { // Token != ESCAPE_SEQUENCE
+           // Otherwise this is a normal token.  Check its length.
+           Clipped = FALSE;
+           integer TokenLength = llStringLength(Token);
+           // Clip if necessary.
+           if (TokenLength > IndicesLeft) {
+               Token = llGetSubString(Token, 0, IndicesLeft - 1);
+               TokenLength = llStringLength(Token);
+               IndicesLeft = 0;
+               Clipped = TRUE;
+           }
+           else
+               IndicesLeft -= TokenLength;
+
+           // Was the previous token an escape sequence?
+           if (LastWasEscapeSequence) {
+               // Yes, the first character is an escape character, the rest are normal.
+
+               // This is the extended character.
+               Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
+
+               // These are the normal characters.
+               integer j;
+               for (j = 1; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+           else { // Normal string.
+               // Just add the characters normally.
+               integer j;
+               for (j = 0; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+
+           // Unset this flag, since this was not an escape sequence.
+           LastWasEscapeSequence = FALSE;
+       }
+   }
+
+   // Use the indices to create grid positions.
+   vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0));
+   vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
+   vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
+   vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
+   vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+integer ConvertIndex(integer index) {
+   // This converts from an ASCII based index to our indexing scheme.
+   if (index >= 32) // ' ' or higher
+       index -= 32;
+   else { // index < 32
+       // Quick bounds check.
+       if (index > 15)
+           index = 15;
+
+       index += 94; // extended characters
+   }
+
+   return index;
+}
+
+default {
+   state_entry() {
+       // Initialize the character index.
+       llSetTexture(TRANSPARENT, ALL_SIDES);
+       ResetCharIndex();
+   }
+ 
+   link_message(integer sender, integer channel, string data, key id) {
+       if (channel == DISPLAY_STRING) {
+           RenderString(data);
+           return;
+       }
+       if (channel == DISPLAY_EXTENDED) {
+           RenderExtended(data);
+           return;
+       }
+       if (channel == gCellChannel) {
+           // Extract the characters we are interested in, and use those to render.
+           string TextToRender = llGetSubString(data, gCellCharPosition, gCellCharPosition + 4);
+                      // See if we need to show special effects.
+           if (gCellUseFading)
+               RenderWithEffects( TextToRender );
+           else // !gCellUseFading
+               RenderString( TextToRender );
+           return;
+       }
+       if (channel == REMAP_INDICES) {
+           // Parse the message, splitting it up into index values.
+           list Parsed = llCSV2List(data);
+           integer i;
+           // Go through the list and swap each pair of indices.
+           for (i = 0; i < llGetListLength(Parsed); i += 2) {
+               integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
+               integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
+
+               // Swap these index values.
+               string Value1 = llGetSubString(gCharIndex, Index1, Index1);
+               string Value2 = llGetSubString(gCharIndex, Index2, Index2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
+               gCharIndex = llInsertString(gCharIndex, Index1, Value2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
+               gCharIndex = llInsertString(gCharIndex, Index2, Value1);
+           }
+           return;
+       }
+       if (channel == RESET_INDICES) {
+           // Restore the character index back to default settings.
+           ResetCharIndex();
+           return;
+       }
+       if (channel == SET_CELL_INFO) {
+           // Change the channel we listen to for cell commands, the
+           // starting character position to extract from, and
+           // special effect attributes.
+           list Parsed = llCSV2List(data);
+           gCellChannel        = (integer) llList2String(Parsed, 0);
+           gCellCharPosition   = (integer) llList2String(Parsed, 1);
+           gCellUseFading      = (integer) llList2String(Parsed, 2);
+           gCellHoldDelay      = (float)   llList2String(Parsed, 3);
+           return;
+       }
+       if (channel == SET_FONT_TEXTURE) {
+           // Use the new texture instead of the current one.
+           gFontTexture = id;
+           // Change the currently shown texture.
+           llSetTexture(gFontTexture, FACE_1);
+           llSetTexture(gFontTexture, FACE_2);
+           llSetTexture(gFontTexture, FACE_3);
+           llSetTexture(gFontTexture, FACE_4);
+           llSetTexture(gFontTexture, FACE_5);
+           return;
+       }
+       if (channel == SET_THICKNESS) {
+           // Set our z scale to thickness, while staying fixed
+           // in position relative the prim below us.
+           vector Scale    = llGetScale();
+           float Thickness = (float) data;
+                      // Reposition only if this isn't the root prim.
+           integer ThisLink = llGetLinkNumber();
+           if (ThisLink != 0 || ThisLink != 1) {
+               // This is not the root prim.
+               vector Up = llRot2Up(llGetLocalRot());
+               float DistanceToMove = Thickness / 2.0 - Scale.z / 2.0;
+               vector Pos = llGetLocalPos();
+               llSetPos(Pos + DistanceToMove * Up);
+           }
+                      // Apply the new thickness.
+           Scale.z = Thickness;
+           llSetScale(Scale);
+           return;
+       }
+       if (channel == SET_COLOR) {
+           vector newColor = (vector)data;
+           llSetColor(newColor, ALL_SIDES);
+       }
+   }
+} // Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: lsl/xytext/xytext.lsl.14

+ 320 - 0
assets/xytext/xytext.lslp.15

@@ -0,0 +1,320 @@
+////////////////////////////////////////////
+// XyText v1.2 Script (5 Face, Single Texture)
+//
+// Written by Xylor Baysklef
+//
+// Modified by Kermitt Quirk 19/01/2006
+// To add support for 5 face prim instead of 3
+//
+////////////////////////////////////////////
+
+/////////////// CONSTANTS ///////////////////
+// XyText Message Map.
+integer DISPLAY_STRING      = 204000;
+integer DISPLAY_EXTENDED    = 204001;
+integer REMAP_INDICES       = 204002;
+integer RESET_INDICES       = 204003;
+integer SET_CELL_INFO       = 204004;
+integer SET_FONT_TEXTURE    = 204005;
+integer SET_THICKNESS       = 204006;
+integer SET_COLOR           = 204007;
+
+// This is an extended character escape sequence.
+string  ESCAPE_SEQUENCE = "\\e";
+
+// This is used to get an index for the extended character.
+string  EXTENDED_INDEX  = "12345";
+
+// Face numbers.
+integer FACE_1          = 3;
+integer FACE_2          = 7;
+integer FACE_3          = 4;
+integer FACE_4          = 6;
+integer FACE_5          = 1;
+
+// Used to hide the text after a fade-out.
+//key     TRANSPARENT     = "701917a8-d614-471f-13dd-5f4644e36e3c";
+string      TRANSPARENT     = "Transparent";
+///////////// END CONSTANTS ////////////////
+
+///////////// GLOBAL VARIABLES ///////////////
+// This is the key of the font we are displaying.
+//key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
+string      gFontTexture    = "FontsTexture";
+// All displayable characters.  Default to ASCII order.
+string gCharIndex;
+// This is the channel to listen on while acting
+// as a cell in a larger display.
+integer gCellChannel        = 2;
+// This is the starting character position in the cell channel message
+// to render.
+integer gCellCharPosition   = 90;
+// This is whether or not to use the fade in/out special effect.
+integer gCellUseFading      = FALSE;
+// This is how long to display the text before fading out (if using
+// fading special effect).
+// Note: < 0  means don't fade out.
+float   gCellHoldDelay      = 1.0;
+/////////// END GLOBAL VARIABLES ////////////
+
+ResetCharIndex() {
+   gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
+   // \" <-- Fixes LSL syntax highlighting bug.
+   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
+   gCharIndex += "\n\n\n\n\n";
+}
+
+vector GetGridOffset(integer index) {
+   // Calculate the offset needed to display this character.
+   integer Row = index / 10;
+   integer Col = index % 10;
+
+   // Return the offset in the texture.
+   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
+}
+
+ShowChars(vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
+   // Set the primitive textures directly.
+   
+   // <-0.256, 0, 0>
+   // <0, 0, 0>
+   // <0.130, 0, 0>
+   // <0, 0, 0>
+   // <-0.74, 0, 0>
+   
+   llSetPrimitiveParams( [
+        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
+        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
+        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
+        ]);
+}
+
+RenderString(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+RenderWithEffects(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // First set the alpha to the lowest possible.
+   llSetAlpha(0.05, ALL_SIDES);
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.
+    float Alpha;
+    for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // See if we want to fade out as well.
+   if (gCellHoldDelay < 0.0)
+       // No, bail out. (Just keep showing the string at full strength).
+       return;
+          // Hold the text for a while.
+   llSleep(gCellHoldDelay);
+      // Now fade out.
+   for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // Make the text transparent to fully hide it.
+   llSetTexture(TRANSPARENT, ALL_SIDES);
+}
+
+RenderExtended(string str) {
+   // Look for escape sequences.
+   list Parsed       = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
+   integer ParsedLen = llGetListLength(Parsed);
+
+   // Create a list of index values to work with.
+   list Indices;
+   // We start with room for 5 indices.
+   integer IndicesLeft = 5;
+
+   integer i;
+   string Token;
+   integer Clipped;
+   integer LastWasEscapeSequence = FALSE;
+   // Work from left to right.
+   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {
+       Token = llList2String(Parsed, i);
+
+       // If this is an escape sequence, just set the flag and move on.
+       if (Token == ESCAPE_SEQUENCE) {
+           LastWasEscapeSequence = TRUE;
+       }
+       else { // Token != ESCAPE_SEQUENCE
+           // Otherwise this is a normal token.  Check its length.
+           Clipped = FALSE;
+           integer TokenLength = llStringLength(Token);
+           // Clip if necessary.
+           if (TokenLength > IndicesLeft) {
+               Token = llGetSubString(Token, 0, IndicesLeft - 1);
+               TokenLength = llStringLength(Token);
+               IndicesLeft = 0;
+               Clipped = TRUE;
+           }
+           else
+               IndicesLeft -= TokenLength;
+
+           // Was the previous token an escape sequence?
+           if (LastWasEscapeSequence) {
+               // Yes, the first character is an escape character, the rest are normal.
+
+               // This is the extended character.
+               Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
+
+               // These are the normal characters.
+               integer j;
+               for (j = 1; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+           else { // Normal string.
+               // Just add the characters normally.
+               integer j;
+               for (j = 0; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+
+           // Unset this flag, since this was not an escape sequence.
+           LastWasEscapeSequence = FALSE;
+       }
+   }
+
+   // Use the indices to create grid positions.
+   vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0));
+   vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
+   vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
+   vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
+   vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+integer ConvertIndex(integer index) {
+   // This converts from an ASCII based index to our indexing scheme.
+   if (index >= 32) // ' ' or higher
+       index -= 32;
+   else { // index < 32
+       // Quick bounds check.
+       if (index > 15)
+           index = 15;
+
+       index += 94; // extended characters
+   }
+
+   return index;
+}
+
+default {
+   state_entry() {
+       // Initialize the character index.
+       llSetTexture(TRANSPARENT, ALL_SIDES);
+       ResetCharIndex();
+   }
+ 
+   link_message(integer sender, integer channel, string data, key id) {
+       if (channel == DISPLAY_STRING) {
+           RenderString(data);
+           return;
+       }
+       if (channel == DISPLAY_EXTENDED) {
+           RenderExtended(data);
+           return;
+       }
+       if (channel == gCellChannel) {
+           // Extract the characters we are interested in, and use those to render.
+           string TextToRender = llGetSubString(data, gCellCharPosition, gCellCharPosition + 4);
+                      // See if we need to show special effects.
+           if (gCellUseFading)
+               RenderWithEffects( TextToRender );
+           else // !gCellUseFading
+               RenderString( TextToRender );
+           return;
+       }
+       if (channel == REMAP_INDICES) {
+           // Parse the message, splitting it up into index values.
+           list Parsed = llCSV2List(data);
+           integer i;
+           // Go through the list and swap each pair of indices.
+           for (i = 0; i < llGetListLength(Parsed); i += 2) {
+               integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
+               integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
+
+               // Swap these index values.
+               string Value1 = llGetSubString(gCharIndex, Index1, Index1);
+               string Value2 = llGetSubString(gCharIndex, Index2, Index2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
+               gCharIndex = llInsertString(gCharIndex, Index1, Value2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
+               gCharIndex = llInsertString(gCharIndex, Index2, Value1);
+           }
+           return;
+       }
+       if (channel == RESET_INDICES) {
+           // Restore the character index back to default settings.
+           ResetCharIndex();
+           return;
+       }
+       if (channel == SET_CELL_INFO) {
+           // Change the channel we listen to for cell commands, the
+           // starting character position to extract from, and
+           // special effect attributes.
+           list Parsed = llCSV2List(data);
+           gCellChannel        = (integer) llList2String(Parsed, 0);
+           gCellCharPosition   = (integer) llList2String(Parsed, 1);
+           gCellUseFading      = (integer) llList2String(Parsed, 2);
+           gCellHoldDelay      = (float)   llList2String(Parsed, 3);
+           return;
+       }
+       if (channel == SET_FONT_TEXTURE) {
+           // Use the new texture instead of the current one.
+           gFontTexture = id;
+           // Change the currently shown texture.
+           llSetTexture(gFontTexture, FACE_1);
+           llSetTexture(gFontTexture, FACE_2);
+           llSetTexture(gFontTexture, FACE_3);
+           llSetTexture(gFontTexture, FACE_4);
+           llSetTexture(gFontTexture, FACE_5);
+           return;
+       }
+       if (channel == SET_THICKNESS) {
+           // Set our z scale to thickness, while staying fixed
+           // in position relative the prim below us.
+           vector Scale    = llGetScale();
+           float Thickness = (float) data;
+                      // Reposition only if this isn't the root prim.
+           integer ThisLink = llGetLinkNumber();
+           if (ThisLink != 0 || ThisLink != 1) {
+               // This is not the root prim.
+               vector Up = llRot2Up(llGetLocalRot());
+               float DistanceToMove = Thickness / 2.0 - Scale.z / 2.0;
+               vector Pos = llGetLocalPos();
+               llSetPos(Pos + DistanceToMove * Up);
+           }
+                      // Apply the new thickness.
+           Scale.z = Thickness;
+           llSetScale(Scale);
+           return;
+       }
+       if (channel == SET_COLOR) {
+           vector newColor = (vector)data;
+           llSetColor(newColor, ALL_SIDES);
+       }
+   }
+} // Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: lsl/xytext/xytext.lsl.15

+ 320 - 0
assets/xytext/xytext.lslp.16

@@ -0,0 +1,320 @@
+////////////////////////////////////////////
+// XyText v1.2 Script (5 Face, Single Texture)
+//
+// Written by Xylor Baysklef
+//
+// Modified by Kermitt Quirk 19/01/2006
+// To add support for 5 face prim instead of 3
+//
+////////////////////////////////////////////
+
+/////////////// CONSTANTS ///////////////////
+// XyText Message Map.
+integer DISPLAY_STRING      = 204000;
+integer DISPLAY_EXTENDED    = 204001;
+integer REMAP_INDICES       = 204002;
+integer RESET_INDICES       = 204003;
+integer SET_CELL_INFO       = 204004;
+integer SET_FONT_TEXTURE    = 204005;
+integer SET_THICKNESS       = 204006;
+integer SET_COLOR           = 204007;
+
+// This is an extended character escape sequence.
+string  ESCAPE_SEQUENCE = "\\e";
+
+// This is used to get an index for the extended character.
+string  EXTENDED_INDEX  = "12345";
+
+// Face numbers.
+integer FACE_1          = 3;
+integer FACE_2          = 7;
+integer FACE_3          = 4;
+integer FACE_4          = 6;
+integer FACE_5          = 1;
+
+// Used to hide the text after a fade-out.
+//key     TRANSPARENT     = "701917a8-d614-471f-13dd-5f4644e36e3c";
+string      TRANSPARENT     = "Transparent";
+///////////// END CONSTANTS ////////////////
+
+///////////// GLOBAL VARIABLES ///////////////
+// This is the key of the font we are displaying.
+//key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
+string      gFontTexture    = "FontsTexture";
+// All displayable characters.  Default to ASCII order.
+string gCharIndex;
+// This is the channel to listen on while acting
+// as a cell in a larger display.
+integer gCellChannel        = 2;
+// This is the starting character position in the cell channel message
+// to render.
+integer gCellCharPosition   = 35;
+// This is whether or not to use the fade in/out special effect.
+integer gCellUseFading      = FALSE;
+// This is how long to display the text before fading out (if using
+// fading special effect).
+// Note: < 0  means don't fade out.
+float   gCellHoldDelay      = 1.0;
+/////////// END GLOBAL VARIABLES ////////////
+
+ResetCharIndex() {
+   gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
+   // \" <-- Fixes LSL syntax highlighting bug.
+   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
+   gCharIndex += "\n\n\n\n\n";
+}
+
+vector GetGridOffset(integer index) {
+   // Calculate the offset needed to display this character.
+   integer Row = index / 10;
+   integer Col = index % 10;
+
+   // Return the offset in the texture.
+   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
+}
+
+ShowChars(vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
+   // Set the primitive textures directly.
+   
+   // <-0.256, 0, 0>
+   // <0, 0, 0>
+   // <0.130, 0, 0>
+   // <0, 0, 0>
+   // <-0.74, 0, 0>
+   
+   llSetPrimitiveParams( [
+        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
+        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
+        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
+        ]);
+}
+
+RenderString(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+RenderWithEffects(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // First set the alpha to the lowest possible.
+   llSetAlpha(0.05, ALL_SIDES);
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.
+    float Alpha;
+    for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // See if we want to fade out as well.
+   if (gCellHoldDelay < 0.0)
+       // No, bail out. (Just keep showing the string at full strength).
+       return;
+          // Hold the text for a while.
+   llSleep(gCellHoldDelay);
+      // Now fade out.
+   for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // Make the text transparent to fully hide it.
+   llSetTexture(TRANSPARENT, ALL_SIDES);
+}
+
+RenderExtended(string str) {
+   // Look for escape sequences.
+   list Parsed       = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
+   integer ParsedLen = llGetListLength(Parsed);
+
+   // Create a list of index values to work with.
+   list Indices;
+   // We start with room for 5 indices.
+   integer IndicesLeft = 5;
+
+   integer i;
+   string Token;
+   integer Clipped;
+   integer LastWasEscapeSequence = FALSE;
+   // Work from left to right.
+   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {
+       Token = llList2String(Parsed, i);
+
+       // If this is an escape sequence, just set the flag and move on.
+       if (Token == ESCAPE_SEQUENCE) {
+           LastWasEscapeSequence = TRUE;
+       }
+       else { // Token != ESCAPE_SEQUENCE
+           // Otherwise this is a normal token.  Check its length.
+           Clipped = FALSE;
+           integer TokenLength = llStringLength(Token);
+           // Clip if necessary.
+           if (TokenLength > IndicesLeft) {
+               Token = llGetSubString(Token, 0, IndicesLeft - 1);
+               TokenLength = llStringLength(Token);
+               IndicesLeft = 0;
+               Clipped = TRUE;
+           }
+           else
+               IndicesLeft -= TokenLength;
+
+           // Was the previous token an escape sequence?
+           if (LastWasEscapeSequence) {
+               // Yes, the first character is an escape character, the rest are normal.
+
+               // This is the extended character.
+               Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
+
+               // These are the normal characters.
+               integer j;
+               for (j = 1; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+           else { // Normal string.
+               // Just add the characters normally.
+               integer j;
+               for (j = 0; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+
+           // Unset this flag, since this was not an escape sequence.
+           LastWasEscapeSequence = FALSE;
+       }
+   }
+
+   // Use the indices to create grid positions.
+   vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0));
+   vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
+   vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
+   vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
+   vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+integer ConvertIndex(integer index) {
+   // This converts from an ASCII based index to our indexing scheme.
+   if (index >= 32) // ' ' or higher
+       index -= 32;
+   else { // index < 32
+       // Quick bounds check.
+       if (index > 15)
+           index = 15;
+
+       index += 94; // extended characters
+   }
+
+   return index;
+}
+
+default {
+   state_entry() {
+       // Initialize the character index.
+       llSetTexture(TRANSPARENT, ALL_SIDES);
+       ResetCharIndex();
+   }
+ 
+   link_message(integer sender, integer channel, string data, key id) {
+       if (channel == DISPLAY_STRING) {
+           RenderString(data);
+           return;
+       }
+       if (channel == DISPLAY_EXTENDED) {
+           RenderExtended(data);
+           return;
+       }
+       if (channel == gCellChannel) {
+           // Extract the characters we are interested in, and use those to render.
+           string TextToRender = llGetSubString(data, gCellCharPosition, gCellCharPosition + 4);
+                      // See if we need to show special effects.
+           if (gCellUseFading)
+               RenderWithEffects( TextToRender );
+           else // !gCellUseFading
+               RenderString( TextToRender );
+           return;
+       }
+       if (channel == REMAP_INDICES) {
+           // Parse the message, splitting it up into index values.
+           list Parsed = llCSV2List(data);
+           integer i;
+           // Go through the list and swap each pair of indices.
+           for (i = 0; i < llGetListLength(Parsed); i += 2) {
+               integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
+               integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
+
+               // Swap these index values.
+               string Value1 = llGetSubString(gCharIndex, Index1, Index1);
+               string Value2 = llGetSubString(gCharIndex, Index2, Index2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
+               gCharIndex = llInsertString(gCharIndex, Index1, Value2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
+               gCharIndex = llInsertString(gCharIndex, Index2, Value1);
+           }
+           return;
+       }
+       if (channel == RESET_INDICES) {
+           // Restore the character index back to default settings.
+           ResetCharIndex();
+           return;
+       }
+       if (channel == SET_CELL_INFO) {
+           // Change the channel we listen to for cell commands, the
+           // starting character position to extract from, and
+           // special effect attributes.
+           list Parsed = llCSV2List(data);
+           gCellChannel        = (integer) llList2String(Parsed, 0);
+           gCellCharPosition   = (integer) llList2String(Parsed, 1);
+           gCellUseFading      = (integer) llList2String(Parsed, 2);
+           gCellHoldDelay      = (float)   llList2String(Parsed, 3);
+           return;
+       }
+       if (channel == SET_FONT_TEXTURE) {
+           // Use the new texture instead of the current one.
+           gFontTexture = id;
+           // Change the currently shown texture.
+           llSetTexture(gFontTexture, FACE_1);
+           llSetTexture(gFontTexture, FACE_2);
+           llSetTexture(gFontTexture, FACE_3);
+           llSetTexture(gFontTexture, FACE_4);
+           llSetTexture(gFontTexture, FACE_5);
+           return;
+       }
+       if (channel == SET_THICKNESS) {
+           // Set our z scale to thickness, while staying fixed
+           // in position relative the prim below us.
+           vector Scale    = llGetScale();
+           float Thickness = (float) data;
+                      // Reposition only if this isn't the root prim.
+           integer ThisLink = llGetLinkNumber();
+           if (ThisLink != 0 || ThisLink != 1) {
+               // This is not the root prim.
+               vector Up = llRot2Up(llGetLocalRot());
+               float DistanceToMove = Thickness / 2.0 - Scale.z / 2.0;
+               vector Pos = llGetLocalPos();
+               llSetPos(Pos + DistanceToMove * Up);
+           }
+                      // Apply the new thickness.
+           Scale.z = Thickness;
+           llSetScale(Scale);
+           return;
+       }
+       if (channel == SET_COLOR) {
+           vector newColor = (vector)data;
+           llSetColor(newColor, ALL_SIDES);
+       }
+   }
+} // Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: lsl/xytext/xytext.lsl.16

+ 320 - 0
assets/xytext/xytext.lslp.17

@@ -0,0 +1,320 @@
+////////////////////////////////////////////
+// XyText v1.2 Script (5 Face, Single Texture)
+//
+// Written by Xylor Baysklef
+//
+// Modified by Kermitt Quirk 19/01/2006
+// To add support for 5 face prim instead of 3
+//
+////////////////////////////////////////////
+
+/////////////// CONSTANTS ///////////////////
+// XyText Message Map.
+integer DISPLAY_STRING      = 204000;
+integer DISPLAY_EXTENDED    = 204001;
+integer REMAP_INDICES       = 204002;
+integer RESET_INDICES       = 204003;
+integer SET_CELL_INFO       = 204004;
+integer SET_FONT_TEXTURE    = 204005;
+integer SET_THICKNESS       = 204006;
+integer SET_COLOR           = 204007;
+
+// This is an extended character escape sequence.
+string  ESCAPE_SEQUENCE = "\\e";
+
+// This is used to get an index for the extended character.
+string  EXTENDED_INDEX  = "12345";
+
+// Face numbers.
+integer FACE_1          = 3;
+integer FACE_2          = 7;
+integer FACE_3          = 4;
+integer FACE_4          = 6;
+integer FACE_5          = 1;
+
+// Used to hide the text after a fade-out.
+//key     TRANSPARENT     = "701917a8-d614-471f-13dd-5f4644e36e3c";
+string      TRANSPARENT     = "Transparent";
+///////////// END CONSTANTS ////////////////
+
+///////////// GLOBAL VARIABLES ///////////////
+// This is the key of the font we are displaying.
+//key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
+string      gFontTexture    = "FontsTexture";
+// All displayable characters.  Default to ASCII order.
+string gCharIndex;
+// This is the channel to listen on while acting
+// as a cell in a larger display.
+integer gCellChannel        = 2;
+// This is the starting character position in the cell channel message
+// to render.
+integer gCellCharPosition   = 80;
+// This is whether or not to use the fade in/out special effect.
+integer gCellUseFading      = FALSE;
+// This is how long to display the text before fading out (if using
+// fading special effect).
+// Note: < 0  means don't fade out.
+float   gCellHoldDelay      = 1.0;
+/////////// END GLOBAL VARIABLES ////////////
+
+ResetCharIndex() {
+   gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
+   // \" <-- Fixes LSL syntax highlighting bug.
+   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
+   gCharIndex += "\n\n\n\n\n";
+}
+
+vector GetGridOffset(integer index) {
+   // Calculate the offset needed to display this character.
+   integer Row = index / 10;
+   integer Col = index % 10;
+
+   // Return the offset in the texture.
+   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
+}
+
+ShowChars(vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
+   // Set the primitive textures directly.
+   
+   // <-0.256, 0, 0>
+   // <0, 0, 0>
+   // <0.130, 0, 0>
+   // <0, 0, 0>
+   // <-0.74, 0, 0>
+   
+   llSetPrimitiveParams( [
+        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
+        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
+        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
+        ]);
+}
+
+RenderString(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+RenderWithEffects(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // First set the alpha to the lowest possible.
+   llSetAlpha(0.05, ALL_SIDES);
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.
+    float Alpha;
+    for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // See if we want to fade out as well.
+   if (gCellHoldDelay < 0.0)
+       // No, bail out. (Just keep showing the string at full strength).
+       return;
+          // Hold the text for a while.
+   llSleep(gCellHoldDelay);
+      // Now fade out.
+   for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // Make the text transparent to fully hide it.
+   llSetTexture(TRANSPARENT, ALL_SIDES);
+}
+
+RenderExtended(string str) {
+   // Look for escape sequences.
+   list Parsed       = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
+   integer ParsedLen = llGetListLength(Parsed);
+
+   // Create a list of index values to work with.
+   list Indices;
+   // We start with room for 5 indices.
+   integer IndicesLeft = 5;
+
+   integer i;
+   string Token;
+   integer Clipped;
+   integer LastWasEscapeSequence = FALSE;
+   // Work from left to right.
+   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {
+       Token = llList2String(Parsed, i);
+
+       // If this is an escape sequence, just set the flag and move on.
+       if (Token == ESCAPE_SEQUENCE) {
+           LastWasEscapeSequence = TRUE;
+       }
+       else { // Token != ESCAPE_SEQUENCE
+           // Otherwise this is a normal token.  Check its length.
+           Clipped = FALSE;
+           integer TokenLength = llStringLength(Token);
+           // Clip if necessary.
+           if (TokenLength > IndicesLeft) {
+               Token = llGetSubString(Token, 0, IndicesLeft - 1);
+               TokenLength = llStringLength(Token);
+               IndicesLeft = 0;
+               Clipped = TRUE;
+           }
+           else
+               IndicesLeft -= TokenLength;
+
+           // Was the previous token an escape sequence?
+           if (LastWasEscapeSequence) {
+               // Yes, the first character is an escape character, the rest are normal.
+
+               // This is the extended character.
+               Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
+
+               // These are the normal characters.
+               integer j;
+               for (j = 1; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+           else { // Normal string.
+               // Just add the characters normally.
+               integer j;
+               for (j = 0; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+
+           // Unset this flag, since this was not an escape sequence.
+           LastWasEscapeSequence = FALSE;
+       }
+   }
+
+   // Use the indices to create grid positions.
+   vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0));
+   vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
+   vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
+   vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
+   vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+integer ConvertIndex(integer index) {
+   // This converts from an ASCII based index to our indexing scheme.
+   if (index >= 32) // ' ' or higher
+       index -= 32;
+   else { // index < 32
+       // Quick bounds check.
+       if (index > 15)
+           index = 15;
+
+       index += 94; // extended characters
+   }
+
+   return index;
+}
+
+default {
+   state_entry() {
+       // Initialize the character index.
+       llSetTexture(TRANSPARENT, ALL_SIDES);
+       ResetCharIndex();
+   }
+ 
+   link_message(integer sender, integer channel, string data, key id) {
+       if (channel == DISPLAY_STRING) {
+           RenderString(data);
+           return;
+       }
+       if (channel == DISPLAY_EXTENDED) {
+           RenderExtended(data);
+           return;
+       }
+       if (channel == gCellChannel) {
+           // Extract the characters we are interested in, and use those to render.
+           string TextToRender = llGetSubString(data, gCellCharPosition, gCellCharPosition + 4);
+                      // See if we need to show special effects.
+           if (gCellUseFading)
+               RenderWithEffects( TextToRender );
+           else // !gCellUseFading
+               RenderString( TextToRender );
+           return;
+       }
+       if (channel == REMAP_INDICES) {
+           // Parse the message, splitting it up into index values.
+           list Parsed = llCSV2List(data);
+           integer i;
+           // Go through the list and swap each pair of indices.
+           for (i = 0; i < llGetListLength(Parsed); i += 2) {
+               integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
+               integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
+
+               // Swap these index values.
+               string Value1 = llGetSubString(gCharIndex, Index1, Index1);
+               string Value2 = llGetSubString(gCharIndex, Index2, Index2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
+               gCharIndex = llInsertString(gCharIndex, Index1, Value2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
+               gCharIndex = llInsertString(gCharIndex, Index2, Value1);
+           }
+           return;
+       }
+       if (channel == RESET_INDICES) {
+           // Restore the character index back to default settings.
+           ResetCharIndex();
+           return;
+       }
+       if (channel == SET_CELL_INFO) {
+           // Change the channel we listen to for cell commands, the
+           // starting character position to extract from, and
+           // special effect attributes.
+           list Parsed = llCSV2List(data);
+           gCellChannel        = (integer) llList2String(Parsed, 0);
+           gCellCharPosition   = (integer) llList2String(Parsed, 1);
+           gCellUseFading      = (integer) llList2String(Parsed, 2);
+           gCellHoldDelay      = (float)   llList2String(Parsed, 3);
+           return;
+       }
+       if (channel == SET_FONT_TEXTURE) {
+           // Use the new texture instead of the current one.
+           gFontTexture = id;
+           // Change the currently shown texture.
+           llSetTexture(gFontTexture, FACE_1);
+           llSetTexture(gFontTexture, FACE_2);
+           llSetTexture(gFontTexture, FACE_3);
+           llSetTexture(gFontTexture, FACE_4);
+           llSetTexture(gFontTexture, FACE_5);
+           return;
+       }
+       if (channel == SET_THICKNESS) {
+           // Set our z scale to thickness, while staying fixed
+           // in position relative the prim below us.
+           vector Scale    = llGetScale();
+           float Thickness = (float) data;
+                      // Reposition only if this isn't the root prim.
+           integer ThisLink = llGetLinkNumber();
+           if (ThisLink != 0 || ThisLink != 1) {
+               // This is not the root prim.
+               vector Up = llRot2Up(llGetLocalRot());
+               float DistanceToMove = Thickness / 2.0 - Scale.z / 2.0;
+               vector Pos = llGetLocalPos();
+               llSetPos(Pos + DistanceToMove * Up);
+           }
+                      // Apply the new thickness.
+           Scale.z = Thickness;
+           llSetScale(Scale);
+           return;
+       }
+       if (channel == SET_COLOR) {
+           vector newColor = (vector)data;
+           llSetColor(newColor, ALL_SIDES);
+       }
+   }
+} // Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: lsl/xytext/xytext.lsl.17

+ 320 - 0
assets/xytext/xytext.lslp.18

@@ -0,0 +1,320 @@
+////////////////////////////////////////////
+// XyText v1.2 Script (5 Face, Single Texture)
+//
+// Written by Xylor Baysklef
+//
+// Modified by Kermitt Quirk 19/01/2006
+// To add support for 5 face prim instead of 3
+//
+////////////////////////////////////////////
+
+/////////////// CONSTANTS ///////////////////
+// XyText Message Map.
+integer DISPLAY_STRING      = 204000;
+integer DISPLAY_EXTENDED    = 204001;
+integer REMAP_INDICES       = 204002;
+integer RESET_INDICES       = 204003;
+integer SET_CELL_INFO       = 204004;
+integer SET_FONT_TEXTURE    = 204005;
+integer SET_THICKNESS       = 204006;
+integer SET_COLOR           = 204007;
+
+// This is an extended character escape sequence.
+string  ESCAPE_SEQUENCE = "\\e";
+
+// This is used to get an index for the extended character.
+string  EXTENDED_INDEX  = "12345";
+
+// Face numbers.
+integer FACE_1          = 3;
+integer FACE_2          = 7;
+integer FACE_3          = 4;
+integer FACE_4          = 6;
+integer FACE_5          = 1;
+
+// Used to hide the text after a fade-out.
+//key     TRANSPARENT     = "701917a8-d614-471f-13dd-5f4644e36e3c";
+string      TRANSPARENT     = "Transparent";
+///////////// END CONSTANTS ////////////////
+
+///////////// GLOBAL VARIABLES ///////////////
+// This is the key of the font we are displaying.
+//key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
+string      gFontTexture    = "FontsTexture";
+// All displayable characters.  Default to ASCII order.
+string gCharIndex;
+// This is the channel to listen on while acting
+// as a cell in a larger display.
+integer gCellChannel        = 2;
+// This is the starting character position in the cell channel message
+// to render.
+integer gCellCharPosition   = 45;
+// This is whether or not to use the fade in/out special effect.
+integer gCellUseFading      = FALSE;
+// This is how long to display the text before fading out (if using
+// fading special effect).
+// Note: < 0  means don't fade out.
+float   gCellHoldDelay      = 1.0;
+/////////// END GLOBAL VARIABLES ////////////
+
+ResetCharIndex() {
+   gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
+   // \" <-- Fixes LSL syntax highlighting bug.
+   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
+   gCharIndex += "\n\n\n\n\n";
+}
+
+vector GetGridOffset(integer index) {
+   // Calculate the offset needed to display this character.
+   integer Row = index / 10;
+   integer Col = index % 10;
+
+   // Return the offset in the texture.
+   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
+}
+
+ShowChars(vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
+   // Set the primitive textures directly.
+   
+   // <-0.256, 0, 0>
+   // <0, 0, 0>
+   // <0.130, 0, 0>
+   // <0, 0, 0>
+   // <-0.74, 0, 0>
+   
+   llSetPrimitiveParams( [
+        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
+        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
+        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
+        ]);
+}
+
+RenderString(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+RenderWithEffects(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // First set the alpha to the lowest possible.
+   llSetAlpha(0.05, ALL_SIDES);
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.
+    float Alpha;
+    for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // See if we want to fade out as well.
+   if (gCellHoldDelay < 0.0)
+       // No, bail out. (Just keep showing the string at full strength).
+       return;
+          // Hold the text for a while.
+   llSleep(gCellHoldDelay);
+      // Now fade out.
+   for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // Make the text transparent to fully hide it.
+   llSetTexture(TRANSPARENT, ALL_SIDES);
+}
+
+RenderExtended(string str) {
+   // Look for escape sequences.
+   list Parsed       = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
+   integer ParsedLen = llGetListLength(Parsed);
+
+   // Create a list of index values to work with.
+   list Indices;
+   // We start with room for 5 indices.
+   integer IndicesLeft = 5;
+
+   integer i;
+   string Token;
+   integer Clipped;
+   integer LastWasEscapeSequence = FALSE;
+   // Work from left to right.
+   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {
+       Token = llList2String(Parsed, i);
+
+       // If this is an escape sequence, just set the flag and move on.
+       if (Token == ESCAPE_SEQUENCE) {
+           LastWasEscapeSequence = TRUE;
+       }
+       else { // Token != ESCAPE_SEQUENCE
+           // Otherwise this is a normal token.  Check its length.
+           Clipped = FALSE;
+           integer TokenLength = llStringLength(Token);
+           // Clip if necessary.
+           if (TokenLength > IndicesLeft) {
+               Token = llGetSubString(Token, 0, IndicesLeft - 1);
+               TokenLength = llStringLength(Token);
+               IndicesLeft = 0;
+               Clipped = TRUE;
+           }
+           else
+               IndicesLeft -= TokenLength;
+
+           // Was the previous token an escape sequence?
+           if (LastWasEscapeSequence) {
+               // Yes, the first character is an escape character, the rest are normal.
+
+               // This is the extended character.
+               Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
+
+               // These are the normal characters.
+               integer j;
+               for (j = 1; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+           else { // Normal string.
+               // Just add the characters normally.
+               integer j;
+               for (j = 0; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+
+           // Unset this flag, since this was not an escape sequence.
+           LastWasEscapeSequence = FALSE;
+       }
+   }
+
+   // Use the indices to create grid positions.
+   vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0));
+   vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
+   vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
+   vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
+   vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+integer ConvertIndex(integer index) {
+   // This converts from an ASCII based index to our indexing scheme.
+   if (index >= 32) // ' ' or higher
+       index -= 32;
+   else { // index < 32
+       // Quick bounds check.
+       if (index > 15)
+           index = 15;
+
+       index += 94; // extended characters
+   }
+
+   return index;
+}
+
+default {
+   state_entry() {
+       // Initialize the character index.
+       llSetTexture(TRANSPARENT, ALL_SIDES);
+       ResetCharIndex();
+   }
+ 
+   link_message(integer sender, integer channel, string data, key id) {
+       if (channel == DISPLAY_STRING) {
+           RenderString(data);
+           return;
+       }
+       if (channel == DISPLAY_EXTENDED) {
+           RenderExtended(data);
+           return;
+       }
+       if (channel == gCellChannel) {
+           // Extract the characters we are interested in, and use those to render.
+           string TextToRender = llGetSubString(data, gCellCharPosition, gCellCharPosition + 4);
+                      // See if we need to show special effects.
+           if (gCellUseFading)
+               RenderWithEffects( TextToRender );
+           else // !gCellUseFading
+               RenderString( TextToRender );
+           return;
+       }
+       if (channel == REMAP_INDICES) {
+           // Parse the message, splitting it up into index values.
+           list Parsed = llCSV2List(data);
+           integer i;
+           // Go through the list and swap each pair of indices.
+           for (i = 0; i < llGetListLength(Parsed); i += 2) {
+               integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
+               integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
+
+               // Swap these index values.
+               string Value1 = llGetSubString(gCharIndex, Index1, Index1);
+               string Value2 = llGetSubString(gCharIndex, Index2, Index2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
+               gCharIndex = llInsertString(gCharIndex, Index1, Value2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
+               gCharIndex = llInsertString(gCharIndex, Index2, Value1);
+           }
+           return;
+       }
+       if (channel == RESET_INDICES) {
+           // Restore the character index back to default settings.
+           ResetCharIndex();
+           return;
+       }
+       if (channel == SET_CELL_INFO) {
+           // Change the channel we listen to for cell commands, the
+           // starting character position to extract from, and
+           // special effect attributes.
+           list Parsed = llCSV2List(data);
+           gCellChannel        = (integer) llList2String(Parsed, 0);
+           gCellCharPosition   = (integer) llList2String(Parsed, 1);
+           gCellUseFading      = (integer) llList2String(Parsed, 2);
+           gCellHoldDelay      = (float)   llList2String(Parsed, 3);
+           return;
+       }
+       if (channel == SET_FONT_TEXTURE) {
+           // Use the new texture instead of the current one.
+           gFontTexture = id;
+           // Change the currently shown texture.
+           llSetTexture(gFontTexture, FACE_1);
+           llSetTexture(gFontTexture, FACE_2);
+           llSetTexture(gFontTexture, FACE_3);
+           llSetTexture(gFontTexture, FACE_4);
+           llSetTexture(gFontTexture, FACE_5);
+           return;
+       }
+       if (channel == SET_THICKNESS) {
+           // Set our z scale to thickness, while staying fixed
+           // in position relative the prim below us.
+           vector Scale    = llGetScale();
+           float Thickness = (float) data;
+                      // Reposition only if this isn't the root prim.
+           integer ThisLink = llGetLinkNumber();
+           if (ThisLink != 0 || ThisLink != 1) {
+               // This is not the root prim.
+               vector Up = llRot2Up(llGetLocalRot());
+               float DistanceToMove = Thickness / 2.0 - Scale.z / 2.0;
+               vector Pos = llGetLocalPos();
+               llSetPos(Pos + DistanceToMove * Up);
+           }
+                      // Apply the new thickness.
+           Scale.z = Thickness;
+           llSetScale(Scale);
+           return;
+       }
+       if (channel == SET_COLOR) {
+           vector newColor = (vector)data;
+           llSetColor(newColor, ALL_SIDES);
+       }
+   }
+} // Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: lsl/xytext/xytext.lsl.18

+ 320 - 0
assets/xytext/xytext.lslp.19

@@ -0,0 +1,320 @@
+////////////////////////////////////////////
+// XyText v1.2 Script (5 Face, Single Texture)
+//
+// Written by Xylor Baysklef
+//
+// Modified by Kermitt Quirk 19/01/2006
+// To add support for 5 face prim instead of 3
+//
+////////////////////////////////////////////
+
+/////////////// CONSTANTS ///////////////////
+// XyText Message Map.
+integer DISPLAY_STRING      = 204000;
+integer DISPLAY_EXTENDED    = 204001;
+integer REMAP_INDICES       = 204002;
+integer RESET_INDICES       = 204003;
+integer SET_CELL_INFO       = 204004;
+integer SET_FONT_TEXTURE    = 204005;
+integer SET_THICKNESS       = 204006;
+integer SET_COLOR           = 204007;
+
+// This is an extended character escape sequence.
+string  ESCAPE_SEQUENCE = "\\e";
+
+// This is used to get an index for the extended character.
+string  EXTENDED_INDEX  = "12345";
+
+// Face numbers.
+integer FACE_1          = 3;
+integer FACE_2          = 7;
+integer FACE_3          = 4;
+integer FACE_4          = 6;
+integer FACE_5          = 1;
+
+// Used to hide the text after a fade-out.
+//key     TRANSPARENT     = "701917a8-d614-471f-13dd-5f4644e36e3c";
+string      TRANSPARENT     = "Transparent";
+///////////// END CONSTANTS ////////////////
+
+///////////// GLOBAL VARIABLES ///////////////
+// This is the key of the font we are displaying.
+//key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
+string      gFontTexture    = "FontsTexture";
+// All displayable characters.  Default to ASCII order.
+string gCharIndex;
+// This is the channel to listen on while acting
+// as a cell in a larger display.
+integer gCellChannel        = 2;
+// This is the starting character position in the cell channel message
+// to render.
+integer gCellCharPosition   = 70;
+// This is whether or not to use the fade in/out special effect.
+integer gCellUseFading      = FALSE;
+// This is how long to display the text before fading out (if using
+// fading special effect).
+// Note: < 0  means don't fade out.
+float   gCellHoldDelay      = 1.0;
+/////////// END GLOBAL VARIABLES ////////////
+
+ResetCharIndex() {
+   gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
+   // \" <-- Fixes LSL syntax highlighting bug.
+   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
+   gCharIndex += "\n\n\n\n\n";
+}
+
+vector GetGridOffset(integer index) {
+   // Calculate the offset needed to display this character.
+   integer Row = index / 10;
+   integer Col = index % 10;
+
+   // Return the offset in the texture.
+   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
+}
+
+ShowChars(vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
+   // Set the primitive textures directly.
+   
+   // <-0.256, 0, 0>
+   // <0, 0, 0>
+   // <0.130, 0, 0>
+   // <0, 0, 0>
+   // <-0.74, 0, 0>
+   
+   llSetPrimitiveParams( [
+        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
+        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
+        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
+        ]);
+}
+
+RenderString(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+RenderWithEffects(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // First set the alpha to the lowest possible.
+   llSetAlpha(0.05, ALL_SIDES);
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.
+    float Alpha;
+    for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // See if we want to fade out as well.
+   if (gCellHoldDelay < 0.0)
+       // No, bail out. (Just keep showing the string at full strength).
+       return;
+          // Hold the text for a while.
+   llSleep(gCellHoldDelay);
+      // Now fade out.
+   for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // Make the text transparent to fully hide it.
+   llSetTexture(TRANSPARENT, ALL_SIDES);
+}
+
+RenderExtended(string str) {
+   // Look for escape sequences.
+   list Parsed       = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
+   integer ParsedLen = llGetListLength(Parsed);
+
+   // Create a list of index values to work with.
+   list Indices;
+   // We start with room for 5 indices.
+   integer IndicesLeft = 5;
+
+   integer i;
+   string Token;
+   integer Clipped;
+   integer LastWasEscapeSequence = FALSE;
+   // Work from left to right.
+   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {
+       Token = llList2String(Parsed, i);
+
+       // If this is an escape sequence, just set the flag and move on.
+       if (Token == ESCAPE_SEQUENCE) {
+           LastWasEscapeSequence = TRUE;
+       }
+       else { // Token != ESCAPE_SEQUENCE
+           // Otherwise this is a normal token.  Check its length.
+           Clipped = FALSE;
+           integer TokenLength = llStringLength(Token);
+           // Clip if necessary.
+           if (TokenLength > IndicesLeft) {
+               Token = llGetSubString(Token, 0, IndicesLeft - 1);
+               TokenLength = llStringLength(Token);
+               IndicesLeft = 0;
+               Clipped = TRUE;
+           }
+           else
+               IndicesLeft -= TokenLength;
+
+           // Was the previous token an escape sequence?
+           if (LastWasEscapeSequence) {
+               // Yes, the first character is an escape character, the rest are normal.
+
+               // This is the extended character.
+               Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
+
+               // These are the normal characters.
+               integer j;
+               for (j = 1; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+           else { // Normal string.
+               // Just add the characters normally.
+               integer j;
+               for (j = 0; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+
+           // Unset this flag, since this was not an escape sequence.
+           LastWasEscapeSequence = FALSE;
+       }
+   }
+
+   // Use the indices to create grid positions.
+   vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0));
+   vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
+   vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
+   vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
+   vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+integer ConvertIndex(integer index) {
+   // This converts from an ASCII based index to our indexing scheme.
+   if (index >= 32) // ' ' or higher
+       index -= 32;
+   else { // index < 32
+       // Quick bounds check.
+       if (index > 15)
+           index = 15;
+
+       index += 94; // extended characters
+   }
+
+   return index;
+}
+
+default {
+   state_entry() {
+       // Initialize the character index.
+       llSetTexture(TRANSPARENT, ALL_SIDES);
+       ResetCharIndex();
+   }
+ 
+   link_message(integer sender, integer channel, string data, key id) {
+       if (channel == DISPLAY_STRING) {
+           RenderString(data);
+           return;
+       }
+       if (channel == DISPLAY_EXTENDED) {
+           RenderExtended(data);
+           return;
+       }
+       if (channel == gCellChannel) {
+           // Extract the characters we are interested in, and use those to render.
+           string TextToRender = llGetSubString(data, gCellCharPosition, gCellCharPosition + 4);
+                      // See if we need to show special effects.
+           if (gCellUseFading)
+               RenderWithEffects( TextToRender );
+           else // !gCellUseFading
+               RenderString( TextToRender );
+           return;
+       }
+       if (channel == REMAP_INDICES) {
+           // Parse the message, splitting it up into index values.
+           list Parsed = llCSV2List(data);
+           integer i;
+           // Go through the list and swap each pair of indices.
+           for (i = 0; i < llGetListLength(Parsed); i += 2) {
+               integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
+               integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
+
+               // Swap these index values.
+               string Value1 = llGetSubString(gCharIndex, Index1, Index1);
+               string Value2 = llGetSubString(gCharIndex, Index2, Index2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
+               gCharIndex = llInsertString(gCharIndex, Index1, Value2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
+               gCharIndex = llInsertString(gCharIndex, Index2, Value1);
+           }
+           return;
+       }
+       if (channel == RESET_INDICES) {
+           // Restore the character index back to default settings.
+           ResetCharIndex();
+           return;
+       }
+       if (channel == SET_CELL_INFO) {
+           // Change the channel we listen to for cell commands, the
+           // starting character position to extract from, and
+           // special effect attributes.
+           list Parsed = llCSV2List(data);
+           gCellChannel        = (integer) llList2String(Parsed, 0);
+           gCellCharPosition   = (integer) llList2String(Parsed, 1);
+           gCellUseFading      = (integer) llList2String(Parsed, 2);
+           gCellHoldDelay      = (float)   llList2String(Parsed, 3);
+           return;
+       }
+       if (channel == SET_FONT_TEXTURE) {
+           // Use the new texture instead of the current one.
+           gFontTexture = id;
+           // Change the currently shown texture.
+           llSetTexture(gFontTexture, FACE_1);
+           llSetTexture(gFontTexture, FACE_2);
+           llSetTexture(gFontTexture, FACE_3);
+           llSetTexture(gFontTexture, FACE_4);
+           llSetTexture(gFontTexture, FACE_5);
+           return;
+       }
+       if (channel == SET_THICKNESS) {
+           // Set our z scale to thickness, while staying fixed
+           // in position relative the prim below us.
+           vector Scale    = llGetScale();
+           float Thickness = (float) data;
+                      // Reposition only if this isn't the root prim.
+           integer ThisLink = llGetLinkNumber();
+           if (ThisLink != 0 || ThisLink != 1) {
+               // This is not the root prim.
+               vector Up = llRot2Up(llGetLocalRot());
+               float DistanceToMove = Thickness / 2.0 - Scale.z / 2.0;
+               vector Pos = llGetLocalPos();
+               llSetPos(Pos + DistanceToMove * Up);
+           }
+                      // Apply the new thickness.
+           Scale.z = Thickness;
+           llSetScale(Scale);
+           return;
+       }
+       if (channel == SET_COLOR) {
+           vector newColor = (vector)data;
+           llSetColor(newColor, ALL_SIDES);
+       }
+   }
+} // Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: lsl/xytext/xytext.lsl.19

+ 320 - 0
assets/xytext/xytext.lslp.2

@@ -0,0 +1,320 @@
+////////////////////////////////////////////
+// XyText v1.2 Script (5 Face, Single Texture)
+//
+// Written by Xylor Baysklef
+//
+// Modified by Kermitt Quirk 19/01/2006
+// To add support for 5 face prim instead of 3
+//
+////////////////////////////////////////////
+
+/////////////// CONSTANTS ///////////////////
+// XyText Message Map.
+integer DISPLAY_STRING      = 204000;
+integer DISPLAY_EXTENDED    = 204001;
+integer REMAP_INDICES       = 204002;
+integer RESET_INDICES       = 204003;
+integer SET_CELL_INFO       = 204004;
+integer SET_FONT_TEXTURE    = 204005;
+integer SET_THICKNESS       = 204006;
+integer SET_COLOR           = 204007;
+
+// This is an extended character escape sequence.
+string  ESCAPE_SEQUENCE = "\\e";
+
+// This is used to get an index for the extended character.
+string  EXTENDED_INDEX  = "12345";
+
+// Face numbers.
+integer FACE_1          = 3;
+integer FACE_2          = 7;
+integer FACE_3          = 4;
+integer FACE_4          = 6;
+integer FACE_5          = 1;
+
+// Used to hide the text after a fade-out.
+//key     TRANSPARENT     = "701917a8-d614-471f-13dd-5f4644e36e3c";
+string      TRANSPARENT     = "Transparent";
+///////////// END CONSTANTS ////////////////
+
+///////////// GLOBAL VARIABLES ///////////////
+// This is the key of the font we are displaying.
+//key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
+string      gFontTexture    = "FontsTexture";
+// All displayable characters.  Default to ASCII order.
+string gCharIndex;
+// This is the channel to listen on while acting
+// as a cell in a larger display.
+integer gCellChannel        = 2;
+// This is the starting character position in the cell channel message
+// to render.
+integer gCellCharPosition   = 0;
+// This is whether or not to use the fade in/out special effect.
+integer gCellUseFading      = FALSE;
+// This is how long to display the text before fading out (if using
+// fading special effect).
+// Note: < 0  means don't fade out.
+float   gCellHoldDelay      = 1.0;
+/////////// END GLOBAL VARIABLES ////////////
+
+ResetCharIndex() {
+   gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
+   // \" <-- Fixes LSL syntax highlighting bug.
+   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
+   gCharIndex += "\n\n\n\n\n";
+}
+
+vector GetGridOffset(integer index) {
+   // Calculate the offset needed to display this character.
+   integer Row = index / 10;
+   integer Col = index % 10;
+
+   // Return the offset in the texture.
+   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
+}
+
+ShowChars(vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
+   // Set the primitive textures directly.
+   
+   // <-0.256, 0, 0>
+   // <0, 0, 0>
+   // <0.130, 0, 0>
+   // <0, 0, 0>
+   // <-0.74, 0, 0>
+   
+   llSetPrimitiveParams( [
+        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
+        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
+        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
+        ]);
+}
+
+RenderString(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+RenderWithEffects(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // First set the alpha to the lowest possible.
+   llSetAlpha(0.05, ALL_SIDES);
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.
+    float Alpha;
+    for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // See if we want to fade out as well.
+   if (gCellHoldDelay < 0.0)
+       // No, bail out. (Just keep showing the string at full strength).
+       return;
+          // Hold the text for a while.
+   llSleep(gCellHoldDelay);
+      // Now fade out.
+   for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // Make the text transparent to fully hide it.
+   llSetTexture(TRANSPARENT, ALL_SIDES);
+}
+
+RenderExtended(string str) {
+   // Look for escape sequences.
+   list Parsed       = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
+   integer ParsedLen = llGetListLength(Parsed);
+
+   // Create a list of index values to work with.
+   list Indices;
+   // We start with room for 5 indices.
+   integer IndicesLeft = 5;
+
+   integer i;
+   string Token;
+   integer Clipped;
+   integer LastWasEscapeSequence = FALSE;
+   // Work from left to right.
+   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {
+       Token = llList2String(Parsed, i);
+
+       // If this is an escape sequence, just set the flag and move on.
+       if (Token == ESCAPE_SEQUENCE) {
+           LastWasEscapeSequence = TRUE;
+       }
+       else { // Token != ESCAPE_SEQUENCE
+           // Otherwise this is a normal token.  Check its length.
+           Clipped = FALSE;
+           integer TokenLength = llStringLength(Token);
+           // Clip if necessary.
+           if (TokenLength > IndicesLeft) {
+               Token = llGetSubString(Token, 0, IndicesLeft - 1);
+               TokenLength = llStringLength(Token);
+               IndicesLeft = 0;
+               Clipped = TRUE;
+           }
+           else
+               IndicesLeft -= TokenLength;
+
+           // Was the previous token an escape sequence?
+           if (LastWasEscapeSequence) {
+               // Yes, the first character is an escape character, the rest are normal.
+
+               // This is the extended character.
+               Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
+
+               // These are the normal characters.
+               integer j;
+               for (j = 1; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+           else { // Normal string.
+               // Just add the characters normally.
+               integer j;
+               for (j = 0; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+
+           // Unset this flag, since this was not an escape sequence.
+           LastWasEscapeSequence = FALSE;
+       }
+   }
+
+   // Use the indices to create grid positions.
+   vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0));
+   vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
+   vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
+   vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
+   vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+integer ConvertIndex(integer index) {
+   // This converts from an ASCII based index to our indexing scheme.
+   if (index >= 32) // ' ' or higher
+       index -= 32;
+   else { // index < 32
+       // Quick bounds check.
+       if (index > 15)
+           index = 15;
+
+       index += 94; // extended characters
+   }
+
+   return index;
+}
+
+default {
+   state_entry() {
+       // Initialize the character index.
+       llSetTexture(TRANSPARENT, ALL_SIDES);
+       ResetCharIndex();
+   }
+ 
+   link_message(integer sender, integer channel, string data, key id) {
+       if (channel == DISPLAY_STRING) {
+           RenderString(data);
+           return;
+       }
+       if (channel == DISPLAY_EXTENDED) {
+           RenderExtended(data);
+           return;
+       }
+       if (channel == gCellChannel) {
+           // Extract the characters we are interested in, and use those to render.
+           string TextToRender = llGetSubString(data, gCellCharPosition, gCellCharPosition + 4);
+                      // See if we need to show special effects.
+           if (gCellUseFading)
+               RenderWithEffects( TextToRender );
+           else // !gCellUseFading
+               RenderString( TextToRender );
+           return;
+       }
+       if (channel == REMAP_INDICES) {
+           // Parse the message, splitting it up into index values.
+           list Parsed = llCSV2List(data);
+           integer i;
+           // Go through the list and swap each pair of indices.
+           for (i = 0; i < llGetListLength(Parsed); i += 2) {
+               integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
+               integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
+
+               // Swap these index values.
+               string Value1 = llGetSubString(gCharIndex, Index1, Index1);
+               string Value2 = llGetSubString(gCharIndex, Index2, Index2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
+               gCharIndex = llInsertString(gCharIndex, Index1, Value2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
+               gCharIndex = llInsertString(gCharIndex, Index2, Value1);
+           }
+           return;
+       }
+       if (channel == RESET_INDICES) {
+           // Restore the character index back to default settings.
+           ResetCharIndex();
+           return;
+       }
+       if (channel == SET_CELL_INFO) {
+           // Change the channel we listen to for cell commands, the
+           // starting character position to extract from, and
+           // special effect attributes.
+           list Parsed = llCSV2List(data);
+           gCellChannel        = (integer) llList2String(Parsed, 0);
+           gCellCharPosition   = (integer) llList2String(Parsed, 1);
+           gCellUseFading      = (integer) llList2String(Parsed, 2);
+           gCellHoldDelay      = (float)   llList2String(Parsed, 3);
+           return;
+       }
+       if (channel == SET_FONT_TEXTURE) {
+           // Use the new texture instead of the current one.
+           gFontTexture = id;
+           // Change the currently shown texture.
+           llSetTexture(gFontTexture, FACE_1);
+           llSetTexture(gFontTexture, FACE_2);
+           llSetTexture(gFontTexture, FACE_3);
+           llSetTexture(gFontTexture, FACE_4);
+           llSetTexture(gFontTexture, FACE_5);
+           return;
+       }
+       if (channel == SET_THICKNESS) {
+           // Set our z scale to thickness, while staying fixed
+           // in position relative the prim below us.
+           vector Scale    = llGetScale();
+           float Thickness = (float) data;
+                      // Reposition only if this isn't the root prim.
+           integer ThisLink = llGetLinkNumber();
+           if (ThisLink != 0 || ThisLink != 1) {
+               // This is not the root prim.
+               vector Up = llRot2Up(llGetLocalRot());
+               float DistanceToMove = Thickness / 2.0 - Scale.z / 2.0;
+               vector Pos = llGetLocalPos();
+               llSetPos(Pos + DistanceToMove * Up);
+           }
+                      // Apply the new thickness.
+           Scale.z = Thickness;
+           llSetScale(Scale);
+           return;
+       }
+       if (channel == SET_COLOR) {
+           vector newColor = (vector)data;
+           llSetColor(newColor, ALL_SIDES);
+       }
+   }
+} // Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: lsl/xytext/xytext.lsl.2

+ 321 - 0
assets/xytext/xytext.lslp.20

@@ -0,0 +1,321 @@
+////////////////////////////////////////////
+// XyText v1.2 Script (5 Face, Single Texture)
+//
+// Written by Xylor Baysklef
+//
+// Modified by Kermitt Quirk 19/01/2006
+// To add support for 5 face prim instead of 3
+//
+////////////////////////////////////////////
+
+/////////////// CONSTANTS ///////////////////
+// XyText Message Map.
+integer DISPLAY_STRING      = 204000;
+integer DISPLAY_EXTENDED    = 204001;
+integer REMAP_INDICES       = 204002;
+integer RESET_INDICES       = 204003;
+integer SET_CELL_INFO       = 204004;
+integer SET_FONT_TEXTURE    = 204005;
+integer SET_THICKNESS       = 204006;
+integer SET_COLOR           = 204007;
+
+// This is an extended character escape sequence.
+string  ESCAPE_SEQUENCE = "\\e";
+
+// This is used to get an index for the extended character.
+string  EXTENDED_INDEX  = "12345";
+
+// Face numbers.
+integer FACE_1          = 3;
+integer FACE_2          = 7;
+integer FACE_3          = 4;
+integer FACE_4          = 6;
+integer FACE_5          = 1;
+
+// Used to hide the text after a fade-out.
+//key     TRANSPARENT     = "701917a8-d614-471f-13dd-5f4644e36e3c";
+string      TRANSPARENT     = "Transparent";
+///////////// END CONSTANTS ////////////////
+
+///////////// GLOBAL VARIABLES ///////////////
+// This is the key of the font we are displaying.
+//key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
+string      gFontTexture    = "FontsTexture";
+// All displayable characters.  Default to ASCII order.
+string gCharIndex;
+// This is the channel to listen on while acting
+// as a cell in a larger display.
+integer gCellChannel        = 2;
+// This is the starting character position in the cell channel message
+// to render.
+integer gCellCharPosition   = 95;
+// This is whether or not to use the fade in/out special effect.
+integer gCellUseFading      = FALSE;
+// This is how long to display the text before fading out (if using
+// fading special effect).
+// Note: < 0  means don't fade out.
+float   gCellHoldDelay      = 1.0;
+/////////// END GLOBAL VARIABLES ////////////
+
+ResetCharIndex() {
+   gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
+   // \" <-- Fixes LSL syntax highlighting bug.
+   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
+   gCharIndex += "\n\n\n\n\n";
+}
+
+vector GetGridOffset(integer index) {
+   // Calculate the offset needed to display this character.
+   integer Row = index / 10;
+   integer Col = index % 10;
+
+   // Return the offset in the texture.
+   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
+}
+
+ShowChars(vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
+   // Set the primitive textures directly.
+   
+   // <-0.256, 0, 0>
+   // <0, 0, 0>
+   // <0.130, 0, 0>
+   // <0, 0, 0>
+   // <-0.74, 0, 0>
+   
+   llSetPrimitiveParams( [
+        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
+        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
+        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
+        ]);
+}
+
+RenderString(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+RenderWithEffects(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // First set the alpha to the lowest possible.
+   llSetAlpha(0.05, ALL_SIDES);
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.
+    float Alpha;
+    for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // See if we want to fade out as well.
+   if (gCellHoldDelay < 0.0)
+       // No, bail out. (Just keep showing the string at full strength).
+       return;
+          // Hold the text for a while.
+   llSleep(gCellHoldDelay);
+      // Now fade out.
+   for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // Make the text transparent to fully hide it.
+   llSetTexture(TRANSPARENT, ALL_SIDES);
+}
+
+RenderExtended(string str) {
+   // Look for escape sequences.
+   list Parsed       = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
+   integer ParsedLen = llGetListLength(Parsed);
+
+   // Create a list of index values to work with.
+   list Indices;
+   // We start with room for 5 indices.
+   integer IndicesLeft = 5;
+
+   integer i;
+   string Token;
+   integer Clipped;
+   integer LastWasEscapeSequence = FALSE;
+   // Work from left to right.
+   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {
+       Token = llList2String(Parsed, i);
+
+       // If this is an escape sequence, just set the flag and move on.
+       if (Token == ESCAPE_SEQUENCE) {
+           LastWasEscapeSequence = TRUE;
+       }
+       else { // Token != ESCAPE_SEQUENCE
+           // Otherwise this is a normal token.  Check its length.
+           Clipped = FALSE;
+           integer TokenLength = llStringLength(Token);
+           // Clip if necessary.
+           if (TokenLength > IndicesLeft) {
+               Token = llGetSubString(Token, 0, IndicesLeft - 1);
+               TokenLength = llStringLength(Token);
+               IndicesLeft = 0;
+               Clipped = TRUE;
+           }
+           else
+               IndicesLeft -= TokenLength;
+
+           // Was the previous token an escape sequence?
+           if (LastWasEscapeSequence) {
+               // Yes, the first character is an escape character, the rest are normal.
+
+               // This is the extended character.
+               Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
+
+               // These are the normal characters.
+               integer j;
+               for (j = 1; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+           else { // Normal string.
+               // Just add the characters normally.
+               integer j;
+               for (j = 0; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+
+           // Unset this flag, since this was not an escape sequence.
+           LastWasEscapeSequence = FALSE;
+       }
+   }
+
+   // Use the indices to create grid positions.
+   vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0));
+   vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
+   vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
+   vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
+   vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+integer ConvertIndex(integer index) {
+   // This converts from an ASCII based index to our indexing scheme.
+   if (index >= 32) // ' ' or higher
+       index -= 32;
+   else { // index < 32
+       // Quick bounds check.
+       if (index > 15)
+           index = 15;
+
+       index += 94; // extended characters
+   }
+
+   return index;
+}
+
+default {
+   state_entry() {
+       // Initialize the character index.
+       llSetTexture(TRANSPARENT, ALL_SIDES);
+       ResetCharIndex();
+   }
+ 
+   link_message(integer sender, integer channel, string data, key id) {
+       if (channel == DISPLAY_STRING) {
+           RenderString(data);
+           return;
+       }
+       if (channel == DISPLAY_EXTENDED) {
+           RenderExtended(data);
+           return;
+       }
+       if (channel == gCellChannel) {
+           // Extract the characters we are interested in, and use those to render.
+           string TextToRender = llGetSubString(data, gCellCharPosition, gCellCharPosition + 4);
+                      // See if we need to show special effects.
+           if (gCellUseFading)
+               RenderWithEffects( TextToRender );
+           else // !gCellUseFading
+               RenderString( TextToRender );
+           return;
+       }
+       if (channel == REMAP_INDICES) {
+           // Parse the message, splitting it up into index values.
+           list Parsed = llCSV2List(data);
+           integer i;
+           // Go through the list and swap each pair of indices.
+           for (i = 0; i < llGetListLength(Parsed); i += 2) {
+               integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
+               integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
+
+               // Swap these index values.
+               string Value1 = llGetSubString(gCharIndex, Index1, Index1);
+               string Value2 = llGetSubString(gCharIndex, Index2, Index2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
+               gCharIndex = llInsertString(gCharIndex, Index1, Value2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
+               gCharIndex = llInsertString(gCharIndex, Index2, Value1);
+           }
+           return;
+       }
+       if (channel == RESET_INDICES) {
+           // Restore the character index back to default settings.
+           ResetCharIndex();
+           return;
+       }
+       if (channel == SET_CELL_INFO) {
+           // Change the channel we listen to for cell commands, the
+           // starting character position to extract from, and
+           // special effect attributes.
+           list Parsed = llCSV2List(data);
+           gCellChannel        = (integer) llList2String(Parsed, 0);
+           gCellCharPosition   = (integer) llList2String(Parsed, 1);
+           gCellUseFading      = (integer) llList2String(Parsed, 2);
+           gCellHoldDelay      = (float)   llList2String(Parsed, 3);
+           return;
+       }
+       if (channel == SET_FONT_TEXTURE) {
+           // Use the new texture instead of the current one.
+           gFontTexture = id;
+           // Change the currently shown texture.
+           llSetTexture(gFontTexture, FACE_1);
+           llSetTexture(gFontTexture, FACE_2);
+           llSetTexture(gFontTexture, FACE_3);
+           llSetTexture(gFontTexture, FACE_4);
+           llSetTexture(gFontTexture, FACE_5);
+           return;
+       }
+       if (channel == SET_THICKNESS) {
+           // Set our z scale to thickness, while staying fixed
+           // in position relative the prim below us.
+           vector Scale    = llGetScale();
+           float Thickness = (float) data;
+                      // Reposition only if this isn't the root prim.
+           integer ThisLink = llGetLinkNumber();
+           if (ThisLink != 0 || ThisLink != 1) {
+               // This is not the root prim.
+               vector Up = llRot2Up(llGetLocalRot());
+               float DistanceToMove = Thickness / 2.0 - Scale.z / 2.0;
+               vector Pos = llGetLocalPos();
+               llSetPos(Pos + DistanceToMove * Up);
+           }
+                      // Apply the new thickness.
+           Scale.z = Thickness;
+           llSetScale(Scale);
+           return;
+       }
+       if (channel == SET_COLOR) {
+           vector newColor = (vector)data;
+           llSetColor(newColor, ALL_SIDES);
+       }
+   }
+} 
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: lsl/xytext/xytext.lsl.20

+ 320 - 0
assets/xytext/xytext.lslp.21

@@ -0,0 +1,320 @@
+////////////////////////////////////////////
+// XyText v1.2 Script (5 Face, Single Texture)
+//
+// Written by Xylor Baysklef
+//
+// Modified by Kermitt Quirk 19/01/2006
+// To add support for 5 face prim instead of 3
+//
+////////////////////////////////////////////
+
+/////////////// CONSTANTS ///////////////////
+// XyText Message Map.
+integer DISPLAY_STRING      = 204000;
+integer DISPLAY_EXTENDED    = 204001;
+integer REMAP_INDICES       = 204002;
+integer RESET_INDICES       = 204003;
+integer SET_CELL_INFO       = 204004;
+integer SET_FONT_TEXTURE    = 204005;
+integer SET_THICKNESS       = 204006;
+integer SET_COLOR           = 204007;
+
+// This is an extended character escape sequence.
+string  ESCAPE_SEQUENCE = "\\e";
+
+// This is used to get an index for the extended character.
+string  EXTENDED_INDEX  = "12345";
+
+// Face numbers.
+integer FACE_1          = 3;
+integer FACE_2          = 7;
+integer FACE_3          = 4;
+integer FACE_4          = 6;
+integer FACE_5          = 1;
+
+// Used to hide the text after a fade-out.
+//key     TRANSPARENT     = "701917a8-d614-471f-13dd-5f4644e36e3c";
+string      TRANSPARENT     = "Transparent";
+///////////// END CONSTANTS ////////////////
+
+///////////// GLOBAL VARIABLES ///////////////
+// This is the key of the font we are displaying.
+//key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
+string      gFontTexture    = "FontsTexture";
+// All displayable characters.  Default to ASCII order.
+string gCharIndex;
+// This is the channel to listen on while acting
+// as a cell in a larger display.
+integer gCellChannel        = 1;
+// This is the starting character position in the cell channel message
+// to render.
+integer gCellCharPosition   = 5;
+// This is whether or not to use the fade in/out special effect.
+integer gCellUseFading      = FALSE;
+// This is how long to display the text before fading out (if using
+// fading special effect).
+// Note: < 0  means don't fade out.
+float   gCellHoldDelay      = 1.0;
+/////////// END GLOBAL VARIABLES ////////////
+
+ResetCharIndex() {
+   gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
+   // \" <-- Fixes LSL syntax highlighting bug.
+   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
+   gCharIndex += "\n\n\n\n\n";
+}
+
+vector GetGridOffset(integer index) {
+   // Calculate the offset needed to display this character.
+   integer Row = index / 10;
+   integer Col = index % 10;
+
+   // Return the offset in the texture.
+   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
+}
+
+ShowChars(vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
+   // Set the primitive textures directly.
+   
+   // <-0.256, 0, 0>
+   // <0, 0, 0>
+   // <0.130, 0, 0>
+   // <0, 0, 0>
+   // <-0.74, 0, 0>
+   
+   llSetPrimitiveParams( [
+        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
+        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
+        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
+        ]);
+}
+
+RenderString(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+RenderWithEffects(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // First set the alpha to the lowest possible.
+   llSetAlpha(0.05, ALL_SIDES);
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.
+    float Alpha;
+    for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // See if we want to fade out as well.
+   if (gCellHoldDelay < 0.0)
+       // No, bail out. (Just keep showing the string at full strength).
+       return;
+          // Hold the text for a while.
+   llSleep(gCellHoldDelay);
+      // Now fade out.
+   for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // Make the text transparent to fully hide it.
+   llSetTexture(TRANSPARENT, ALL_SIDES);
+}
+
+RenderExtended(string str) {
+   // Look for escape sequences.
+   list Parsed       = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
+   integer ParsedLen = llGetListLength(Parsed);
+
+   // Create a list of index values to work with.
+   list Indices;
+   // We start with room for 5 indices.
+   integer IndicesLeft = 5;
+
+   integer i;
+   string Token;
+   integer Clipped;
+   integer LastWasEscapeSequence = FALSE;
+   // Work from left to right.
+   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {
+       Token = llList2String(Parsed, i);
+
+       // If this is an escape sequence, just set the flag and move on.
+       if (Token == ESCAPE_SEQUENCE) {
+           LastWasEscapeSequence = TRUE;
+       }
+       else { // Token != ESCAPE_SEQUENCE
+           // Otherwise this is a normal token.  Check its length.
+           Clipped = FALSE;
+           integer TokenLength = llStringLength(Token);
+           // Clip if necessary.
+           if (TokenLength > IndicesLeft) {
+               Token = llGetSubString(Token, 0, IndicesLeft - 1);
+               TokenLength = llStringLength(Token);
+               IndicesLeft = 0;
+               Clipped = TRUE;
+           }
+           else
+               IndicesLeft -= TokenLength;
+
+           // Was the previous token an escape sequence?
+           if (LastWasEscapeSequence) {
+               // Yes, the first character is an escape character, the rest are normal.
+
+               // This is the extended character.
+               Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
+
+               // These are the normal characters.
+               integer j;
+               for (j = 1; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+           else { // Normal string.
+               // Just add the characters normally.
+               integer j;
+               for (j = 0; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+
+           // Unset this flag, since this was not an escape sequence.
+           LastWasEscapeSequence = FALSE;
+       }
+   }
+
+   // Use the indices to create grid positions.
+   vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0));
+   vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
+   vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
+   vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
+   vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+integer ConvertIndex(integer index) {
+   // This converts from an ASCII based index to our indexing scheme.
+   if (index >= 32) // ' ' or higher
+       index -= 32;
+   else { // index < 32
+       // Quick bounds check.
+       if (index > 15)
+           index = 15;
+
+       index += 94; // extended characters
+   }
+
+   return index;
+}
+
+default {
+   state_entry() {
+       // Initialize the character index.
+       llSetTexture(TRANSPARENT, ALL_SIDES);
+       ResetCharIndex();
+   }
+ 
+   link_message(integer sender, integer channel, string data, key id) {
+       if (channel == DISPLAY_STRING) {
+           RenderString(data);
+           return;
+       }
+       if (channel == DISPLAY_EXTENDED) {
+           RenderExtended(data);
+           return;
+       }
+       if (channel == gCellChannel) {
+           // Extract the characters we are interested in, and use those to render.
+           string TextToRender = llGetSubString(data, gCellCharPosition, gCellCharPosition + 4);
+                      // See if we need to show special effects.
+           if (gCellUseFading)
+               RenderWithEffects( TextToRender );
+           else // !gCellUseFading
+               RenderString( TextToRender );
+           return;
+       }
+       if (channel == REMAP_INDICES) {
+           // Parse the message, splitting it up into index values.
+           list Parsed = llCSV2List(data);
+           integer i;
+           // Go through the list and swap each pair of indices.
+           for (i = 0; i < llGetListLength(Parsed); i += 2) {
+               integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
+               integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
+
+               // Swap these index values.
+               string Value1 = llGetSubString(gCharIndex, Index1, Index1);
+               string Value2 = llGetSubString(gCharIndex, Index2, Index2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
+               gCharIndex = llInsertString(gCharIndex, Index1, Value2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
+               gCharIndex = llInsertString(gCharIndex, Index2, Value1);
+           }
+           return;
+       }
+       if (channel == RESET_INDICES) {
+           // Restore the character index back to default settings.
+           ResetCharIndex();
+           return;
+       }
+       if (channel == SET_CELL_INFO) {
+           // Change the channel we listen to for cell commands, the
+           // starting character position to extract from, and
+           // special effect attributes.
+           list Parsed = llCSV2List(data);
+           gCellChannel        = (integer) llList2String(Parsed, 0);
+           gCellCharPosition   = (integer) llList2String(Parsed, 1);
+           gCellUseFading      = (integer) llList2String(Parsed, 2);
+           gCellHoldDelay      = (float)   llList2String(Parsed, 3);
+           return;
+       }
+       if (channel == SET_FONT_TEXTURE) {
+           // Use the new texture instead of the current one.
+           gFontTexture = id;
+           // Change the currently shown texture.
+           llSetTexture(gFontTexture, FACE_1);
+           llSetTexture(gFontTexture, FACE_2);
+           llSetTexture(gFontTexture, FACE_3);
+           llSetTexture(gFontTexture, FACE_4);
+           llSetTexture(gFontTexture, FACE_5);
+           return;
+       }
+       if (channel == SET_THICKNESS) {
+           // Set our z scale to thickness, while staying fixed
+           // in position relative the prim below us.
+           vector Scale    = llGetScale();
+           float Thickness = (float) data;
+                      // Reposition only if this isn't the root prim.
+           integer ThisLink = llGetLinkNumber();
+           if (ThisLink != 0 || ThisLink != 1) {
+               // This is not the root prim.
+               vector Up = llRot2Up(llGetLocalRot());
+               float DistanceToMove = Thickness / 2.0 - Scale.z / 2.0;
+               vector Pos = llGetLocalPos();
+               llSetPos(Pos + DistanceToMove * Up);
+           }
+                      // Apply the new thickness.
+           Scale.z = Thickness;
+           llSetScale(Scale);
+           return;
+       }
+       if (channel == SET_COLOR) {
+           vector newColor = (vector)data;
+           llSetColor(newColor, ALL_SIDES);
+       }
+   }
+} // Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: lsl/xytext/xytext.lsl.21

+ 321 - 0
assets/xytext/xytext.lslp.22

@@ -0,0 +1,321 @@
+////////////////////////////////////////////
+// XyText v1.2 Script (5 Face, Single Texture)
+//
+// Written by Xylor Baysklef
+//
+// Modified by Kermitt Quirk 19/01/2006
+// To add support for 5 face prim instead of 3
+//
+////////////////////////////////////////////
+
+/////////////// CONSTANTS ///////////////////
+// XyText Message Map.
+integer DISPLAY_STRING      = 204000;
+integer DISPLAY_EXTENDED    = 204001;
+integer REMAP_INDICES       = 204002;
+integer RESET_INDICES       = 204003;
+integer SET_CELL_INFO       = 204004;
+integer SET_FONT_TEXTURE    = 204005;
+integer SET_THICKNESS       = 204006;
+integer SET_COLOR           = 204007;
+
+// This is an extended character escape sequence.
+string  ESCAPE_SEQUENCE = "\\e";
+
+// This is used to get an index for the extended character.
+string  EXTENDED_INDEX  = "12345";
+
+// Face numbers.
+integer FACE_1          = 3;
+integer FACE_2          = 7;
+integer FACE_3          = 4;
+integer FACE_4          = 6;
+integer FACE_5          = 1;
+
+// Used to hide the text after a fade-out.
+//key     TRANSPARENT     = "701917a8-d614-471f-13dd-5f4644e36e3c";
+string      TRANSPARENT     = "Transparent";
+///////////// END CONSTANTS ////////////////
+
+///////////// GLOBAL VARIABLES ///////////////
+// This is the key of the font we are displaying.
+//key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
+string      gFontTexture    = "FontsTexture";
+// All displayable characters.  Default to ASCII order.
+string gCharIndex;
+// This is the channel to listen on while acting
+// as a cell in a larger display.
+integer gCellChannel        = 2;
+// This is the starting character position in the cell channel message
+// to render.
+integer gCellCharPosition   = 20;
+// This is whether or not to use the fade in/out special effect.
+integer gCellUseFading      = FALSE;
+// This is how long to display the text before fading out (if using
+// fading special effect).
+// Note: < 0  means don't fade out.
+float   gCellHoldDelay      = 1.0;
+/////////// END GLOBAL VARIABLES ////////////
+
+ResetCharIndex() {
+   gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
+   // \" <-- Fixes LSL syntax highlighting bug.
+   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
+   gCharIndex += "\n\n\n\n\n";
+}
+
+vector GetGridOffset(integer index) {
+   // Calculate the offset needed to display this character.
+   integer Row = index / 10;
+   integer Col = index % 10;
+
+   // Return the offset in the texture.
+   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
+}
+
+ShowChars(vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
+   // Set the primitive textures directly.
+   
+   // <-0.256, 0, 0>
+   // <0, 0, 0>
+   // <0.130, 0, 0>
+   // <0, 0, 0>
+   // <-0.74, 0, 0>
+   
+   llSetPrimitiveParams( [
+        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
+        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
+        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
+        ]);
+}
+
+RenderString(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+RenderWithEffects(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // First set the alpha to the lowest possible.
+   llSetAlpha(0.05, ALL_SIDES);
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.
+    float Alpha;
+    for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // See if we want to fade out as well.
+   if (gCellHoldDelay < 0.0)
+       // No, bail out. (Just keep showing the string at full strength).
+       return;
+          // Hold the text for a while.
+   llSleep(gCellHoldDelay);
+      // Now fade out.
+   for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // Make the text transparent to fully hide it.
+   llSetTexture(TRANSPARENT, ALL_SIDES);
+}
+
+RenderExtended(string str) {
+   // Look for escape sequences.
+   list Parsed       = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
+   integer ParsedLen = llGetListLength(Parsed);
+
+   // Create a list of index values to work with.
+   list Indices;
+   // We start with room for 5 indices.
+   integer IndicesLeft = 5;
+
+   integer i;
+   string Token;
+   integer Clipped;
+   integer LastWasEscapeSequence = FALSE;
+   // Work from left to right.
+   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {
+       Token = llList2String(Parsed, i);
+
+       // If this is an escape sequence, just set the flag and move on.
+       if (Token == ESCAPE_SEQUENCE) {
+           LastWasEscapeSequence = TRUE;
+       }
+       else { // Token != ESCAPE_SEQUENCE
+           // Otherwise this is a normal token.  Check its length.
+           Clipped = FALSE;
+           integer TokenLength = llStringLength(Token);
+           // Clip if necessary.
+           if (TokenLength > IndicesLeft) {
+               Token = llGetSubString(Token, 0, IndicesLeft - 1);
+               TokenLength = llStringLength(Token);
+               IndicesLeft = 0;
+               Clipped = TRUE;
+           }
+           else
+               IndicesLeft -= TokenLength;
+
+           // Was the previous token an escape sequence?
+           if (LastWasEscapeSequence) {
+               // Yes, the first character is an escape character, the rest are normal.
+
+               // This is the extended character.
+               Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
+
+               // These are the normal characters.
+               integer j;
+               for (j = 1; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+           else { // Normal string.
+               // Just add the characters normally.
+               integer j;
+               for (j = 0; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+
+           // Unset this flag, since this was not an escape sequence.
+           LastWasEscapeSequence = FALSE;
+       }
+   }
+
+   // Use the indices to create grid positions.
+   vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0));
+   vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
+   vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
+   vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
+   vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+integer ConvertIndex(integer index) {
+   // This converts from an ASCII based index to our indexing scheme.
+   if (index >= 32) // ' ' or higher
+       index -= 32;
+   else { // index < 32
+       // Quick bounds check.
+       if (index > 15)
+           index = 15;
+
+       index += 94; // extended characters
+   }
+
+   return index;
+}
+
+default {
+   state_entry() {
+       // Initialize the character index.
+       llSetTexture(TRANSPARENT, ALL_SIDES);
+       ResetCharIndex();
+   }
+ 
+   link_message(integer sender, integer channel, string data, key id) {
+       if (channel == DISPLAY_STRING) {
+           RenderString(data);
+           return;
+       }
+       if (channel == DISPLAY_EXTENDED) {
+           RenderExtended(data);
+           return;
+       }
+       if (channel == gCellChannel) {
+           // Extract the characters we are interested in, and use those to render.
+           string TextToRender = llGetSubString(data, gCellCharPosition, gCellCharPosition + 4);
+                      // See if we need to show special effects.
+           if (gCellUseFading)
+               RenderWithEffects( TextToRender );
+           else // !gCellUseFading
+               RenderString( TextToRender );
+           return;
+       }
+       if (channel == REMAP_INDICES) {
+           // Parse the message, splitting it up into index values.
+           list Parsed = llCSV2List(data);
+           integer i;
+           // Go through the list and swap each pair of indices.
+           for (i = 0; i < llGetListLength(Parsed); i += 2) {
+               integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
+               integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
+
+               // Swap these index values.
+               string Value1 = llGetSubString(gCharIndex, Index1, Index1);
+               string Value2 = llGetSubString(gCharIndex, Index2, Index2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
+               gCharIndex = llInsertString(gCharIndex, Index1, Value2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
+               gCharIndex = llInsertString(gCharIndex, Index2, Value1);
+           }
+           return;
+       }
+       if (channel == RESET_INDICES) {
+           // Restore the character index back to default settings.
+           ResetCharIndex();
+           return;
+       }
+       if (channel == SET_CELL_INFO) {
+           // Change the channel we listen to for cell commands, the
+           // starting character position to extract from, and
+           // special effect attributes.
+           list Parsed = llCSV2List(data);
+           gCellChannel        = (integer) llList2String(Parsed, 0);
+           gCellCharPosition   = (integer) llList2String(Parsed, 1);
+           gCellUseFading      = (integer) llList2String(Parsed, 2);
+           gCellHoldDelay      = (float)   llList2String(Parsed, 3);
+           return;
+       }
+       if (channel == SET_FONT_TEXTURE) {
+           // Use the new texture instead of the current one.
+           gFontTexture = id;
+           // Change the currently shown texture.
+           llSetTexture(gFontTexture, FACE_1);
+           llSetTexture(gFontTexture, FACE_2);
+           llSetTexture(gFontTexture, FACE_3);
+           llSetTexture(gFontTexture, FACE_4);
+           llSetTexture(gFontTexture, FACE_5);
+           return;
+       }
+       if (channel == SET_THICKNESS) {
+           // Set our z scale to thickness, while staying fixed
+           // in position relative the prim below us.
+           vector Scale    = llGetScale();
+           float Thickness = (float) data;
+                      // Reposition only if this isn't the root prim.
+           integer ThisLink = llGetLinkNumber();
+           if (ThisLink != 0 || ThisLink != 1) {
+               // This is not the root prim.
+               vector Up = llRot2Up(llGetLocalRot());
+               float DistanceToMove = Thickness / 2.0 - Scale.z / 2.0;
+               vector Pos = llGetLocalPos();
+               llSetPos(Pos + DistanceToMove * Up);
+           }
+                      // Apply the new thickness.
+           Scale.z = Thickness;
+           llSetScale(Scale);
+           return;
+       }
+       if (channel == SET_COLOR) {
+           vector newColor = (vector)data;
+           llSetColor(newColor, ALL_SIDES);
+       }
+   }
+} 
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: lsl/xytext/xytext.lsl.22

+ 320 - 0
assets/xytext/xytext.lslp.23

@@ -0,0 +1,320 @@
+////////////////////////////////////////////
+// XyText v1.2 Script (5 Face, Single Texture)
+//
+// Written by Xylor Baysklef
+//
+// Modified by Kermitt Quirk 19/01/2006
+// To add support for 5 face prim instead of 3
+//
+////////////////////////////////////////////
+
+/////////////// CONSTANTS ///////////////////
+// XyText Message Map.
+integer DISPLAY_STRING      = 204000;
+integer DISPLAY_EXTENDED    = 204001;
+integer REMAP_INDICES       = 204002;
+integer RESET_INDICES       = 204003;
+integer SET_CELL_INFO       = 204004;
+integer SET_FONT_TEXTURE    = 204005;
+integer SET_THICKNESS       = 204006;
+integer SET_COLOR           = 204007;
+
+// This is an extended character escape sequence.
+string  ESCAPE_SEQUENCE = "\\e";
+
+// This is used to get an index for the extended character.
+string  EXTENDED_INDEX  = "12345";
+
+// Face numbers.
+integer FACE_1          = 3;
+integer FACE_2          = 7;
+integer FACE_3          = 4;
+integer FACE_4          = 6;
+integer FACE_5          = 1;
+
+// Used to hide the text after a fade-out.
+//key     TRANSPARENT     = "701917a8-d614-471f-13dd-5f4644e36e3c";
+string      TRANSPARENT     = "Transparent";
+///////////// END CONSTANTS ////////////////
+
+///////////// GLOBAL VARIABLES ///////////////
+// This is the key of the font we are displaying.
+//key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
+string      gFontTexture    = "FontsTexture";
+// All displayable characters.  Default to ASCII order.
+string gCharIndex;
+// This is the channel to listen on while acting
+// as a cell in a larger display.
+integer gCellChannel        = 2;
+// This is the starting character position in the cell channel message
+// to render.
+integer gCellCharPosition   = 30;
+// This is whether or not to use the fade in/out special effect.
+integer gCellUseFading      = FALSE;
+// This is how long to display the text before fading out (if using
+// fading special effect).
+// Note: < 0  means don't fade out.
+float   gCellHoldDelay      = 1.0;
+/////////// END GLOBAL VARIABLES ////////////
+
+ResetCharIndex() {
+   gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
+   // \" <-- Fixes LSL syntax highlighting bug.
+   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
+   gCharIndex += "\n\n\n\n\n";
+}
+
+vector GetGridOffset(integer index) {
+   // Calculate the offset needed to display this character.
+   integer Row = index / 10;
+   integer Col = index % 10;
+
+   // Return the offset in the texture.
+   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
+}
+
+ShowChars(vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
+   // Set the primitive textures directly.
+   
+   // <-0.256, 0, 0>
+   // <0, 0, 0>
+   // <0.130, 0, 0>
+   // <0, 0, 0>
+   // <-0.74, 0, 0>
+   
+   llSetPrimitiveParams( [
+        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
+        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
+        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
+        ]);
+}
+
+RenderString(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+RenderWithEffects(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // First set the alpha to the lowest possible.
+   llSetAlpha(0.05, ALL_SIDES);
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.
+    float Alpha;
+    for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // See if we want to fade out as well.
+   if (gCellHoldDelay < 0.0)
+       // No, bail out. (Just keep showing the string at full strength).
+       return;
+          // Hold the text for a while.
+   llSleep(gCellHoldDelay);
+      // Now fade out.
+   for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // Make the text transparent to fully hide it.
+   llSetTexture(TRANSPARENT, ALL_SIDES);
+}
+
+RenderExtended(string str) {
+   // Look for escape sequences.
+   list Parsed       = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
+   integer ParsedLen = llGetListLength(Parsed);
+
+   // Create a list of index values to work with.
+   list Indices;
+   // We start with room for 5 indices.
+   integer IndicesLeft = 5;
+
+   integer i;
+   string Token;
+   integer Clipped;
+   integer LastWasEscapeSequence = FALSE;
+   // Work from left to right.
+   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {
+       Token = llList2String(Parsed, i);
+
+       // If this is an escape sequence, just set the flag and move on.
+       if (Token == ESCAPE_SEQUENCE) {
+           LastWasEscapeSequence = TRUE;
+       }
+       else { // Token != ESCAPE_SEQUENCE
+           // Otherwise this is a normal token.  Check its length.
+           Clipped = FALSE;
+           integer TokenLength = llStringLength(Token);
+           // Clip if necessary.
+           if (TokenLength > IndicesLeft) {
+               Token = llGetSubString(Token, 0, IndicesLeft - 1);
+               TokenLength = llStringLength(Token);
+               IndicesLeft = 0;
+               Clipped = TRUE;
+           }
+           else
+               IndicesLeft -= TokenLength;
+
+           // Was the previous token an escape sequence?
+           if (LastWasEscapeSequence) {
+               // Yes, the first character is an escape character, the rest are normal.
+
+               // This is the extended character.
+               Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
+
+               // These are the normal characters.
+               integer j;
+               for (j = 1; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+           else { // Normal string.
+               // Just add the characters normally.
+               integer j;
+               for (j = 0; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+
+           // Unset this flag, since this was not an escape sequence.
+           LastWasEscapeSequence = FALSE;
+       }
+   }
+
+   // Use the indices to create grid positions.
+   vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0));
+   vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
+   vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
+   vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
+   vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+integer ConvertIndex(integer index) {
+   // This converts from an ASCII based index to our indexing scheme.
+   if (index >= 32) // ' ' or higher
+       index -= 32;
+   else { // index < 32
+       // Quick bounds check.
+       if (index > 15)
+           index = 15;
+
+       index += 94; // extended characters
+   }
+
+   return index;
+}
+
+default {
+   state_entry() {
+       // Initialize the character index.
+       llSetTexture(TRANSPARENT, ALL_SIDES);
+       ResetCharIndex();
+   }
+ 
+   link_message(integer sender, integer channel, string data, key id) {
+       if (channel == DISPLAY_STRING) {
+           RenderString(data);
+           return;
+       }
+       if (channel == DISPLAY_EXTENDED) {
+           RenderExtended(data);
+           return;
+       }
+       if (channel == gCellChannel) {
+           // Extract the characters we are interested in, and use those to render.
+           string TextToRender = llGetSubString(data, gCellCharPosition, gCellCharPosition + 4);
+                      // See if we need to show special effects.
+           if (gCellUseFading)
+               RenderWithEffects( TextToRender );
+           else // !gCellUseFading
+               RenderString( TextToRender );
+           return;
+       }
+       if (channel == REMAP_INDICES) {
+           // Parse the message, splitting it up into index values.
+           list Parsed = llCSV2List(data);
+           integer i;
+           // Go through the list and swap each pair of indices.
+           for (i = 0; i < llGetListLength(Parsed); i += 2) {
+               integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
+               integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
+
+               // Swap these index values.
+               string Value1 = llGetSubString(gCharIndex, Index1, Index1);
+               string Value2 = llGetSubString(gCharIndex, Index2, Index2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
+               gCharIndex = llInsertString(gCharIndex, Index1, Value2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
+               gCharIndex = llInsertString(gCharIndex, Index2, Value1);
+           }
+           return;
+       }
+       if (channel == RESET_INDICES) {
+           // Restore the character index back to default settings.
+           ResetCharIndex();
+           return;
+       }
+       if (channel == SET_CELL_INFO) {
+           // Change the channel we listen to for cell commands, the
+           // starting character position to extract from, and
+           // special effect attributes.
+           list Parsed = llCSV2List(data);
+           gCellChannel        = (integer) llList2String(Parsed, 0);
+           gCellCharPosition   = (integer) llList2String(Parsed, 1);
+           gCellUseFading      = (integer) llList2String(Parsed, 2);
+           gCellHoldDelay      = (float)   llList2String(Parsed, 3);
+           return;
+       }
+       if (channel == SET_FONT_TEXTURE) {
+           // Use the new texture instead of the current one.
+           gFontTexture = id;
+           // Change the currently shown texture.
+           llSetTexture(gFontTexture, FACE_1);
+           llSetTexture(gFontTexture, FACE_2);
+           llSetTexture(gFontTexture, FACE_3);
+           llSetTexture(gFontTexture, FACE_4);
+           llSetTexture(gFontTexture, FACE_5);
+           return;
+       }
+       if (channel == SET_THICKNESS) {
+           // Set our z scale to thickness, while staying fixed
+           // in position relative the prim below us.
+           vector Scale    = llGetScale();
+           float Thickness = (float) data;
+                      // Reposition only if this isn't the root prim.
+           integer ThisLink = llGetLinkNumber();
+           if (ThisLink != 0 || ThisLink != 1) {
+               // This is not the root prim.
+               vector Up = llRot2Up(llGetLocalRot());
+               float DistanceToMove = Thickness / 2.0 - Scale.z / 2.0;
+               vector Pos = llGetLocalPos();
+               llSetPos(Pos + DistanceToMove * Up);
+           }
+                      // Apply the new thickness.
+           Scale.z = Thickness;
+           llSetScale(Scale);
+           return;
+       }
+       if (channel == SET_COLOR) {
+           vector newColor = (vector)data;
+           llSetColor(newColor, ALL_SIDES);
+       }
+   }
+} // Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: lsl/xytext/xytext.lsl.23

+ 320 - 0
assets/xytext/xytext.lslp.24

@@ -0,0 +1,320 @@
+////////////////////////////////////////////
+// XyText v1.2 Script (5 Face, Single Texture)
+//
+// Written by Xylor Baysklef
+//
+// Modified by Kermitt Quirk 19/01/2006
+// To add support for 5 face prim instead of 3
+//
+////////////////////////////////////////////
+
+/////////////// CONSTANTS ///////////////////
+// XyText Message Map.
+integer DISPLAY_STRING      = 204000;
+integer DISPLAY_EXTENDED    = 204001;
+integer REMAP_INDICES       = 204002;
+integer RESET_INDICES       = 204003;
+integer SET_CELL_INFO       = 204004;
+integer SET_FONT_TEXTURE    = 204005;
+integer SET_THICKNESS       = 204006;
+integer SET_COLOR           = 204007;
+
+// This is an extended character escape sequence.
+string  ESCAPE_SEQUENCE = "\\e";
+
+// This is used to get an index for the extended character.
+string  EXTENDED_INDEX  = "12345";
+
+// Face numbers.
+integer FACE_1          = 3;
+integer FACE_2          = 7;
+integer FACE_3          = 4;
+integer FACE_4          = 6;
+integer FACE_5          = 1;
+
+// Used to hide the text after a fade-out.
+//key     TRANSPARENT     = "701917a8-d614-471f-13dd-5f4644e36e3c";
+string      TRANSPARENT     = "Transparent";
+///////////// END CONSTANTS ////////////////
+
+///////////// GLOBAL VARIABLES ///////////////
+// This is the key of the font we are displaying.
+//key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
+string      gFontTexture    = "FontsTexture";
+// All displayable characters.  Default to ASCII order.
+string gCharIndex;
+// This is the channel to listen on while acting
+// as a cell in a larger display.
+integer gCellChannel        = 2;
+// This is the starting character position in the cell channel message
+// to render.
+integer gCellCharPosition   = 85;
+// This is whether or not to use the fade in/out special effect.
+integer gCellUseFading      = FALSE;
+// This is how long to display the text before fading out (if using
+// fading special effect).
+// Note: < 0  means don't fade out.
+float   gCellHoldDelay      = 1.0;
+/////////// END GLOBAL VARIABLES ////////////
+
+ResetCharIndex() {
+   gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
+   // \" <-- Fixes LSL syntax highlighting bug.
+   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
+   gCharIndex += "\n\n\n\n\n";
+}
+
+vector GetGridOffset(integer index) {
+   // Calculate the offset needed to display this character.
+   integer Row = index / 10;
+   integer Col = index % 10;
+
+   // Return the offset in the texture.
+   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
+}
+
+ShowChars(vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
+   // Set the primitive textures directly.
+   
+   // <-0.256, 0, 0>
+   // <0, 0, 0>
+   // <0.130, 0, 0>
+   // <0, 0, 0>
+   // <-0.74, 0, 0>
+   
+   llSetPrimitiveParams( [
+        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
+        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
+        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
+        ]);
+}
+
+RenderString(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+RenderWithEffects(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // First set the alpha to the lowest possible.
+   llSetAlpha(0.05, ALL_SIDES);
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.
+    float Alpha;
+    for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // See if we want to fade out as well.
+   if (gCellHoldDelay < 0.0)
+       // No, bail out. (Just keep showing the string at full strength).
+       return;
+          // Hold the text for a while.
+   llSleep(gCellHoldDelay);
+      // Now fade out.
+   for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // Make the text transparent to fully hide it.
+   llSetTexture(TRANSPARENT, ALL_SIDES);
+}
+
+RenderExtended(string str) {
+   // Look for escape sequences.
+   list Parsed       = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
+   integer ParsedLen = llGetListLength(Parsed);
+
+   // Create a list of index values to work with.
+   list Indices;
+   // We start with room for 5 indices.
+   integer IndicesLeft = 5;
+
+   integer i;
+   string Token;
+   integer Clipped;
+   integer LastWasEscapeSequence = FALSE;
+   // Work from left to right.
+   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {
+       Token = llList2String(Parsed, i);
+
+       // If this is an escape sequence, just set the flag and move on.
+       if (Token == ESCAPE_SEQUENCE) {
+           LastWasEscapeSequence = TRUE;
+       }
+       else { // Token != ESCAPE_SEQUENCE
+           // Otherwise this is a normal token.  Check its length.
+           Clipped = FALSE;
+           integer TokenLength = llStringLength(Token);
+           // Clip if necessary.
+           if (TokenLength > IndicesLeft) {
+               Token = llGetSubString(Token, 0, IndicesLeft - 1);
+               TokenLength = llStringLength(Token);
+               IndicesLeft = 0;
+               Clipped = TRUE;
+           }
+           else
+               IndicesLeft -= TokenLength;
+
+           // Was the previous token an escape sequence?
+           if (LastWasEscapeSequence) {
+               // Yes, the first character is an escape character, the rest are normal.
+
+               // This is the extended character.
+               Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
+
+               // These are the normal characters.
+               integer j;
+               for (j = 1; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+           else { // Normal string.
+               // Just add the characters normally.
+               integer j;
+               for (j = 0; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+
+           // Unset this flag, since this was not an escape sequence.
+           LastWasEscapeSequence = FALSE;
+       }
+   }
+
+   // Use the indices to create grid positions.
+   vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0));
+   vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
+   vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
+   vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
+   vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+integer ConvertIndex(integer index) {
+   // This converts from an ASCII based index to our indexing scheme.
+   if (index >= 32) // ' ' or higher
+       index -= 32;
+   else { // index < 32
+       // Quick bounds check.
+       if (index > 15)
+           index = 15;
+
+       index += 94; // extended characters
+   }
+
+   return index;
+}
+
+default {
+   state_entry() {
+       // Initialize the character index.
+       llSetTexture(TRANSPARENT, ALL_SIDES);
+       ResetCharIndex();
+   }
+ 
+   link_message(integer sender, integer channel, string data, key id) {
+       if (channel == DISPLAY_STRING) {
+           RenderString(data);
+           return;
+       }
+       if (channel == DISPLAY_EXTENDED) {
+           RenderExtended(data);
+           return;
+       }
+       if (channel == gCellChannel) {
+           // Extract the characters we are interested in, and use those to render.
+           string TextToRender = llGetSubString(data, gCellCharPosition, gCellCharPosition + 4);
+                      // See if we need to show special effects.
+           if (gCellUseFading)
+               RenderWithEffects( TextToRender );
+           else // !gCellUseFading
+               RenderString( TextToRender );
+           return;
+       }
+       if (channel == REMAP_INDICES) {
+           // Parse the message, splitting it up into index values.
+           list Parsed = llCSV2List(data);
+           integer i;
+           // Go through the list and swap each pair of indices.
+           for (i = 0; i < llGetListLength(Parsed); i += 2) {
+               integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
+               integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
+
+               // Swap these index values.
+               string Value1 = llGetSubString(gCharIndex, Index1, Index1);
+               string Value2 = llGetSubString(gCharIndex, Index2, Index2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
+               gCharIndex = llInsertString(gCharIndex, Index1, Value2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
+               gCharIndex = llInsertString(gCharIndex, Index2, Value1);
+           }
+           return;
+       }
+       if (channel == RESET_INDICES) {
+           // Restore the character index back to default settings.
+           ResetCharIndex();
+           return;
+       }
+       if (channel == SET_CELL_INFO) {
+           // Change the channel we listen to for cell commands, the
+           // starting character position to extract from, and
+           // special effect attributes.
+           list Parsed = llCSV2List(data);
+           gCellChannel        = (integer) llList2String(Parsed, 0);
+           gCellCharPosition   = (integer) llList2String(Parsed, 1);
+           gCellUseFading      = (integer) llList2String(Parsed, 2);
+           gCellHoldDelay      = (float)   llList2String(Parsed, 3);
+           return;
+       }
+       if (channel == SET_FONT_TEXTURE) {
+           // Use the new texture instead of the current one.
+           gFontTexture = id;
+           // Change the currently shown texture.
+           llSetTexture(gFontTexture, FACE_1);
+           llSetTexture(gFontTexture, FACE_2);
+           llSetTexture(gFontTexture, FACE_3);
+           llSetTexture(gFontTexture, FACE_4);
+           llSetTexture(gFontTexture, FACE_5);
+           return;
+       }
+       if (channel == SET_THICKNESS) {
+           // Set our z scale to thickness, while staying fixed
+           // in position relative the prim below us.
+           vector Scale    = llGetScale();
+           float Thickness = (float) data;
+                      // Reposition only if this isn't the root prim.
+           integer ThisLink = llGetLinkNumber();
+           if (ThisLink != 0 || ThisLink != 1) {
+               // This is not the root prim.
+               vector Up = llRot2Up(llGetLocalRot());
+               float DistanceToMove = Thickness / 2.0 - Scale.z / 2.0;
+               vector Pos = llGetLocalPos();
+               llSetPos(Pos + DistanceToMove * Up);
+           }
+                      // Apply the new thickness.
+           Scale.z = Thickness;
+           llSetScale(Scale);
+           return;
+       }
+       if (channel == SET_COLOR) {
+           vector newColor = (vector)data;
+           llSetColor(newColor, ALL_SIDES);
+       }
+   }
+} // Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: lsl/xytext/xytext.lsl.24

+ 320 - 0
assets/xytext/xytext.lslp.3

@@ -0,0 +1,320 @@
+////////////////////////////////////////////
+// XyText v1.2 Script (5 Face, Single Texture)
+//
+// Written by Xylor Baysklef
+//
+// Modified by Kermitt Quirk 19/01/2006
+// To add support for 5 face prim instead of 3
+//
+////////////////////////////////////////////
+
+/////////////// CONSTANTS ///////////////////
+// XyText Message Map.
+integer DISPLAY_STRING      = 204000;
+integer DISPLAY_EXTENDED    = 204001;
+integer REMAP_INDICES       = 204002;
+integer RESET_INDICES       = 204003;
+integer SET_CELL_INFO       = 204004;
+integer SET_FONT_TEXTURE    = 204005;
+integer SET_THICKNESS       = 204006;
+integer SET_COLOR           = 204007;
+
+// This is an extended character escape sequence.
+string  ESCAPE_SEQUENCE = "\\e";
+
+// This is used to get an index for the extended character.
+string  EXTENDED_INDEX  = "12345";
+
+// Face numbers.
+integer FACE_1          = 3;
+integer FACE_2          = 7;
+integer FACE_3          = 4;
+integer FACE_4          = 6;
+integer FACE_5          = 1;
+
+// Used to hide the text after a fade-out.
+//key     TRANSPARENT     = "701917a8-d614-471f-13dd-5f4644e36e3c";
+string      TRANSPARENT     = "Transparent";
+///////////// END CONSTANTS ////////////////
+
+///////////// GLOBAL VARIABLES ///////////////
+// This is the key of the font we are displaying.
+//key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
+string      gFontTexture    = "FontsTexture";
+// All displayable characters.  Default to ASCII order.
+string gCharIndex;
+// This is the channel to listen on while acting
+// as a cell in a larger display.
+integer gCellChannel        = 1;
+// This is the starting character position in the cell channel message
+// to render.
+integer gCellCharPosition   = 0;
+// This is whether or not to use the fade in/out special effect.
+integer gCellUseFading      = FALSE;
+// This is how long to display the text before fading out (if using
+// fading special effect).
+// Note: < 0  means don't fade out.
+float   gCellHoldDelay      = 1.0;
+/////////// END GLOBAL VARIABLES ////////////
+
+ResetCharIndex() {
+   gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
+   // \" <-- Fixes LSL syntax highlighting bug.
+   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
+   gCharIndex += "\n\n\n\n\n";
+}
+
+vector GetGridOffset(integer index) {
+   // Calculate the offset needed to display this character.
+   integer Row = index / 10;
+   integer Col = index % 10;
+
+   // Return the offset in the texture.
+   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
+}
+
+ShowChars(vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
+   // Set the primitive textures directly.
+   
+   // <-0.256, 0, 0>
+   // <0, 0, 0>
+   // <0.130, 0, 0>
+   // <0, 0, 0>
+   // <-0.74, 0, 0>
+   
+   llSetPrimitiveParams( [
+        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
+        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
+        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
+        ]);
+}
+
+RenderString(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+RenderWithEffects(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // First set the alpha to the lowest possible.
+   llSetAlpha(0.05, ALL_SIDES);
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.
+    float Alpha;
+    for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // See if we want to fade out as well.
+   if (gCellHoldDelay < 0.0)
+       // No, bail out. (Just keep showing the string at full strength).
+       return;
+          // Hold the text for a while.
+   llSleep(gCellHoldDelay);
+      // Now fade out.
+   for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // Make the text transparent to fully hide it.
+   llSetTexture(TRANSPARENT, ALL_SIDES);
+}
+
+RenderExtended(string str) {
+   // Look for escape sequences.
+   list Parsed       = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
+   integer ParsedLen = llGetListLength(Parsed);
+
+   // Create a list of index values to work with.
+   list Indices;
+   // We start with room for 5 indices.
+   integer IndicesLeft = 5;
+
+   integer i;
+   string Token;
+   integer Clipped;
+   integer LastWasEscapeSequence = FALSE;
+   // Work from left to right.
+   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {
+       Token = llList2String(Parsed, i);
+
+       // If this is an escape sequence, just set the flag and move on.
+       if (Token == ESCAPE_SEQUENCE) {
+           LastWasEscapeSequence = TRUE;
+       }
+       else { // Token != ESCAPE_SEQUENCE
+           // Otherwise this is a normal token.  Check its length.
+           Clipped = FALSE;
+           integer TokenLength = llStringLength(Token);
+           // Clip if necessary.
+           if (TokenLength > IndicesLeft) {
+               Token = llGetSubString(Token, 0, IndicesLeft - 1);
+               TokenLength = llStringLength(Token);
+               IndicesLeft = 0;
+               Clipped = TRUE;
+           }
+           else
+               IndicesLeft -= TokenLength;
+
+           // Was the previous token an escape sequence?
+           if (LastWasEscapeSequence) {
+               // Yes, the first character is an escape character, the rest are normal.
+
+               // This is the extended character.
+               Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
+
+               // These are the normal characters.
+               integer j;
+               for (j = 1; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+           else { // Normal string.
+               // Just add the characters normally.
+               integer j;
+               for (j = 0; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+
+           // Unset this flag, since this was not an escape sequence.
+           LastWasEscapeSequence = FALSE;
+       }
+   }
+
+   // Use the indices to create grid positions.
+   vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0));
+   vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
+   vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
+   vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
+   vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+integer ConvertIndex(integer index) {
+   // This converts from an ASCII based index to our indexing scheme.
+   if (index >= 32) // ' ' or higher
+       index -= 32;
+   else { // index < 32
+       // Quick bounds check.
+       if (index > 15)
+           index = 15;
+
+       index += 94; // extended characters
+   }
+
+   return index;
+}
+
+default {
+   state_entry() {
+       // Initialize the character index.
+       llSetTexture(TRANSPARENT, ALL_SIDES);
+       ResetCharIndex();
+   }
+ 
+   link_message(integer sender, integer channel, string data, key id) {
+       if (channel == DISPLAY_STRING) {
+           RenderString(data);
+           return;
+       }
+       if (channel == DISPLAY_EXTENDED) {
+           RenderExtended(data);
+           return;
+       }
+       if (channel == gCellChannel) {
+           // Extract the characters we are interested in, and use those to render.
+           string TextToRender = llGetSubString(data, gCellCharPosition, gCellCharPosition + 4);
+                      // See if we need to show special effects.
+           if (gCellUseFading)
+               RenderWithEffects( TextToRender );
+           else // !gCellUseFading
+               RenderString( TextToRender );
+           return;
+       }
+       if (channel == REMAP_INDICES) {
+           // Parse the message, splitting it up into index values.
+           list Parsed = llCSV2List(data);
+           integer i;
+           // Go through the list and swap each pair of indices.
+           for (i = 0; i < llGetListLength(Parsed); i += 2) {
+               integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
+               integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
+
+               // Swap these index values.
+               string Value1 = llGetSubString(gCharIndex, Index1, Index1);
+               string Value2 = llGetSubString(gCharIndex, Index2, Index2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
+               gCharIndex = llInsertString(gCharIndex, Index1, Value2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
+               gCharIndex = llInsertString(gCharIndex, Index2, Value1);
+           }
+           return;
+       }
+       if (channel == RESET_INDICES) {
+           // Restore the character index back to default settings.
+           ResetCharIndex();
+           return;
+       }
+       if (channel == SET_CELL_INFO) {
+           // Change the channel we listen to for cell commands, the
+           // starting character position to extract from, and
+           // special effect attributes.
+           list Parsed = llCSV2List(data);
+           gCellChannel        = (integer) llList2String(Parsed, 0);
+           gCellCharPosition   = (integer) llList2String(Parsed, 1);
+           gCellUseFading      = (integer) llList2String(Parsed, 2);
+           gCellHoldDelay      = (float)   llList2String(Parsed, 3);
+           return;
+       }
+       if (channel == SET_FONT_TEXTURE) {
+           // Use the new texture instead of the current one.
+           gFontTexture = id;
+           // Change the currently shown texture.
+           llSetTexture(gFontTexture, FACE_1);
+           llSetTexture(gFontTexture, FACE_2);
+           llSetTexture(gFontTexture, FACE_3);
+           llSetTexture(gFontTexture, FACE_4);
+           llSetTexture(gFontTexture, FACE_5);
+           return;
+       }
+       if (channel == SET_THICKNESS) {
+           // Set our z scale to thickness, while staying fixed
+           // in position relative the prim below us.
+           vector Scale    = llGetScale();
+           float Thickness = (float) data;
+                      // Reposition only if this isn't the root prim.
+           integer ThisLink = llGetLinkNumber();
+           if (ThisLink != 0 || ThisLink != 1) {
+               // This is not the root prim.
+               vector Up = llRot2Up(llGetLocalRot());
+               float DistanceToMove = Thickness / 2.0 - Scale.z / 2.0;
+               vector Pos = llGetLocalPos();
+               llSetPos(Pos + DistanceToMove * Up);
+           }
+                      // Apply the new thickness.
+           Scale.z = Thickness;
+           llSetScale(Scale);
+           return;
+       }
+       if (channel == SET_COLOR) {
+           vector newColor = (vector)data;
+           llSetColor(newColor, ALL_SIDES);
+       }
+   }
+} // Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: lsl/xytext/xytext.lsl.3

+ 321 - 0
assets/xytext/xytext.lslp.4

@@ -0,0 +1,321 @@
+////////////////////////////////////////////
+// XyText v1.2 Script (5 Face, Single Texture)
+//
+// Written by Xylor Baysklef
+//
+// Modified by Kermitt Quirk 19/01/2006
+// To add support for 5 face prim instead of 3
+//
+////////////////////////////////////////////
+
+/////////////// CONSTANTS ///////////////////
+// XyText Message Map.
+integer DISPLAY_STRING      = 204000;
+integer DISPLAY_EXTENDED    = 204001;
+integer REMAP_INDICES       = 204002;
+integer RESET_INDICES       = 204003;
+integer SET_CELL_INFO       = 204004;
+integer SET_FONT_TEXTURE    = 204005;
+integer SET_THICKNESS       = 204006;
+integer SET_COLOR           = 204007;
+
+// This is an extended character escape sequence.
+string  ESCAPE_SEQUENCE = "\\e";
+
+// This is used to get an index for the extended character.
+string  EXTENDED_INDEX  = "12345";
+
+// Face numbers.
+integer FACE_1          = 3;
+integer FACE_2          = 7;
+integer FACE_3          = 4;
+integer FACE_4          = 6;
+integer FACE_5          = 1;
+
+// Used to hide the text after a fade-out.
+//key     TRANSPARENT     = "701917a8-d614-471f-13dd-5f4644e36e3c";
+string      TRANSPARENT     = "Transparent";
+///////////// END CONSTANTS ////////////////
+
+///////////// GLOBAL VARIABLES ///////////////
+// This is the key of the font we are displaying.
+//key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
+string      gFontTexture    = "FontsTexture";
+// All displayable characters.  Default to ASCII order.
+string gCharIndex;
+// This is the channel to listen on while acting
+// as a cell in a larger display.
+integer gCellChannel        = 2;
+// This is the starting character position in the cell channel message
+// to render.
+integer gCellCharPosition   = 10;
+// This is whether or not to use the fade in/out special effect.
+integer gCellUseFading      = FALSE;
+// This is how long to display the text before fading out (if using
+// fading special effect).
+// Note: < 0  means don't fade out.
+float   gCellHoldDelay      = 1.0;
+/////////// END GLOBAL VARIABLES ////////////
+
+ResetCharIndex() {
+   gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
+   // \" <-- Fixes LSL syntax highlighting bug.
+   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
+   gCharIndex += "\n\n\n\n\n";
+}
+
+vector GetGridOffset(integer index) {
+   // Calculate the offset needed to display this character.
+   integer Row = index / 10;
+   integer Col = index % 10;
+
+   // Return the offset in the texture.
+   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
+}
+
+ShowChars(vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
+   // Set the primitive textures directly.
+   
+   // <-0.256, 0, 0>
+   // <0, 0, 0>
+   // <0.130, 0, 0>
+   // <0, 0, 0>
+   // <-0.74, 0, 0>
+   
+   llSetPrimitiveParams( [
+        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
+        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
+        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
+        ]);
+}
+
+RenderString(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+RenderWithEffects(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // First set the alpha to the lowest possible.
+   llSetAlpha(0.05, ALL_SIDES);
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.
+    float Alpha;
+    for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // See if we want to fade out as well.
+   if (gCellHoldDelay < 0.0)
+       // No, bail out. (Just keep showing the string at full strength).
+       return;
+          // Hold the text for a while.
+   llSleep(gCellHoldDelay);
+      // Now fade out.
+   for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // Make the text transparent to fully hide it.
+   llSetTexture(TRANSPARENT, ALL_SIDES);
+}
+
+RenderExtended(string str) {
+   // Look for escape sequences.
+   list Parsed       = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
+   integer ParsedLen = llGetListLength(Parsed);
+
+   // Create a list of index values to work with.
+   list Indices;
+   // We start with room for 5 indices.
+   integer IndicesLeft = 5;
+
+   integer i;
+   string Token;
+   integer Clipped;
+   integer LastWasEscapeSequence = FALSE;
+   // Work from left to right.
+   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {
+       Token = llList2String(Parsed, i);
+
+       // If this is an escape sequence, just set the flag and move on.
+       if (Token == ESCAPE_SEQUENCE) {
+           LastWasEscapeSequence = TRUE;
+       }
+       else { // Token != ESCAPE_SEQUENCE
+           // Otherwise this is a normal token.  Check its length.
+           Clipped = FALSE;
+           integer TokenLength = llStringLength(Token);
+           // Clip if necessary.
+           if (TokenLength > IndicesLeft) {
+               Token = llGetSubString(Token, 0, IndicesLeft - 1);
+               TokenLength = llStringLength(Token);
+               IndicesLeft = 0;
+               Clipped = TRUE;
+           }
+           else
+               IndicesLeft -= TokenLength;
+
+           // Was the previous token an escape sequence?
+           if (LastWasEscapeSequence) {
+               // Yes, the first character is an escape character, the rest are normal.
+
+               // This is the extended character.
+               Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
+
+               // These are the normal characters.
+               integer j;
+               for (j = 1; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+           else { // Normal string.
+               // Just add the characters normally.
+               integer j;
+               for (j = 0; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+
+           // Unset this flag, since this was not an escape sequence.
+           LastWasEscapeSequence = FALSE;
+       }
+   }
+
+   // Use the indices to create grid positions.
+   vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0));
+   vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
+   vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
+   vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
+   vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+integer ConvertIndex(integer index) {
+   // This converts from an ASCII based index to our indexing scheme.
+   if (index >= 32) // ' ' or higher
+       index -= 32;
+   else { // index < 32
+       // Quick bounds check.
+       if (index > 15)
+           index = 15;
+
+       index += 94; // extended characters
+   }
+
+   return index;
+}
+
+default {
+   state_entry() {
+       // Initialize the character index.
+       llSetTexture(TRANSPARENT, ALL_SIDES);
+       ResetCharIndex();
+   }
+ 
+   link_message(integer sender, integer channel, string data, key id) {
+       if (channel == DISPLAY_STRING) {
+           RenderString(data);
+           return;
+       }
+       if (channel == DISPLAY_EXTENDED) {
+           RenderExtended(data);
+           return;
+       }
+       if (channel == gCellChannel) {
+           // Extract the characters we are interested in, and use those to render.
+           string TextToRender = llGetSubString(data, gCellCharPosition, gCellCharPosition + 4);
+                      // See if we need to show special effects.
+           if (gCellUseFading)
+               RenderWithEffects( TextToRender );
+           else // !gCellUseFading
+               RenderString( TextToRender );
+           return;
+       }
+       if (channel == REMAP_INDICES) {
+           // Parse the message, splitting it up into index values.
+           list Parsed = llCSV2List(data);
+           integer i;
+           // Go through the list and swap each pair of indices.
+           for (i = 0; i < llGetListLength(Parsed); i += 2) {
+               integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
+               integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
+
+               // Swap these index values.
+               string Value1 = llGetSubString(gCharIndex, Index1, Index1);
+               string Value2 = llGetSubString(gCharIndex, Index2, Index2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
+               gCharIndex = llInsertString(gCharIndex, Index1, Value2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
+               gCharIndex = llInsertString(gCharIndex, Index2, Value1);
+           }
+           return;
+       }
+       if (channel == RESET_INDICES) {
+           // Restore the character index back to default settings.
+           ResetCharIndex();
+           return;
+       }
+       if (channel == SET_CELL_INFO) {
+           // Change the channel we listen to for cell commands, the
+           // starting character position to extract from, and
+           // special effect attributes.
+           list Parsed = llCSV2List(data);
+           gCellChannel        = (integer) llList2String(Parsed, 0);
+           gCellCharPosition   = (integer) llList2String(Parsed, 1);
+           gCellUseFading      = (integer) llList2String(Parsed, 2);
+           gCellHoldDelay      = (float)   llList2String(Parsed, 3);
+           return;
+       }
+       if (channel == SET_FONT_TEXTURE) {
+           // Use the new texture instead of the current one.
+           gFontTexture = id;
+           // Change the currently shown texture.
+           llSetTexture(gFontTexture, FACE_1);
+           llSetTexture(gFontTexture, FACE_2);
+           llSetTexture(gFontTexture, FACE_3);
+           llSetTexture(gFontTexture, FACE_4);
+           llSetTexture(gFontTexture, FACE_5);
+           return;
+       }
+       if (channel == SET_THICKNESS) {
+           // Set our z scale to thickness, while staying fixed
+           // in position relative the prim below us.
+           vector Scale    = llGetScale();
+           float Thickness = (float) data;
+                      // Reposition only if this isn't the root prim.
+           integer ThisLink = llGetLinkNumber();
+           if (ThisLink != 0 || ThisLink != 1) {
+               // This is not the root prim.
+               vector Up = llRot2Up(llGetLocalRot());
+               float DistanceToMove = Thickness / 2.0 - Scale.z / 2.0;
+               vector Pos = llGetLocalPos();
+               llSetPos(Pos + DistanceToMove * Up);
+           }
+                      // Apply the new thickness.
+           Scale.z = Thickness;
+           llSetScale(Scale);
+           return;
+       }
+       if (channel == SET_COLOR) {
+           vector newColor = (vector)data;
+           llSetColor(newColor, ALL_SIDES);
+       }
+   }
+} 
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: lsl/xytext/xytext.lsl.4

+ 321 - 0
assets/xytext/xytext.lslp.5

@@ -0,0 +1,321 @@
+////////////////////////////////////////////
+// XyText v1.2 Script (5 Face, Single Texture)
+//
+// Written by Xylor Baysklef
+//
+// Modified by Kermitt Quirk 19/01/2006
+// To add support for 5 face prim instead of 3
+//
+////////////////////////////////////////////
+
+/////////////// CONSTANTS ///////////////////
+// XyText Message Map.
+integer DISPLAY_STRING      = 204000;
+integer DISPLAY_EXTENDED    = 204001;
+integer REMAP_INDICES       = 204002;
+integer RESET_INDICES       = 204003;
+integer SET_CELL_INFO       = 204004;
+integer SET_FONT_TEXTURE    = 204005;
+integer SET_THICKNESS       = 204006;
+integer SET_COLOR           = 204007;
+
+// This is an extended character escape sequence.
+string  ESCAPE_SEQUENCE = "\\e";
+
+// This is used to get an index for the extended character.
+string  EXTENDED_INDEX  = "12345";
+
+// Face numbers.
+integer FACE_1          = 3;
+integer FACE_2          = 7;
+integer FACE_3          = 4;
+integer FACE_4          = 6;
+integer FACE_5          = 1;
+
+// Used to hide the text after a fade-out.
+//key     TRANSPARENT     = "701917a8-d614-471f-13dd-5f4644e36e3c";
+string      TRANSPARENT     = "Transparent";
+///////////// END CONSTANTS ////////////////
+
+///////////// GLOBAL VARIABLES ///////////////
+// This is the key of the font we are displaying.
+//key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
+string      gFontTexture    = "FontsTexture";
+// All displayable characters.  Default to ASCII order.
+string gCharIndex;
+// This is the channel to listen on while acting
+// as a cell in a larger display.
+integer gCellChannel        = 2;
+// This is the starting character position in the cell channel message
+// to render.
+integer gCellCharPosition   = 40;
+// This is whether or not to use the fade in/out special effect.
+integer gCellUseFading      = FALSE;
+// This is how long to display the text before fading out (if using
+// fading special effect).
+// Note: < 0  means don't fade out.
+float   gCellHoldDelay      = 1.0;
+/////////// END GLOBAL VARIABLES ////////////
+
+ResetCharIndex() {
+   gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
+   // \" <-- Fixes LSL syntax highlighting bug.
+   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
+   gCharIndex += "\n\n\n\n\n";
+}
+
+vector GetGridOffset(integer index) {
+   // Calculate the offset needed to display this character.
+   integer Row = index / 10;
+   integer Col = index % 10;
+
+   // Return the offset in the texture.
+   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
+}
+
+ShowChars(vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
+   // Set the primitive textures directly.
+   
+   // <-0.256, 0, 0>
+   // <0, 0, 0>
+   // <0.130, 0, 0>
+   // <0, 0, 0>
+   // <-0.74, 0, 0>
+   
+   llSetPrimitiveParams( [
+        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
+        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
+        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
+        ]);
+}
+
+RenderString(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+RenderWithEffects(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // First set the alpha to the lowest possible.
+   llSetAlpha(0.05, ALL_SIDES);
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.
+    float Alpha;
+    for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // See if we want to fade out as well.
+   if (gCellHoldDelay < 0.0)
+       // No, bail out. (Just keep showing the string at full strength).
+       return;
+          // Hold the text for a while.
+   llSleep(gCellHoldDelay);
+      // Now fade out.
+   for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // Make the text transparent to fully hide it.
+   llSetTexture(TRANSPARENT, ALL_SIDES);
+}
+
+RenderExtended(string str) {
+   // Look for escape sequences.
+   list Parsed       = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
+   integer ParsedLen = llGetListLength(Parsed);
+
+   // Create a list of index values to work with.
+   list Indices;
+   // We start with room for 5 indices.
+   integer IndicesLeft = 5;
+
+   integer i;
+   string Token;
+   integer Clipped;
+   integer LastWasEscapeSequence = FALSE;
+   // Work from left to right.
+   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {
+       Token = llList2String(Parsed, i);
+
+       // If this is an escape sequence, just set the flag and move on.
+       if (Token == ESCAPE_SEQUENCE) {
+           LastWasEscapeSequence = TRUE;
+       }
+       else { // Token != ESCAPE_SEQUENCE
+           // Otherwise this is a normal token.  Check its length.
+           Clipped = FALSE;
+           integer TokenLength = llStringLength(Token);
+           // Clip if necessary.
+           if (TokenLength > IndicesLeft) {
+               Token = llGetSubString(Token, 0, IndicesLeft - 1);
+               TokenLength = llStringLength(Token);
+               IndicesLeft = 0;
+               Clipped = TRUE;
+           }
+           else
+               IndicesLeft -= TokenLength;
+
+           // Was the previous token an escape sequence?
+           if (LastWasEscapeSequence) {
+               // Yes, the first character is an escape character, the rest are normal.
+
+               // This is the extended character.
+               Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
+
+               // These are the normal characters.
+               integer j;
+               for (j = 1; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+           else { // Normal string.
+               // Just add the characters normally.
+               integer j;
+               for (j = 0; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+
+           // Unset this flag, since this was not an escape sequence.
+           LastWasEscapeSequence = FALSE;
+       }
+   }
+
+   // Use the indices to create grid positions.
+   vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0));
+   vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
+   vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
+   vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
+   vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+integer ConvertIndex(integer index) {
+   // This converts from an ASCII based index to our indexing scheme.
+   if (index >= 32) // ' ' or higher
+       index -= 32;
+   else { // index < 32
+       // Quick bounds check.
+       if (index > 15)
+           index = 15;
+
+       index += 94; // extended characters
+   }
+
+   return index;
+}
+
+default {
+   state_entry() {
+       // Initialize the character index.
+       llSetTexture(TRANSPARENT, ALL_SIDES);
+       ResetCharIndex();
+   }
+ 
+   link_message(integer sender, integer channel, string data, key id) {
+       if (channel == DISPLAY_STRING) {
+           RenderString(data);
+           return;
+       }
+       if (channel == DISPLAY_EXTENDED) {
+           RenderExtended(data);
+           return;
+       }
+       if (channel == gCellChannel) {
+           // Extract the characters we are interested in, and use those to render.
+           string TextToRender = llGetSubString(data, gCellCharPosition, gCellCharPosition + 4);
+                      // See if we need to show special effects.
+           if (gCellUseFading)
+               RenderWithEffects( TextToRender );
+           else // !gCellUseFading
+               RenderString( TextToRender );
+           return;
+       }
+       if (channel == REMAP_INDICES) {
+           // Parse the message, splitting it up into index values.
+           list Parsed = llCSV2List(data);
+           integer i;
+           // Go through the list and swap each pair of indices.
+           for (i = 0; i < llGetListLength(Parsed); i += 2) {
+               integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
+               integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
+
+               // Swap these index values.
+               string Value1 = llGetSubString(gCharIndex, Index1, Index1);
+               string Value2 = llGetSubString(gCharIndex, Index2, Index2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
+               gCharIndex = llInsertString(gCharIndex, Index1, Value2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
+               gCharIndex = llInsertString(gCharIndex, Index2, Value1);
+           }
+           return;
+       }
+       if (channel == RESET_INDICES) {
+           // Restore the character index back to default settings.
+           ResetCharIndex();
+           return;
+       }
+       if (channel == SET_CELL_INFO) {
+           // Change the channel we listen to for cell commands, the
+           // starting character position to extract from, and
+           // special effect attributes.
+           list Parsed = llCSV2List(data);
+           gCellChannel        = (integer) llList2String(Parsed, 0);
+           gCellCharPosition   = (integer) llList2String(Parsed, 1);
+           gCellUseFading      = (integer) llList2String(Parsed, 2);
+           gCellHoldDelay      = (float)   llList2String(Parsed, 3);
+           return;
+       }
+       if (channel == SET_FONT_TEXTURE) {
+           // Use the new texture instead of the current one.
+           gFontTexture = id;
+           // Change the currently shown texture.
+           llSetTexture(gFontTexture, FACE_1);
+           llSetTexture(gFontTexture, FACE_2);
+           llSetTexture(gFontTexture, FACE_3);
+           llSetTexture(gFontTexture, FACE_4);
+           llSetTexture(gFontTexture, FACE_5);
+           return;
+       }
+       if (channel == SET_THICKNESS) {
+           // Set our z scale to thickness, while staying fixed
+           // in position relative the prim below us.
+           vector Scale    = llGetScale();
+           float Thickness = (float) data;
+                      // Reposition only if this isn't the root prim.
+           integer ThisLink = llGetLinkNumber();
+           if (ThisLink != 0 || ThisLink != 1) {
+               // This is not the root prim.
+               vector Up = llRot2Up(llGetLocalRot());
+               float DistanceToMove = Thickness / 2.0 - Scale.z / 2.0;
+               vector Pos = llGetLocalPos();
+               llSetPos(Pos + DistanceToMove * Up);
+           }
+                      // Apply the new thickness.
+           Scale.z = Thickness;
+           llSetScale(Scale);
+           return;
+       }
+       if (channel == SET_COLOR) {
+           vector newColor = (vector)data;
+           llSetColor(newColor, ALL_SIDES);
+       }
+   }
+} 
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: lsl/xytext/xytext.lsl.5

+ 320 - 0
assets/xytext/xytext.lslp.6

@@ -0,0 +1,320 @@
+////////////////////////////////////////////
+// XyText v1.2 Script (5 Face, Single Texture)
+//
+// Written by Xylor Baysklef
+//
+// Modified by Kermitt Quirk 19/01/2006
+// To add support for 5 face prim instead of 3
+//
+////////////////////////////////////////////
+
+/////////////// CONSTANTS ///////////////////
+// XyText Message Map.
+integer DISPLAY_STRING      = 204000;
+integer DISPLAY_EXTENDED    = 204001;
+integer REMAP_INDICES       = 204002;
+integer RESET_INDICES       = 204003;
+integer SET_CELL_INFO       = 204004;
+integer SET_FONT_TEXTURE    = 204005;
+integer SET_THICKNESS       = 204006;
+integer SET_COLOR           = 204007;
+
+// This is an extended character escape sequence.
+string  ESCAPE_SEQUENCE = "\\e";
+
+// This is used to get an index for the extended character.
+string  EXTENDED_INDEX  = "12345";
+
+// Face numbers.
+integer FACE_1          = 3;
+integer FACE_2          = 7;
+integer FACE_3          = 4;
+integer FACE_4          = 6;
+integer FACE_5          = 1;
+
+// Used to hide the text after a fade-out.
+//key     TRANSPARENT     = "701917a8-d614-471f-13dd-5f4644e36e3c";
+string      TRANSPARENT     = "Transparent";
+///////////// END CONSTANTS ////////////////
+
+///////////// GLOBAL VARIABLES ///////////////
+// This is the key of the font we are displaying.
+//key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
+string      gFontTexture    = "FontsTexture";
+// All displayable characters.  Default to ASCII order.
+string gCharIndex;
+// This is the channel to listen on while acting
+// as a cell in a larger display.
+integer gCellChannel        = 2;
+// This is the starting character position in the cell channel message
+// to render.
+integer gCellCharPosition   = 15;
+// This is whether or not to use the fade in/out special effect.
+integer gCellUseFading      = FALSE;
+// This is how long to display the text before fading out (if using
+// fading special effect).
+// Note: < 0  means don't fade out.
+float   gCellHoldDelay      = 1.0;
+/////////// END GLOBAL VARIABLES ////////////
+
+ResetCharIndex() {
+   gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
+   // \" <-- Fixes LSL syntax highlighting bug.
+   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
+   gCharIndex += "\n\n\n\n\n";
+}
+
+vector GetGridOffset(integer index) {
+   // Calculate the offset needed to display this character.
+   integer Row = index / 10;
+   integer Col = index % 10;
+
+   // Return the offset in the texture.
+   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
+}
+
+ShowChars(vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
+   // Set the primitive textures directly.
+   
+   // <-0.256, 0, 0>
+   // <0, 0, 0>
+   // <0.130, 0, 0>
+   // <0, 0, 0>
+   // <-0.74, 0, 0>
+   
+   llSetPrimitiveParams( [
+        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
+        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
+        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
+        ]);
+}
+
+RenderString(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+RenderWithEffects(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // First set the alpha to the lowest possible.
+   llSetAlpha(0.05, ALL_SIDES);
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.
+    float Alpha;
+    for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // See if we want to fade out as well.
+   if (gCellHoldDelay < 0.0)
+       // No, bail out. (Just keep showing the string at full strength).
+       return;
+          // Hold the text for a while.
+   llSleep(gCellHoldDelay);
+      // Now fade out.
+   for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // Make the text transparent to fully hide it.
+   llSetTexture(TRANSPARENT, ALL_SIDES);
+}
+
+RenderExtended(string str) {
+   // Look for escape sequences.
+   list Parsed       = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
+   integer ParsedLen = llGetListLength(Parsed);
+
+   // Create a list of index values to work with.
+   list Indices;
+   // We start with room for 5 indices.
+   integer IndicesLeft = 5;
+
+   integer i;
+   string Token;
+   integer Clipped;
+   integer LastWasEscapeSequence = FALSE;
+   // Work from left to right.
+   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {
+       Token = llList2String(Parsed, i);
+
+       // If this is an escape sequence, just set the flag and move on.
+       if (Token == ESCAPE_SEQUENCE) {
+           LastWasEscapeSequence = TRUE;
+       }
+       else { // Token != ESCAPE_SEQUENCE
+           // Otherwise this is a normal token.  Check its length.
+           Clipped = FALSE;
+           integer TokenLength = llStringLength(Token);
+           // Clip if necessary.
+           if (TokenLength > IndicesLeft) {
+               Token = llGetSubString(Token, 0, IndicesLeft - 1);
+               TokenLength = llStringLength(Token);
+               IndicesLeft = 0;
+               Clipped = TRUE;
+           }
+           else
+               IndicesLeft -= TokenLength;
+
+           // Was the previous token an escape sequence?
+           if (LastWasEscapeSequence) {
+               // Yes, the first character is an escape character, the rest are normal.
+
+               // This is the extended character.
+               Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
+
+               // These are the normal characters.
+               integer j;
+               for (j = 1; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+           else { // Normal string.
+               // Just add the characters normally.
+               integer j;
+               for (j = 0; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+
+           // Unset this flag, since this was not an escape sequence.
+           LastWasEscapeSequence = FALSE;
+       }
+   }
+
+   // Use the indices to create grid positions.
+   vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0));
+   vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
+   vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
+   vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
+   vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+integer ConvertIndex(integer index) {
+   // This converts from an ASCII based index to our indexing scheme.
+   if (index >= 32) // ' ' or higher
+       index -= 32;
+   else { // index < 32
+       // Quick bounds check.
+       if (index > 15)
+           index = 15;
+
+       index += 94; // extended characters
+   }
+
+   return index;
+}
+
+default {
+   state_entry() {
+       // Initialize the character index.
+       llSetTexture(TRANSPARENT, ALL_SIDES);
+       ResetCharIndex();
+   }
+ 
+   link_message(integer sender, integer channel, string data, key id) {
+       if (channel == DISPLAY_STRING) {
+           RenderString(data);
+           return;
+       }
+       if (channel == DISPLAY_EXTENDED) {
+           RenderExtended(data);
+           return;
+       }
+       if (channel == gCellChannel) {
+           // Extract the characters we are interested in, and use those to render.
+           string TextToRender = llGetSubString(data, gCellCharPosition, gCellCharPosition + 4);
+                      // See if we need to show special effects.
+           if (gCellUseFading)
+               RenderWithEffects( TextToRender );
+           else // !gCellUseFading
+               RenderString( TextToRender );
+           return;
+       }
+       if (channel == REMAP_INDICES) {
+           // Parse the message, splitting it up into index values.
+           list Parsed = llCSV2List(data);
+           integer i;
+           // Go through the list and swap each pair of indices.
+           for (i = 0; i < llGetListLength(Parsed); i += 2) {
+               integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
+               integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
+
+               // Swap these index values.
+               string Value1 = llGetSubString(gCharIndex, Index1, Index1);
+               string Value2 = llGetSubString(gCharIndex, Index2, Index2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
+               gCharIndex = llInsertString(gCharIndex, Index1, Value2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
+               gCharIndex = llInsertString(gCharIndex, Index2, Value1);
+           }
+           return;
+       }
+       if (channel == RESET_INDICES) {
+           // Restore the character index back to default settings.
+           ResetCharIndex();
+           return;
+       }
+       if (channel == SET_CELL_INFO) {
+           // Change the channel we listen to for cell commands, the
+           // starting character position to extract from, and
+           // special effect attributes.
+           list Parsed = llCSV2List(data);
+           gCellChannel        = (integer) llList2String(Parsed, 0);
+           gCellCharPosition   = (integer) llList2String(Parsed, 1);
+           gCellUseFading      = (integer) llList2String(Parsed, 2);
+           gCellHoldDelay      = (float)   llList2String(Parsed, 3);
+           return;
+       }
+       if (channel == SET_FONT_TEXTURE) {
+           // Use the new texture instead of the current one.
+           gFontTexture = id;
+           // Change the currently shown texture.
+           llSetTexture(gFontTexture, FACE_1);
+           llSetTexture(gFontTexture, FACE_2);
+           llSetTexture(gFontTexture, FACE_3);
+           llSetTexture(gFontTexture, FACE_4);
+           llSetTexture(gFontTexture, FACE_5);
+           return;
+       }
+       if (channel == SET_THICKNESS) {
+           // Set our z scale to thickness, while staying fixed
+           // in position relative the prim below us.
+           vector Scale    = llGetScale();
+           float Thickness = (float) data;
+                      // Reposition only if this isn't the root prim.
+           integer ThisLink = llGetLinkNumber();
+           if (ThisLink != 0 || ThisLink != 1) {
+               // This is not the root prim.
+               vector Up = llRot2Up(llGetLocalRot());
+               float DistanceToMove = Thickness / 2.0 - Scale.z / 2.0;
+               vector Pos = llGetLocalPos();
+               llSetPos(Pos + DistanceToMove * Up);
+           }
+                      // Apply the new thickness.
+           Scale.z = Thickness;
+           llSetScale(Scale);
+           return;
+       }
+       if (channel == SET_COLOR) {
+           vector newColor = (vector)data;
+           llSetColor(newColor, ALL_SIDES);
+       }
+   }
+} // Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: lsl/xytext/xytext.lsl.6

+ 321 - 0
assets/xytext/xytext.lslp.7

@@ -0,0 +1,321 @@
+////////////////////////////////////////////
+// XyText v1.2 Script (5 Face, Single Texture)
+//
+// Written by Xylor Baysklef
+//
+// Modified by Kermitt Quirk 19/01/2006
+// To add support for 5 face prim instead of 3
+//
+////////////////////////////////////////////
+
+/////////////// CONSTANTS ///////////////////
+// XyText Message Map.
+integer DISPLAY_STRING      = 204000;
+integer DISPLAY_EXTENDED    = 204001;
+integer REMAP_INDICES       = 204002;
+integer RESET_INDICES       = 204003;
+integer SET_CELL_INFO       = 204004;
+integer SET_FONT_TEXTURE    = 204005;
+integer SET_THICKNESS       = 204006;
+integer SET_COLOR           = 204007;
+
+// This is an extended character escape sequence.
+string  ESCAPE_SEQUENCE = "\\e";
+
+// This is used to get an index for the extended character.
+string  EXTENDED_INDEX  = "12345";
+
+// Face numbers.
+integer FACE_1          = 3;
+integer FACE_2          = 7;
+integer FACE_3          = 4;
+integer FACE_4          = 6;
+integer FACE_5          = 1;
+
+// Used to hide the text after a fade-out.
+//key     TRANSPARENT     = "701917a8-d614-471f-13dd-5f4644e36e3c";
+string      TRANSPARENT     = "Transparent";
+///////////// END CONSTANTS ////////////////
+
+///////////// GLOBAL VARIABLES ///////////////
+// This is the key of the font we are displaying.
+//key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
+string      gFontTexture    = "FontsTexture";
+// All displayable characters.  Default to ASCII order.
+string gCharIndex;
+// This is the channel to listen on while acting
+// as a cell in a larger display.
+integer gCellChannel        = 2;
+// This is the starting character position in the cell channel message
+// to render.
+integer gCellCharPosition   = 55;
+// This is whether or not to use the fade in/out special effect.
+integer gCellUseFading      = FALSE;
+// This is how long to display the text before fading out (if using
+// fading special effect).
+// Note: < 0  means don't fade out.
+float   gCellHoldDelay      = 1.0;
+/////////// END GLOBAL VARIABLES ////////////
+
+ResetCharIndex() {
+   gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
+   // \" <-- Fixes LSL syntax highlighting bug.
+   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
+   gCharIndex += "\n\n\n\n\n";
+}
+
+vector GetGridOffset(integer index) {
+   // Calculate the offset needed to display this character.
+   integer Row = index / 10;
+   integer Col = index % 10;
+
+   // Return the offset in the texture.
+   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
+}
+
+ShowChars(vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
+   // Set the primitive textures directly.
+   
+   // <-0.256, 0, 0>
+   // <0, 0, 0>
+   // <0.130, 0, 0>
+   // <0, 0, 0>
+   // <-0.74, 0, 0>
+   
+   llSetPrimitiveParams( [
+        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
+        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
+        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
+        ]);
+}
+
+RenderString(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+RenderWithEffects(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // First set the alpha to the lowest possible.
+   llSetAlpha(0.05, ALL_SIDES);
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.
+    float Alpha;
+    for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // See if we want to fade out as well.
+   if (gCellHoldDelay < 0.0)
+       // No, bail out. (Just keep showing the string at full strength).
+       return;
+          // Hold the text for a while.
+   llSleep(gCellHoldDelay);
+      // Now fade out.
+   for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // Make the text transparent to fully hide it.
+   llSetTexture(TRANSPARENT, ALL_SIDES);
+}
+
+RenderExtended(string str) {
+   // Look for escape sequences.
+   list Parsed       = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
+   integer ParsedLen = llGetListLength(Parsed);
+
+   // Create a list of index values to work with.
+   list Indices;
+   // We start with room for 5 indices.
+   integer IndicesLeft = 5;
+
+   integer i;
+   string Token;
+   integer Clipped;
+   integer LastWasEscapeSequence = FALSE;
+   // Work from left to right.
+   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {
+       Token = llList2String(Parsed, i);
+
+       // If this is an escape sequence, just set the flag and move on.
+       if (Token == ESCAPE_SEQUENCE) {
+           LastWasEscapeSequence = TRUE;
+       }
+       else { // Token != ESCAPE_SEQUENCE
+           // Otherwise this is a normal token.  Check its length.
+           Clipped = FALSE;
+           integer TokenLength = llStringLength(Token);
+           // Clip if necessary.
+           if (TokenLength > IndicesLeft) {
+               Token = llGetSubString(Token, 0, IndicesLeft - 1);
+               TokenLength = llStringLength(Token);
+               IndicesLeft = 0;
+               Clipped = TRUE;
+           }
+           else
+               IndicesLeft -= TokenLength;
+
+           // Was the previous token an escape sequence?
+           if (LastWasEscapeSequence) {
+               // Yes, the first character is an escape character, the rest are normal.
+
+               // This is the extended character.
+               Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
+
+               // These are the normal characters.
+               integer j;
+               for (j = 1; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+           else { // Normal string.
+               // Just add the characters normally.
+               integer j;
+               for (j = 0; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+
+           // Unset this flag, since this was not an escape sequence.
+           LastWasEscapeSequence = FALSE;
+       }
+   }
+
+   // Use the indices to create grid positions.
+   vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0));
+   vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
+   vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
+   vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
+   vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+integer ConvertIndex(integer index) {
+   // This converts from an ASCII based index to our indexing scheme.
+   if (index >= 32) // ' ' or higher
+       index -= 32;
+   else { // index < 32
+       // Quick bounds check.
+       if (index > 15)
+           index = 15;
+
+       index += 94; // extended characters
+   }
+
+   return index;
+}
+
+default {
+   state_entry() {
+       // Initialize the character index.
+       llSetTexture(TRANSPARENT, ALL_SIDES);
+       ResetCharIndex();
+   }
+ 
+   link_message(integer sender, integer channel, string data, key id) {
+       if (channel == DISPLAY_STRING) {
+           RenderString(data);
+           return;
+       }
+       if (channel == DISPLAY_EXTENDED) {
+           RenderExtended(data);
+           return;
+       }
+       if (channel == gCellChannel) {
+           // Extract the characters we are interested in, and use those to render.
+           string TextToRender = llGetSubString(data, gCellCharPosition, gCellCharPosition + 4);
+                      // See if we need to show special effects.
+           if (gCellUseFading)
+               RenderWithEffects( TextToRender );
+           else // !gCellUseFading
+               RenderString( TextToRender );
+           return;
+       }
+       if (channel == REMAP_INDICES) {
+           // Parse the message, splitting it up into index values.
+           list Parsed = llCSV2List(data);
+           integer i;
+           // Go through the list and swap each pair of indices.
+           for (i = 0; i < llGetListLength(Parsed); i += 2) {
+               integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
+               integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
+
+               // Swap these index values.
+               string Value1 = llGetSubString(gCharIndex, Index1, Index1);
+               string Value2 = llGetSubString(gCharIndex, Index2, Index2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
+               gCharIndex = llInsertString(gCharIndex, Index1, Value2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
+               gCharIndex = llInsertString(gCharIndex, Index2, Value1);
+           }
+           return;
+       }
+       if (channel == RESET_INDICES) {
+           // Restore the character index back to default settings.
+           ResetCharIndex();
+           return;
+       }
+       if (channel == SET_CELL_INFO) {
+           // Change the channel we listen to for cell commands, the
+           // starting character position to extract from, and
+           // special effect attributes.
+           list Parsed = llCSV2List(data);
+           gCellChannel        = (integer) llList2String(Parsed, 0);
+           gCellCharPosition   = (integer) llList2String(Parsed, 1);
+           gCellUseFading      = (integer) llList2String(Parsed, 2);
+           gCellHoldDelay      = (float)   llList2String(Parsed, 3);
+           return;
+       }
+       if (channel == SET_FONT_TEXTURE) {
+           // Use the new texture instead of the current one.
+           gFontTexture = id;
+           // Change the currently shown texture.
+           llSetTexture(gFontTexture, FACE_1);
+           llSetTexture(gFontTexture, FACE_2);
+           llSetTexture(gFontTexture, FACE_3);
+           llSetTexture(gFontTexture, FACE_4);
+           llSetTexture(gFontTexture, FACE_5);
+           return;
+       }
+       if (channel == SET_THICKNESS) {
+           // Set our z scale to thickness, while staying fixed
+           // in position relative the prim below us.
+           vector Scale    = llGetScale();
+           float Thickness = (float) data;
+                      // Reposition only if this isn't the root prim.
+           integer ThisLink = llGetLinkNumber();
+           if (ThisLink != 0 || ThisLink != 1) {
+               // This is not the root prim.
+               vector Up = llRot2Up(llGetLocalRot());
+               float DistanceToMove = Thickness / 2.0 - Scale.z / 2.0;
+               vector Pos = llGetLocalPos();
+               llSetPos(Pos + DistanceToMove * Up);
+           }
+                      // Apply the new thickness.
+           Scale.z = Thickness;
+           llSetScale(Scale);
+           return;
+       }
+       if (channel == SET_COLOR) {
+           vector newColor = (vector)data;
+           llSetColor(newColor, ALL_SIDES);
+       }
+   }
+} 
+// Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: lsl/xytext/xytext.lsl.7

+ 320 - 0
assets/xytext/xytext.lslp.8

@@ -0,0 +1,320 @@
+////////////////////////////////////////////
+// XyText v1.2 Script (5 Face, Single Texture)
+//
+// Written by Xylor Baysklef
+//
+// Modified by Kermitt Quirk 19/01/2006
+// To add support for 5 face prim instead of 3
+//
+////////////////////////////////////////////
+
+/////////////// CONSTANTS ///////////////////
+// XyText Message Map.
+integer DISPLAY_STRING      = 204000;
+integer DISPLAY_EXTENDED    = 204001;
+integer REMAP_INDICES       = 204002;
+integer RESET_INDICES       = 204003;
+integer SET_CELL_INFO       = 204004;
+integer SET_FONT_TEXTURE    = 204005;
+integer SET_THICKNESS       = 204006;
+integer SET_COLOR           = 204007;
+
+// This is an extended character escape sequence.
+string  ESCAPE_SEQUENCE = "\\e";
+
+// This is used to get an index for the extended character.
+string  EXTENDED_INDEX  = "12345";
+
+// Face numbers.
+integer FACE_1          = 3;
+integer FACE_2          = 7;
+integer FACE_3          = 4;
+integer FACE_4          = 6;
+integer FACE_5          = 1;
+
+// Used to hide the text after a fade-out.
+//key     TRANSPARENT     = "701917a8-d614-471f-13dd-5f4644e36e3c";
+string      TRANSPARENT     = "Transparent";
+///////////// END CONSTANTS ////////////////
+
+///////////// GLOBAL VARIABLES ///////////////
+// This is the key of the font we are displaying.
+//key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
+string      gFontTexture    = "FontsTexture";
+// All displayable characters.  Default to ASCII order.
+string gCharIndex;
+// This is the channel to listen on while acting
+// as a cell in a larger display.
+integer gCellChannel        = 1;
+// This is the starting character position in the cell channel message
+// to render.
+integer gCellCharPosition   = 10;
+// This is whether or not to use the fade in/out special effect.
+integer gCellUseFading      = FALSE;
+// This is how long to display the text before fading out (if using
+// fading special effect).
+// Note: < 0  means don't fade out.
+float   gCellHoldDelay      = 1.0;
+/////////// END GLOBAL VARIABLES ////////////
+
+ResetCharIndex() {
+   gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
+   // \" <-- Fixes LSL syntax highlighting bug.
+   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
+   gCharIndex += "\n\n\n\n\n";
+}
+
+vector GetGridOffset(integer index) {
+   // Calculate the offset needed to display this character.
+   integer Row = index / 10;
+   integer Col = index % 10;
+
+   // Return the offset in the texture.
+   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
+}
+
+ShowChars(vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
+   // Set the primitive textures directly.
+   
+   // <-0.256, 0, 0>
+   // <0, 0, 0>
+   // <0.130, 0, 0>
+   // <0, 0, 0>
+   // <-0.74, 0, 0>
+   
+   llSetPrimitiveParams( [
+        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
+        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
+        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
+        ]);
+}
+
+RenderString(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+RenderWithEffects(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // First set the alpha to the lowest possible.
+   llSetAlpha(0.05, ALL_SIDES);
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.
+    float Alpha;
+    for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // See if we want to fade out as well.
+   if (gCellHoldDelay < 0.0)
+       // No, bail out. (Just keep showing the string at full strength).
+       return;
+          // Hold the text for a while.
+   llSleep(gCellHoldDelay);
+      // Now fade out.
+   for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // Make the text transparent to fully hide it.
+   llSetTexture(TRANSPARENT, ALL_SIDES);
+}
+
+RenderExtended(string str) {
+   // Look for escape sequences.
+   list Parsed       = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
+   integer ParsedLen = llGetListLength(Parsed);
+
+   // Create a list of index values to work with.
+   list Indices;
+   // We start with room for 5 indices.
+   integer IndicesLeft = 5;
+
+   integer i;
+   string Token;
+   integer Clipped;
+   integer LastWasEscapeSequence = FALSE;
+   // Work from left to right.
+   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {
+       Token = llList2String(Parsed, i);
+
+       // If this is an escape sequence, just set the flag and move on.
+       if (Token == ESCAPE_SEQUENCE) {
+           LastWasEscapeSequence = TRUE;
+       }
+       else { // Token != ESCAPE_SEQUENCE
+           // Otherwise this is a normal token.  Check its length.
+           Clipped = FALSE;
+           integer TokenLength = llStringLength(Token);
+           // Clip if necessary.
+           if (TokenLength > IndicesLeft) {
+               Token = llGetSubString(Token, 0, IndicesLeft - 1);
+               TokenLength = llStringLength(Token);
+               IndicesLeft = 0;
+               Clipped = TRUE;
+           }
+           else
+               IndicesLeft -= TokenLength;
+
+           // Was the previous token an escape sequence?
+           if (LastWasEscapeSequence) {
+               // Yes, the first character is an escape character, the rest are normal.
+
+               // This is the extended character.
+               Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
+
+               // These are the normal characters.
+               integer j;
+               for (j = 1; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+           else { // Normal string.
+               // Just add the characters normally.
+               integer j;
+               for (j = 0; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+
+           // Unset this flag, since this was not an escape sequence.
+           LastWasEscapeSequence = FALSE;
+       }
+   }
+
+   // Use the indices to create grid positions.
+   vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0));
+   vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
+   vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
+   vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
+   vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+integer ConvertIndex(integer index) {
+   // This converts from an ASCII based index to our indexing scheme.
+   if (index >= 32) // ' ' or higher
+       index -= 32;
+   else { // index < 32
+       // Quick bounds check.
+       if (index > 15)
+           index = 15;
+
+       index += 94; // extended characters
+   }
+
+   return index;
+}
+
+default {
+   state_entry() {
+       // Initialize the character index.
+       llSetTexture(TRANSPARENT, ALL_SIDES);
+       ResetCharIndex();
+   }
+ 
+   link_message(integer sender, integer channel, string data, key id) {
+       if (channel == DISPLAY_STRING) {
+           RenderString(data);
+           return;
+       }
+       if (channel == DISPLAY_EXTENDED) {
+           RenderExtended(data);
+           return;
+       }
+       if (channel == gCellChannel) {
+           // Extract the characters we are interested in, and use those to render.
+           string TextToRender = llGetSubString(data, gCellCharPosition, gCellCharPosition + 4);
+                      // See if we need to show special effects.
+           if (gCellUseFading)
+               RenderWithEffects( TextToRender );
+           else // !gCellUseFading
+               RenderString( TextToRender );
+           return;
+       }
+       if (channel == REMAP_INDICES) {
+           // Parse the message, splitting it up into index values.
+           list Parsed = llCSV2List(data);
+           integer i;
+           // Go through the list and swap each pair of indices.
+           for (i = 0; i < llGetListLength(Parsed); i += 2) {
+               integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
+               integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
+
+               // Swap these index values.
+               string Value1 = llGetSubString(gCharIndex, Index1, Index1);
+               string Value2 = llGetSubString(gCharIndex, Index2, Index2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
+               gCharIndex = llInsertString(gCharIndex, Index1, Value2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
+               gCharIndex = llInsertString(gCharIndex, Index2, Value1);
+           }
+           return;
+       }
+       if (channel == RESET_INDICES) {
+           // Restore the character index back to default settings.
+           ResetCharIndex();
+           return;
+       }
+       if (channel == SET_CELL_INFO) {
+           // Change the channel we listen to for cell commands, the
+           // starting character position to extract from, and
+           // special effect attributes.
+           list Parsed = llCSV2List(data);
+           gCellChannel        = (integer) llList2String(Parsed, 0);
+           gCellCharPosition   = (integer) llList2String(Parsed, 1);
+           gCellUseFading      = (integer) llList2String(Parsed, 2);
+           gCellHoldDelay      = (float)   llList2String(Parsed, 3);
+           return;
+       }
+       if (channel == SET_FONT_TEXTURE) {
+           // Use the new texture instead of the current one.
+           gFontTexture = id;
+           // Change the currently shown texture.
+           llSetTexture(gFontTexture, FACE_1);
+           llSetTexture(gFontTexture, FACE_2);
+           llSetTexture(gFontTexture, FACE_3);
+           llSetTexture(gFontTexture, FACE_4);
+           llSetTexture(gFontTexture, FACE_5);
+           return;
+       }
+       if (channel == SET_THICKNESS) {
+           // Set our z scale to thickness, while staying fixed
+           // in position relative the prim below us.
+           vector Scale    = llGetScale();
+           float Thickness = (float) data;
+                      // Reposition only if this isn't the root prim.
+           integer ThisLink = llGetLinkNumber();
+           if (ThisLink != 0 || ThisLink != 1) {
+               // This is not the root prim.
+               vector Up = llRot2Up(llGetLocalRot());
+               float DistanceToMove = Thickness / 2.0 - Scale.z / 2.0;
+               vector Pos = llGetLocalPos();
+               llSetPos(Pos + DistanceToMove * Up);
+           }
+                      // Apply the new thickness.
+           Scale.z = Thickness;
+           llSetScale(Scale);
+           return;
+       }
+       if (channel == SET_COLOR) {
+           vector newColor = (vector)data;
+           llSetColor(newColor, ALL_SIDES);
+       }
+   }
+} // Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: lsl/xytext/xytext.lsl.8

+ 320 - 0
assets/xytext/xytext.lslp.9

@@ -0,0 +1,320 @@
+////////////////////////////////////////////
+// XyText v1.2 Script (5 Face, Single Texture)
+//
+// Written by Xylor Baysklef
+//
+// Modified by Kermitt Quirk 19/01/2006
+// To add support for 5 face prim instead of 3
+//
+////////////////////////////////////////////
+
+/////////////// CONSTANTS ///////////////////
+// XyText Message Map.
+integer DISPLAY_STRING      = 204000;
+integer DISPLAY_EXTENDED    = 204001;
+integer REMAP_INDICES       = 204002;
+integer RESET_INDICES       = 204003;
+integer SET_CELL_INFO       = 204004;
+integer SET_FONT_TEXTURE    = 204005;
+integer SET_THICKNESS       = 204006;
+integer SET_COLOR           = 204007;
+
+// This is an extended character escape sequence.
+string  ESCAPE_SEQUENCE = "\\e";
+
+// This is used to get an index for the extended character.
+string  EXTENDED_INDEX  = "12345";
+
+// Face numbers.
+integer FACE_1          = 3;
+integer FACE_2          = 7;
+integer FACE_3          = 4;
+integer FACE_4          = 6;
+integer FACE_5          = 1;
+
+// Used to hide the text after a fade-out.
+//key     TRANSPARENT     = "701917a8-d614-471f-13dd-5f4644e36e3c";
+string      TRANSPARENT     = "Transparent";
+///////////// END CONSTANTS ////////////////
+
+///////////// GLOBAL VARIABLES ///////////////
+// This is the key of the font we are displaying.
+//key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e";
+string      gFontTexture    = "FontsTexture";
+// All displayable characters.  Default to ASCII order.
+string gCharIndex;
+// This is the channel to listen on while acting
+// as a cell in a larger display.
+integer gCellChannel        = 2;
+// This is the starting character position in the cell channel message
+// to render.
+integer gCellCharPosition   = 65;
+// This is whether or not to use the fade in/out special effect.
+integer gCellUseFading      = FALSE;
+// This is how long to display the text before fading out (if using
+// fading special effect).
+// Note: < 0  means don't fade out.
+float   gCellHoldDelay      = 1.0;
+/////////// END GLOBAL VARIABLES ////////////
+
+ResetCharIndex() {
+   gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
+   // \" <-- Fixes LSL syntax highlighting bug.
+   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
+   gCharIndex += "\n\n\n\n\n";
+}
+
+vector GetGridOffset(integer index) {
+   // Calculate the offset needed to display this character.
+   integer Row = index / 10;
+   integer Col = index % 10;
+
+   // Return the offset in the texture.
+   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>;
+}
+
+ShowChars(vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) {
+   // Set the primitive textures directly.
+   
+   // <-0.256, 0, 0>
+   // <0, 0, 0>
+   // <0.130, 0, 0>
+   // <0, 0, 0>
+   // <-0.74, 0, 0>
+   
+   llSetPrimitiveParams( [
+        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,
+        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0,
+        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0,
+        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.12, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0
+        ]);
+}
+
+RenderString(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+RenderWithEffects(string str) {
+   // Get the grid positions for each pair of characters.
+   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) );
+   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
+   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) );
+   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
+   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) );
+
+   // First set the alpha to the lowest possible.
+   llSetAlpha(0.05, ALL_SIDES);
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.
+    float Alpha;
+    for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // See if we want to fade out as well.
+   if (gCellHoldDelay < 0.0)
+       // No, bail out. (Just keep showing the string at full strength).
+       return;
+          // Hold the text for a while.
+   llSleep(gCellHoldDelay);
+      // Now fade out.
+   for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
+       llSetAlpha(Alpha, ALL_SIDES);
+          // Make the text transparent to fully hide it.
+   llSetTexture(TRANSPARENT, ALL_SIDES);
+}
+
+RenderExtended(string str) {
+   // Look for escape sequences.
+   list Parsed       = llParseString2List(str, [], [ESCAPE_SEQUENCE]);
+   integer ParsedLen = llGetListLength(Parsed);
+
+   // Create a list of index values to work with.
+   list Indices;
+   // We start with room for 5 indices.
+   integer IndicesLeft = 5;
+
+   integer i;
+   string Token;
+   integer Clipped;
+   integer LastWasEscapeSequence = FALSE;
+   // Work from left to right.
+   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {
+       Token = llList2String(Parsed, i);
+
+       // If this is an escape sequence, just set the flag and move on.
+       if (Token == ESCAPE_SEQUENCE) {
+           LastWasEscapeSequence = TRUE;
+       }
+       else { // Token != ESCAPE_SEQUENCE
+           // Otherwise this is a normal token.  Check its length.
+           Clipped = FALSE;
+           integer TokenLength = llStringLength(Token);
+           // Clip if necessary.
+           if (TokenLength > IndicesLeft) {
+               Token = llGetSubString(Token, 0, IndicesLeft - 1);
+               TokenLength = llStringLength(Token);
+               IndicesLeft = 0;
+               Clipped = TRUE;
+           }
+           else
+               IndicesLeft -= TokenLength;
+
+           // Was the previous token an escape sequence?
+           if (LastWasEscapeSequence) {
+               // Yes, the first character is an escape character, the rest are normal.
+
+               // This is the extended character.
+               Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
+
+               // These are the normal characters.
+               integer j;
+               for (j = 1; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+           else { // Normal string.
+               // Just add the characters normally.
+               integer j;
+               for (j = 0; j < TokenLength; j++)
+                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
+           }
+
+           // Unset this flag, since this was not an escape sequence.
+           LastWasEscapeSequence = FALSE;
+       }
+   }
+
+   // Use the indices to create grid positions.
+   vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0));
+   vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) );
+   vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) );
+   vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) );
+   vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) );
+
+   // Use these grid positions to display the correct textures/offsets.
+   ShowChars(GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
+}
+
+integer ConvertIndex(integer index) {
+   // This converts from an ASCII based index to our indexing scheme.
+   if (index >= 32) // ' ' or higher
+       index -= 32;
+   else { // index < 32
+       // Quick bounds check.
+       if (index > 15)
+           index = 15;
+
+       index += 94; // extended characters
+   }
+
+   return index;
+}
+
+default {
+   state_entry() {
+       // Initialize the character index.
+       llSetTexture(TRANSPARENT, ALL_SIDES);
+       ResetCharIndex();
+   }
+ 
+   link_message(integer sender, integer channel, string data, key id) {
+       if (channel == DISPLAY_STRING) {
+           RenderString(data);
+           return;
+       }
+       if (channel == DISPLAY_EXTENDED) {
+           RenderExtended(data);
+           return;
+       }
+       if (channel == gCellChannel) {
+           // Extract the characters we are interested in, and use those to render.
+           string TextToRender = llGetSubString(data, gCellCharPosition, gCellCharPosition + 4);
+                      // See if we need to show special effects.
+           if (gCellUseFading)
+               RenderWithEffects( TextToRender );
+           else // !gCellUseFading
+               RenderString( TextToRender );
+           return;
+       }
+       if (channel == REMAP_INDICES) {
+           // Parse the message, splitting it up into index values.
+           list Parsed = llCSV2List(data);
+           integer i;
+           // Go through the list and swap each pair of indices.
+           for (i = 0; i < llGetListLength(Parsed); i += 2) {
+               integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
+               integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
+
+               // Swap these index values.
+               string Value1 = llGetSubString(gCharIndex, Index1, Index1);
+               string Value2 = llGetSubString(gCharIndex, Index2, Index2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
+               gCharIndex = llInsertString(gCharIndex, Index1, Value2);
+
+               gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
+               gCharIndex = llInsertString(gCharIndex, Index2, Value1);
+           }
+           return;
+       }
+       if (channel == RESET_INDICES) {
+           // Restore the character index back to default settings.
+           ResetCharIndex();
+           return;
+       }
+       if (channel == SET_CELL_INFO) {
+           // Change the channel we listen to for cell commands, the
+           // starting character position to extract from, and
+           // special effect attributes.
+           list Parsed = llCSV2List(data);
+           gCellChannel        = (integer) llList2String(Parsed, 0);
+           gCellCharPosition   = (integer) llList2String(Parsed, 1);
+           gCellUseFading      = (integer) llList2String(Parsed, 2);
+           gCellHoldDelay      = (float)   llList2String(Parsed, 3);
+           return;
+       }
+       if (channel == SET_FONT_TEXTURE) {
+           // Use the new texture instead of the current one.
+           gFontTexture = id;
+           // Change the currently shown texture.
+           llSetTexture(gFontTexture, FACE_1);
+           llSetTexture(gFontTexture, FACE_2);
+           llSetTexture(gFontTexture, FACE_3);
+           llSetTexture(gFontTexture, FACE_4);
+           llSetTexture(gFontTexture, FACE_5);
+           return;
+       }
+       if (channel == SET_THICKNESS) {
+           // Set our z scale to thickness, while staying fixed
+           // in position relative the prim below us.
+           vector Scale    = llGetScale();
+           float Thickness = (float) data;
+                      // Reposition only if this isn't the root prim.
+           integer ThisLink = llGetLinkNumber();
+           if (ThisLink != 0 || ThisLink != 1) {
+               // This is not the root prim.
+               vector Up = llRot2Up(llGetLocalRot());
+               float DistanceToMove = Thickness / 2.0 - Scale.z / 2.0;
+               vector Pos = llGetLocalPos();
+               llSetPos(Pos + DistanceToMove * Up);
+           }
+                      // Apply the new thickness.
+           Scale.z = Thickness;
+           llSetScale(Scale);
+           return;
+       }
+       if (channel == SET_COLOR) {
+           vector newColor = (vector)data;
+           llSetColor(newColor, ALL_SIDES);
+       }
+   }
+} // Please leave the following line intact to show where the script lives in Git:
+// SLOODLE LSL Script Git Location: lsl/xytext/xytext.lsl.9

+ 47 - 0
backup/moodle2/backup_sloodle_activity_task.class.php

@@ -0,0 +1,47 @@
+<?php
+require_once($CFG->dirroot . '/mod/sloodle/backup/moodle2/backup_sloodle_stepslib.php'); 
+require_once($CFG->dirroot . '/mod/sloodle/backup/moodle2/backup_sloodle_settingslib.php'); 
+ 
+/**
+ * sloodle backup task that provides all the settings and steps to perform one
+ * complete backup of the activity
+ */
+class backup_sloodle_activity_task extends backup_activity_task {
+ 
+    /**
+     * Define (add) particular settings this activity can have
+     */
+    protected function define_my_settings() {
+        // No particular settings for this activity
+    }
+ 
+    /**
+     * Define (add) particular steps this activity can have
+     */
+    protected function define_my_steps() {
+        // Choice only has one structure step
+        $this->add_step(new backup_sloodle_activity_structure_step('sloodle_structure', 'sloodle.xml'));
+    }
+ 
+    /**
+     * Code the transformations to perform in the activity in
+     * order to get transportable (encoded) links
+     */
+    static public function encode_content_links($content) {
+        global $CFG;
+
+        $base = preg_quote($CFG->wwwroot,"#");
+
+        // Any links to our own pluginfile.php URLs should be turned into something generic.
+        // These will then be reencoded on restore into whatever is needed for that site.
+        // Example:
+        // http://gershwinklata1.avatarclassroom.com/pluginfile.php/408/mod_sloodle/presenter/1340348485/edmanga.jpg</source>
+
+        if (preg_match('#^'.$base.'(/pluginfile.php/)\d+(/mod_sloodle/presenter/.*)#', $content, $matches)) {
+            $content = '$@SITEROOT@$'.$matches[1].'$@CONTEXTID@$'.$matches[2];
+        }
+
+        return $content;
+    }
+
+}

+ 27 - 0
backup/moodle2/backup_sloodle_settingslib.php

@@ -0,0 +1,27 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+ 
+/**
+ * @package sloodle
+ * @subpackage backup-sloodle
+ * @copyright 2010 onwards Edmund Edgar
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+ 
+ // This activity has not particular settings but the inherited from the generic
+ // backup_activity_task so here there isn't any class definition, like the ones
+ // existing in /backup/moodle2/backup_settingslib.php (activities section)
+

+ 209 - 0
backup/moodle2/backup_sloodle_stepslib.php

@@ -0,0 +1,209 @@
+<?php
+
+
+/**
+ * Define the complete sloodle structure for backup, with file and id annotations
+ */     
+class backup_sloodle_activity_structure_step extends backup_activity_structure_step {
+     
+    protected function define_structure() {
+ 
+        // To know if we are including userinfo
+        $userinfo = $this->get_setting_value('userinfo');
+
+        // Define each element separated
+        $sloodle = new backup_nested_element('sloodle', 
+            array('id'),  
+            array(
+                'course',
+                'type',
+                'name',
+                'intro',
+                'introformat',
+                'timecreated',
+                'timemodified'
+            )
+        );
+
+        $trackers = new backup_nested_element('trackers');
+        $tracker = new backup_nested_element('tracker',
+            array('id'),
+            array(
+                'sloodleid',
+            )
+        );
+
+
+        $distributors = new backup_nested_element('distributors');
+        $distributor = new backup_nested_element('distributor',
+            array('id'),
+            array(
+                'sloodleid',
+                'channel',
+                'timeupdated'
+            )
+        );
+
+        $presenters = new backup_nested_element('presenters');
+        $presenter = new backup_nested_element('presenter',
+            array('id'),
+            array(
+                'sloodleid',
+                'framewidth',
+                'frameheight'
+            )
+        );
+
+        $presenterentries = new backup_nested_element('presenter_entries');
+        $presenterentry = new backup_nested_element('presenter_entry',
+            array('id'),
+            array(
+                'sloodleid',
+                'name',
+                'source',
+                'type', 
+                'ordering'
+            )
+        );
+
+        $controllers = new backup_nested_element('controllers');
+        $controller = new backup_nested_element('controller',
+            array('id'),
+            array(
+                'sloodleid',
+                'enabled',
+                'password'
+            )
+        );
+
+        $layouts = new backup_nested_element('layouts');
+        $layout = new backup_nested_element('layout',
+            array('id'),
+            array(
+                   'course',
+                   'name',
+                   'timeupdated',
+                   'controllerid'
+            )
+        );
+
+        $layoutentries = new backup_nested_element('layout_entries');
+        $layoutentry = new backup_nested_element('layout_entry',
+            array('id'),
+            array(
+                'layout',
+                'name',
+                'position',
+                'rotation'
+            )
+        );
+
+        $layoutentryconfigs = new backup_nested_element('layout_entry_configs');
+        $layoutentryconfig = new backup_nested_element('layout_entry_config',
+            array('id'),
+            array(
+                'layout_entry',
+                'name',
+                'value'   
+            )
+        );        
+
+        $sloodlecurrencytypes = new backup_nested_element('currency_types');
+        $sloodlecurrencytype = new backup_nested_element('currency_type',
+            array('id'),
+            array(
+                'name',
+                'timemodified',
+                'imageurl',
+                'displayorder'
+            )
+        );
+
+        $sloodleusers = new backup_nested_element('users');
+        $sloodleuser = new backup_nested_element('user',
+            array('id'),
+            array(
+                'userid',
+                'uuid',
+                'avname',
+                'profilepic',
+                'lastactive'
+            )
+        );
+
+        $sloodle->add_child($controllers);
+        $controllers->add_child($controller);
+        $controller->add_child($layouts);
+        $layouts->add_child($layout);
+        $layout->add_child($layoutentries);
+        $layoutentries->add_child($layoutentry);
+
+        $layoutentry->add_child($layoutentryconfigs);
+        $layoutentryconfigs->add_child($layoutentryconfig);
+
+        $distributors->add_child($distributor);
+        $sloodle->add_child($distributors);
+
+        $trackers->add_child($tracker);
+        $sloodle->add_child($trackers);
+
+        $presenterentries->add_child($presenterentry);
+        $presenter->add_child($presenterentries);
+        $presenters->add_child($presenter);
+        $sloodle->add_child($presenters);
+
+
+        if ($userinfo) {
+            $sloodleusers->add_child($sloodleuser);
+            $sloodle->add_child($sloodleusers);
+        }
+
+        $sloodlecurrencytypes->add_child($sloodlecurrencytype);
+        $sloodle->add_child($sloodlecurrencytypes);
+
+        // Build the tree
+ 
+        // Define sources
+        $sloodle->set_source_table('sloodle', array('id' => backup::VAR_ACTIVITYID));
+
+        /*
+        Neither of these tables link directly to a course or controller.
+        However, they may be referenced by other things that are in a controller, so we can't leave them out.
+        There will need to be some restore code in the to do something sensible with these on load.
+        */
+        $sloodleuser->set_source_table('sloodle_users', array());
+        $sloodlecurrencytype->set_source_table('sloodle_currency_types', array());
+        //$sloodleuser->set_source_sql("SELECT * FROM {sloodle_users} ORDER BY id", array());
+
+
+        if ($userinfo) {
+        }
+        $distributor->set_source_table('sloodle_distributor', array('sloodleid' => backup::VAR_ACTIVITYID));
+        $tracker->set_source_table('sloodle_tracker', array('sloodleid' => backup::VAR_ACTIVITYID));
+        $presenter->set_source_table('sloodle_presenter', array('sloodleid' => backup::VAR_ACTIVITYID));
+        $presenterentry->set_source_table('sloodle_presenter_entry', array('sloodleid' => backup::VAR_ACTIVITYID));
+        $controller->set_source_table('sloodle_controller', array('sloodleid' => backup::VAR_ACTIVITYID));
+        $layout->set_source_table('sloodle_layout', array('controllerid' => backup::VAR_MODID));
+        $layoutentry->set_source_table('sloodle_layout_entry', array('layout' => backup::VAR_PARENTID));
+        $layoutentryconfig->set_source_table('sloodle_layout_entry_config', array('layout_entry' => backup::VAR_PARENTID));
+
+        /*
+        $layout->set_source_sql(
+            'SELECT * FROM {sloodle_layout} WHERE controllerid = ?',
+            array(backup::VAR_MODID)
+        );
+        */
+
+        // Define id annotations
+        $sloodleuser->annotate_ids('sloodle_user', 'userid');
+ 
+        // Define file annotations
+        //mod_sloodle | presenter |      8 
+        $presenter->annotate_files('mod_sloodle', 'presenter', null);
+ 
+        // Return the root element (sloodle), wrapped into standard activity structure
+        return $this->prepare_activity_structure($sloodle);
+ 
+    }
+
+}

+ 167 - 0
backup/moodle2/restore_sloodle_activity_task.class.php

@@ -0,0 +1,167 @@
+<?php
+require_once($CFG->dirroot . '/mod/sloodle/backup/moodle2/restore_sloodle_stepslib.php');
+/**
+ * sloodle restore task that provides all the settings and steps to perform one
+* complete restore of the activity
+*/
+class restore_sloodle_activity_task extends restore_activity_task {
+
+    /**
+    * Define (add) particular settings this activity can have
+    */
+    protected function define_my_settings() {
+    // No particular settings for this activity
+    }
+
+    /**
+    * Define (add) particular steps this activity can have
+    */
+    protected function define_my_steps() {
+        // Sloodle only has one structure step
+        $this->add_step(new restore_sloodle_activity_structure_step('sloodle_structure', 'sloodle.xml'));
+    }
+
+    /*
+    Hack alert, Edmund Edgar, 2012-06-23:
+    Presenter URLs need to be mapped back to the new site and context.
+    This is probably doable with these decode_rules, but I don't understand how they work
+    ...so I'm using after_restore instead.
+    If anyone understands this stuff, please go ahead and rewrite it to do it right.
+    */
+
+    /**
+    * Define the contents in the activity that must be
+    * processed by the link decoder
+    */
+    static public function define_decode_contents() {
+        $contents = array();
+
+        //$contents[] = new restore_decode_content('sloodle_presenter_entry', array('source'), 'sloodle_presenter_entry');
+
+        return $contents;
+    }
+
+    /**
+    * Define the decoding rules for links belonging
+    * to the activity to be executed by the link decoder
+    */
+    static public function define_decode_rules() {
+        $rules = array();
+
+        /*
+        $rules[] = new restore_decode_rule('CHOICEVIEWBYID', '/mod/choice/view.php?id=$1', 'course_module');
+        $rules[] = new restore_decode_rule('CHOICEINDEX', '/mod/choice/index.php?id=$1', 'course');
+        */
+
+        return $rules;
+
+    }
+
+    /**
+    * Define the restore log rules that will be applied
+    * by the {@link restore_logs_processor} when restoring
+    * choice logs. It must return one array
+    * of {@link restore_log_rule} objects
+    */
+    static public function define_restore_log_rules() {
+
+        $rules = array();
+
+        /*
+        $rules[] = new restore_log_rule('choice', 'add', 'view.php?id={course_module}', '{choice}');
+        $rules[] = new restore_log_rule('choice', 'update', 'view.php?id={course_module}', '{choice}');
+        $rules[] = new restore_log_rule('choice', 'view', 'view.php?id={course_module}', '{choice}');
+        $rules[] = new restore_log_rule('choice', 'choose', 'view.php?id={course_module}', '{choice}');
+        $rules[] = new restore_log_rule('choice', 'choose again', 'view.php?id={course_module}', '{choice}');
+        $rules[] = new restore_log_rule('choice', 'report', 'report.php?id={course_module}', '{choice}');
+        */
+
+        return $rules;
+    }
+
+    /**
+    * Define the restore log rules that will be applied
+    * by the {@link restore_logs_processor} when restoring
+    * course logs. It must return one array
+    * of {@link restore_log_rule} objects
+    *
+    * Note this rules are applied when restoring course logs
+    * by the restore final task, but are defined here at
+    * activity level. All them are rules not linked to any module instance (cmid = 0)
+    */
+    static public function define_restore_log_rules_for_course() {
+        $rules = array();
+
+        /*
+        // Fix old wrong uses (missing extension)
+        $rules[] = new restore_log_rule('choice', 'view all', 'index?id={course}', null,
+        null, null, 'index.php?id={course}');
+        $rules[] = new restore_log_rule('choice', 'view all', 'index.php?id={course}', null);
+        */
+
+        return $rules;
+    }
+ 
+    public function after_restore() {
+        // Get a list of inserted layout entries
+
+        global $DB;
+        global $CFG;
+
+        // Layout entry config may refer to an external course module, eg chatroom.
+        // If we've done a full course backup, we should be able to recover it.
+        // If we can't recover it, we'll set it to 0/null, and hope the UI can deal with that sensibly.
+        $layouts = $DB->get_records('sloodle_layout', array('controllerid' => $this->get_moduleid()));
+        if (count($layouts)) {
+            foreach($layouts as $layout) {
+                $layout_entries = $DB->get_records('sloodle_layout_entry', array('layout' => $layout->id)); 
+                if (count($layout_entries)) {
+                    foreach($layout_entries as $le) {
+
+                        $layout_entry_configs = $DB->get_records('sloodle_layout_entry_config', array('layout_entry' => $le->id, 'name'=>'sloodlemoduleid')); 
+                        foreach($layout_entry_configs as $lec) {
+                            if ($mapcm = restore_structure_step::get_mapping('course_module', $lec->value)) {
+                                $lec->value = $mapcm->newitemid;
+                                $DB->update_record('sloodle_layout_entry_config', $lec);
+                            } 
+                        }
+
+                        $layout_entry_configs = $DB->get_records('sloodle_layout_entry_config', array('layout_entry' => $le->id, 'name'=>'sloodlecurrencyid')); 
+                        foreach($layout_entry_configs as $lec) {
+                            if ($mapcm = restore_structure_step::get_mapping('sloodle_currency_types', $lec->value)) {
+                                $lec->value = $mapcm->newitemid;
+                                $DB->update_record('sloodle_layout_entry_config', $lec);
+                            }
+                        } 
+
+                    }
+                }
+            }
+        }
+
+        /*
+        This should probably really be done with decode_rules - see hack alert above.
+        */
+        $moduleid = $this->get_moduleid();
+        $sloodleid = $DB->get_field('course_modules', 'instance', array('id'=>$moduleid));
+        $contextid = $this->get_contextid();
+
+        $presenter_entries = $DB->get_records('sloodle_presenter_entry', array('sloodleid' => $sloodleid));
+        if (count($presenter_entries) > 0) {
+            foreach($presenter_entries as $pe) {
+                $source = $pe->source;
+                $base = preg_quote('$@SITEROOT@$', '#');
+                $contextidplaceholder = preg_quote('$@CONTEXTID@$', '#');
+                if (preg_match('#^'.$base.'(/pluginfile.php/)'.$contextidplaceholder.'(/mod_sloodle/presenter/.*)#', $source, $matches)) {
+                    $pe->source = $CFG->wwwroot.$matches[1].$contextid.$matches[2];
+                    $DB->update_record('sloodle_presenter_entry', $pe);
+                }
+            }
+        }
+        //$file_entries = $DB->get_records('mdl_files', array('contextid'=>$contextid, 'component'=>'mod_sloodle', 'filearea'=>'presenter'));
+        
+
+
+    }
+
+}

+ 289 - 0
backup/moodle2/restore_sloodle_stepslib.php

@@ -0,0 +1,289 @@
+<?php
+/**
+ * Structure step to restore one choice activity
+*/
+class restore_sloodle_activity_structure_step extends restore_activity_structure_step {
+       
+    protected function define_structure() {
+                    
+        $paths = array();
+        $userinfo = $this->get_setting_value('userinfo');
+
+        $paths[] = new restore_path_element('sloodle', '/activity/sloodle');
+
+        $paths[] = new restore_path_element('sloodle_currency_type', '/activity/sloodle/currency_types/currency_type');
+        if ($userinfo) {
+            $paths[] = new restore_path_element('sloodle_user', '/activity/sloodle/users/user');
+        }
+
+        $paths[] = new restore_path_element('sloodle_controller', '/activity/sloodle/controllers/controller');
+
+        $paths[] = new restore_path_element('sloodle_tracker', '/activity/sloodle/trackers/tracker');
+
+        $paths[] = new restore_path_element('sloodle_presenter', '/activity/sloodle/presenters/presenter');
+        $paths[] = new restore_path_element('sloodle_presenter_entry', '/activity/sloodle/presenters/presenter/presenter_entries/presenter_entry');
+
+        $paths[] = new restore_path_element('sloodle_distributor', '/activity/sloodle/distributors/distributor');
+        $paths[] = new restore_path_element('sloodle_distributor_entry', '/activity/sloodle/distributor/distributors/distributor_entries/distributor_entry');
+
+        $paths[] = new restore_path_element('sloodle_layout', '/activity/sloodle/controllers/controller/layouts/layout');
+        $paths[] = new restore_path_element('sloodle_layout_entry', '/activity/sloodle/controllers/controller/layouts/layout/layout_entries/layout_entry');
+        $paths[] = new restore_path_element('sloodle_layout_entry_config', '/activity/sloodle/controllers/controller/layouts/layout/layout_entries/layout_entry/layout_entry_configs/layout_entry_config');
+
+        /*
+        if ($userinfo) {
+            $paths[] = new restore_path_element('choice_answer', '/activity/choice/answers/answer');
+        }
+        */
+                                                                                      
+        // Return the paths wrapped into standard activity structure
+        return $this->prepare_activity_structure($paths);
+
+    }
+
+    protected function process_sloodle($data) {
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+        $data->course = $this->get_courseid();
+
+        $data->timecreated = $this->apply_date_offset($data->timecreated);
+        $data->timemodified = $this->apply_date_offset($data->timemodified);
+
+        // insert the choice record
+        $newitemid = $DB->insert_record('sloodle', $data);
+        // immediately after inserting "activity" record, call this
+        $this->apply_activity_instance($newitemid);
+
+    }
+
+    protected function process_sloodle_controller($data) {
+
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+
+        $data->sloodleid = $this->get_new_parentid('sloodle'); 
+
+        $newitemid = $DB->insert_record('sloodle_controller', $data);
+        // immediately after inserting "activity" record, call this
+
+        $this->set_mapping('sloodle_controller', $oldid, $newitemid);
+
+    }
+
+    protected function process_sloodle_tracker($data) {
+
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+
+        $data->sloodleid = $this->get_new_parentid('sloodle'); 
+
+        $newitemid = $DB->insert_record('sloodle_tracker', $data);
+        // immediately after inserting "activity" record, call this
+
+        $this->set_mapping('sloodle_tracker', $oldid, $newitemid);
+
+    }
+
+    protected function process_sloodle_presenter($data) {
+
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+
+        $data->sloodleid = $this->get_new_parentid('sloodle'); 
+
+        $newitemid = $DB->insert_record('sloodle_presenter', $data);
+        // immediately after inserting "activity" record, call this
+
+        $this->set_mapping('sloodle_presenter', $oldid, $newitemid);
+
+    }
+
+    protected function process_sloodle_presenter_entry($data) {
+
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+
+        $data->sloodleid = $this->get_new_parentid('sloodle'); 
+
+        $newitemid = $DB->insert_record('sloodle_presenter_entry', $data);
+        // immediately after inserting "activity" record, call this
+
+        $this->set_mapping('sloodle_presenter_entry', $oldid, $newitemid);
+
+    }
+
+    protected function process_sloodle_distributor($data) {
+
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+
+        $data->sloodleid = $this->get_new_parentid('sloodle'); 
+
+        $newitemid = $DB->insert_record('sloodle_distributor', $data);
+        // immediately after inserting "activity" record, call this
+
+        $this->set_mapping('sloodle_distributor', $oldid, $newitemid);
+
+    }
+
+    protected function process_sloodle_distributor_entry($data) {
+
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+
+        $data->sloodleid = $this->get_new_parentid('sloodle'); 
+
+        $newitemid = $DB->insert_record('sloodle_distributor_entry', $data);
+        // immediately after inserting "activity" record, call this
+
+        $this->set_mapping('sloodle_distributor_entry', $oldid, $newitemid);
+
+    }
+
+    protected function process_sloodle_layout($data) {
+
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+
+        $data->course = $this->get_courseid();
+        $data->controllerid = $this->task->get_moduleid();
+        //$data->controllerid = $this->get_moduleid();
+        //var_dump($this);
+        //exit;
+        //$data->controllerid = $this->get_new_parentid('sloodle'); // CHECK: Probably need to look for the course module ID instead
+        $data->timeupdated = $this->apply_date_offset($data->timeupdated);
+
+        $newitemid = $DB->insert_record('sloodle_layout', $data);
+        $this->set_mapping('sloodle_layout', $oldid, $newitemid);
+
+    }
+
+    protected function process_sloodle_user($data) {
+
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+
+        $newitemid = 0;
+
+        // If the avatar is already there, map the record ID to the existing user.
+        if ( $existing = $DB->get_record('sloodle_users', array( 'uuid' => $data->uuid ) ) ) {
+            $newitemid = $existing->id;
+        } else {
+            // If the Moodle user is already there, create an avatar for them.
+            $data->userid = $this->get_mappingid('user', $data->userid);
+            // Only import avatars if we know which user they are.
+            if ($data->userid > 0) {
+                $newitemid = $DB->insert_record('sloodle_users', $data);
+            }
+        }
+
+        $this->set_mapping('sloodle_user', $oldid, $newitemid);
+
+    }
+
+    protected function process_sloodle_currency_type($data) {
+
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+
+        $newitemid = 0;
+
+        // If the avatar is already there, map the record ID to the existing user.
+        if ( $existing = $DB->get_record('sloodle_currency_types', array( 'name' => $data->name) ) ) {
+            $newitemid = $existing->id;
+        } else {
+            $newitemid = $DB->insert_record('sloodle_currency_types', $data);
+        }
+
+        $this->set_mapping('sloodle_currency_types', $oldid, $newitemid);
+
+    }
+ 
+    protected function process_sloodle_layout_entry($data) {
+
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+
+        $data->layout = $this->get_new_parentid('sloodle_layout');
+
+        $newitemid = $DB->insert_record('sloodle_layout_entry', $data);
+        $this->set_mapping('sloodle_layout_entry', $oldid, $newitemid);
+
+    }
+
+    protected function process_sloodle_layout_entry_config($data) {
+
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+
+        $data->layout_entry = $this->get_new_parentid('sloodle_layout_entry');
+
+        $newitemid = $DB->insert_record('sloodle_layout_entry_config', $data);
+        $this->set_mapping('sloodle_layout_entry_config', $oldid, $newitemid);
+
+    }
+
+    protected function after_execute() {
+
+        global $DB;
+
+        // Add sloodle related files, no need to match by itemname (just internally handled context)
+        $this->add_related_files('mod_sloodle', 'presenter', null);
+
+        /*
+        Presenter files have the context ID in the path.
+        This needs to be replaced with the new context ID.
+        We'll get the files directly from the files table and fix any context IDs.
+        This is almost definitely the wrong way to do this, but I'm buggered if I know what the right one is.
+        */
+
+        $contextid = $this->task->get_contextid();
+
+        $files = $DB->get_records('files', array('contextid'=>$contextid, 'component'=>'mod_sloodle', 'filearea'=>'presenter'));
+
+        if (count($files) > 0) {
+
+            foreach($files as $fr) {
+
+                $fs = get_file_storage();
+
+                if (!preg_match('#^(/)\d+(/.*)$#', $fr->filepath, $matches)) {
+                    continue;
+                }
+
+                $fr->filepath = $matches[1].$contextid.$matches[2];
+
+                $fr->pathnamehash = $fs->get_pathname_hash($fr->contextid, $fr->component, $fr->filearea, $fr->itemid, $fr->filepath, $fr->filename);
+                $DB->update_record('files', $fr);
+
+            }
+
+        }
+
+    }
+
+}

+ 234 - 0
backuplib.php

@@ -0,0 +1,234 @@
+<?php
+
+    /**
+    * This script contains all the functionality allowing SLOODLE data in Moodle to be backed-up.
+    * The bulk of visible SLOODLE data resides in module instances.
+    * Each module has a "primary" record in the "sloodle" database table.
+    * However, there are several sub-types of modules, most of which have one or more "secondary" records elsewhere in the database.
+    * This file directly backs-up the primary data, but then relies on individual module code (in "lib/modules") to backup secondary data.
+    *
+    * The current 'active objects' (i.e. objects which are authorised to access the course when the backup is initiated) will NOT currently be backed-up.
+    * This is for security, since in the event that a course is being transfered it is not desirable to allow existing objects access to the new course.
+    * An option may be added in future to allow object authorisations to be transferred in a safe way.
+    *
+    * NOTE: despite the above restriction, any objects relying on a prim password will still have access, since the prim password does not change during backup.
+    *
+    * @package sloodle
+    * @todo Implement backup of site data, including avatar registrations
+    * @todo Implement backup fo course data, including autoreg/enrol settings, and object layouts/configurations
+    *
+    */
+    
+    require_once($CFG->dirroot.'/mod/sloodle/init.php');
+    require_once(SLOODLE_LIBROOT.'/modules.php');
+    require_once(SLOODLE_LIBROOT.'/sloodle_session.php');
+    
+    /**
+    * Backs-up multiple modules of SLOODLE data.
+    * @param $bf File handle for writing backup data to.
+    * @param $preferences Contains the options controlling this backup.
+    * @return bool True if successful or false on failure.
+    */
+    function sloodle_backup_mods($bf, $preferences)
+    {
+        $status = true;
+        
+        // Backup course-specific data
+        //...
+        
+        // Go through each SLOODLE module in the specified course.
+        $mods = sloodle_get_records('sloodle', 'course', $preferences->backup_course, 'id');
+        if ($mods === false) return false;
+        foreach ($mods as $mod) {
+            // Is this module due for backup?
+            if (backup_mod_selected($preferences, 'sloodle', $mod->id)) {
+                // Backup this particular module
+                if (!sloodle_backup_one_mod($bf, $preferences, $mod)) $status = false;
+            }
+        }
+        
+        return $status;
+    }
+ 
+    /**
+    * Backs-up one instance of a SLOODLE module.
+    * @param $bf Handle to the file to which backup data should be written.
+    * @param $preferences Structure defining preferences which govern the backup.
+    * @param object|int $mod If an object, then it is a record from the 'sloodle' db table. If it is a number, then it is the ID of a record in the 'sloodle' db table.
+    * @return bool True if successful, or false if not.
+    */
+    function sloodle_backup_one_mod($bf, $preferences, $mod)
+    {
+        // Load a 'sloodle' record if necessary
+        if (is_numeric($mod)) {
+            $mod = sloodle_get_record('sloodle', 'id', $mod);
+            if ($mod === false) return false;
+        }
+        
+        $status = true;
+        
+        // Attempt to load the course module record from the database
+        $cm = get_coursemodule_from_instance('sloodle', $mod->id);
+        if ($cm === false) return false;
+        
+        // Attempt to get a SloodleModule object for this module sub-type
+        $dummysession = new SloodleSession(false); // We need to provide this to keep the module happy!
+        $moduleobj = sloodle_load_module($mod->type, $dummysession, $cm->id);
+        if ($moduleobj == false) return false;
+        
+        // Start an element for this module instance, and backup the primary table data
+        fwrite($bf, start_tag('MOD', 3, true));
+        
+        fwrite($bf, full_tag('ID', 4, false, $mod->id));        // Instance ID of this SLOODLE module
+        fwrite($bf, full_tag('MODTYPE', 4, false, 'sloodle'));  // Main type of module (always 'sloodle' in this case)
+        fwrite($bf, full_tag('SUBTYPE', 4, false, $mod->type));       // Sub-type of module (e.g. 'controller' or 'presenter')
+        fwrite($bf, full_tag('NAME', 4, false, $mod->name));
+        fwrite($bf, full_tag('INTRO', 4, false, $mod->intro));
+        fwrite($bf, full_tag('TIMECREATED', 4, false, $mod->timecreated));
+        fwrite($bf, full_tag('TIMEMODIFIED', 4, false, $mod->timemodified));
+        
+        // Backup any secondary data
+        fwrite($bf, start_tag('SECONDARYDATA', 4, true));
+        if (!$moduleobj->backup($bf, backup_userdata_selected($preferences, 'sloodle', $mod->id))) $status = false;
+        fwrite($bf, end_tag('SECONDARYDATA', 4, true));
+        
+        // Finish off
+        if (!fwrite($bf, end_tag('MOD', 3, true))) $status = false;
+        
+        return $status;
+    }
+
+
+    /**
+    * Checks the specified course (or specific instances therein) for SLOODLE data that can be backed-up.
+    * @param $course Identifies the Moodle course to be backed-up.
+    * @param bool $user_data Indicates whether or not user data is to be included in the check.
+    * @param $backup_unique_code A unique code identifying this backup.
+    * @param array $instances Optional. An array of instances of SLOODLE modules which should be checked.
+    * @return Array of information that can be backed up.
+    */
+    function sloodle_check_backup_mods($course, $user_data, $backup_unique_code, $instances = null)
+    {
+        // Has information about specified instances been requested?
+        if (!empty($instances) && is_array($instances) && count($instances)) {
+            // Yes - construct an array of information about those instances.
+            $info = array();
+            foreach ($instances as $id => $instance) {
+                $info += sloodle_check_backup_mods_instances($instance, $backup_unique_code);
+            }
+            return $info;
+        }
+        
+        // We're getting backup information about the whole course.
+        
+        // Return structural information about the whole course
+        $info[0][0] = get_string('modulenameplural', 'sloodle');
+        $ids = sloodle_ids($course);
+        if ($ids) {
+            $info[0][1] = count($ids);
+        } else {
+            $info[0][1] = 0;
+        }
+        
+        // Now, if requested, the user_data
+        if ($user_data) {
+            $info[1][0] = get_string('userdata', 'sloodle');
+            $info[1][1] = 0;
+            // Go through each SLOODLE module in the course
+            if (is_array($ids)) {
+                foreach ($ids as $id) {
+                    // Attempt to load a module object for this instance
+                    $module = sloodle_quick_load_module_from_instance($id[0]);
+                    if ($module) $info[1][1] += $module->get_user_data_count();
+                }
+            }
+        }
+        
+        return $info;
+    }
+    
+    /**
+    * Check the backup infomation for a specific instance of the SLOODLE module.
+    * @param $instance An object representing a record from the 'sloodle' table.
+    * @param $backup_unique_code A unique code for this backup
+    * @return array Backup information
+    */
+    function sloodle_check_backup_mods_instances($instance, $backup_unique_code) {
+        // Add the course data
+        $info[$instance->id.'0'][0] = '<b>'.$instance->name.'</b>';
+        $info[$instance->id.'0'][1] = '';
+        
+        // Now, if requested, the user_data
+        if (!empty($instance->userdata)) {
+            $info[$instance->id.'1'][0] = get_string('userdata', 'sloodle');
+            $info[$instance->id.'1'][1] = 0;
+        
+            // Attempt to load the module data for this instance
+            $module = sloodle_quick_load_module_from_instance($instance->id);
+            if ($module) {
+                $info[$instance->id.'1'][0] = $module->get_user_data_name();
+                $info[$instance->id.'1'][1] = $module->get_user_data_count();
+            }
+        }
+        return $info;
+    }
+    
+    /**
+    * Return the given content, encoded to support interactivities linking.
+    * This is necessary so that any links between activities remain intact after backup, since the instance IDs change.
+    * Function is called automatically by Moodle backup functionality.
+    * @param string $content The content to be encoded.
+    * @param $preferences An object defining preferences in this encoding.
+    * @return string The content with encoded links.
+    */
+    function sloodle_encode_content_links ($content,$preferences)
+    {
+        global $CFG;
+        // Define the base link.
+        $base = preg_quote($CFG->wwwroot,"/");
+
+        // Link to the list of SLOODLE module instances
+        $buscar = "/(".$base."\/mod\/sloodle\/index.php\?id\=)([0-9]+)/";
+        $result = preg_replace($buscar,'$@SLOODLEINDEX*$2@$', $content);
+        
+        // Link to an implicit module view
+        $buscar = "/(".$base."\/mod\/sloodle\/view.php\?id\=)([0-9]+)/";
+        $result = preg_replace($buscar,'$@SLOODLEVIEWBYID*$2@$', $result);
+        
+        // Link to an explicit module view
+        $buscar = "/(".$base."\/mod\/sloodle\/view.php\?_type=module&id\=)([0-9]+)/";
+        $result = preg_replace($buscar,'$@SLOODLEVIEWBYID*$2@$', $result);
+        
+        // Link to an explicit module view with HTML entity for &
+        $buscar = "/(".$base."\/mod\/sloodle\/view.php\?_type=module&amp;id\=)([0-9]+)/";
+        $result = preg_replace($buscar,'$@SLOODLEVIEWBYID*$2@$', $result);
+        
+        // To pass through any view type, use the following line:
+        // $buscar = "/(".$base."\/mod\/sloodle\/view.php\?_type=[a-zA-Z0-9.-]+&amp;id\=)([0-9]+)/";
+        
+        // It is not known whether Moodle provides the ability to encode for the following view scripts in SLOODLE:
+        // - view_course.php
+        // - view.php?_type=course   (course settings... same as view_course.php)
+        // - view_layout.php
+
+        return $result;
+    }
+
+    // INTERNAL FUNCTIONS. BASED IN THE MOD STRUCTURE
+
+    /**
+    * Gets an array of SLOODLE module instance IDs.
+    * (Corresponding to the 'id' field of the 'sloodle' db table.)
+    * @param int $course ID number of the course get the instances from
+    * @return array An array of instance IDs of SLOODLE modules.
+    */
+    function sloodle_ids($course)
+    {
+        global $CFG;
+        return sloodle_get_records_sql_params("
+            SELECT s.id, s.course
+            FROM {$CFG->prefix}sloodle s
+            WHERE s.course = ?
+        ", array($course));
+    }
+?>

+ 52 - 0
classroom/active_object_ping_linker.php

@@ -0,0 +1,52 @@
+<?php
+    /**
+    * Sloodle active object ping.
+    * Allows active objects to notify the Sloodle installation that they are still 'alive'.
+    *
+    * @package sloodleclassroom
+    * @copyright Copyright (c) 2008 Sloodle (various contributors)
+    * @license http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
+    *
+    * @contributor Peter R. Bloomfield
+    *
+    */
+    
+    // The following parameters are required:
+    //
+    //  sloodlecontrollerid = the ID of the controller through which the current object may access Sloodle
+    //  sloodlepwd = the prim password or object-specific session key to authenticate access
+    //  sloodleobjuuid = the UUID of the object which is active
+    //
+    //
+    // If the check is successful, the status code will be 1.
+    // If the object was not found, status code -103 is returned.
+    //
+    // NOTE: using the "sloodleobjuuid" parameter allows an object to ping on behalf of another.
+    // However, the object must be authorised on the controller identified by "sloodlecontrollerid".
+    //
+    
+    
+    /** Lets Sloodle know we are in a linker script. */
+    define('SLOODLE_LINKER_SCRIPT', true);
+    
+    /** Grab the Sloodle/Moodle configuration. */
+    require_once('../init.php');
+    /** Include the Sloodle PHP API. */
+    require_once(SLOODLE_LIBROOT.'/sloodle_session.php');
+    
+    // Authenticate the request
+    $sloodle = new SloodleSession();
+
+    // The access record happens when we authenticate.
+    // If it fails, it should error out and exit.
+    $sloodle->authenticate_request();
+
+    
+    $sloodle->response->set_status_code(1);
+    $sloodle->response->set_status_descriptor('OK');
+    $sloodle->response->set_refresh_seconds(SLOODLE_PING_INTERVAL);
+
+    
+    // Output the response
+    $sloodle->response->render_to_output();
+?>

+ 56 - 0
classroom/auth_check_linker.php

@@ -0,0 +1,56 @@
+<?php
+    /**
+    * Sloodle object authorization check linker.
+    * Allows authorised objects in SL to check whether or not a specified user can authorize objects.
+    *
+    * @package sloodleclassroom
+    * @copyright Copyright (c) 2008 Sloodle (various contributors)
+    * @license http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
+    *
+    * @contributor Peter R. Bloomfield
+    *
+    */
+    
+    // The following parameters are required:
+    //
+    //  sloodlecontrollerid = the ID of the controller through which the current object may access Sloodle
+    //  sloodlepwd = the prim password or object-specific session key to authenticate access
+    //  sloodleuuid = the UUID of the agent requesting object authorisation
+    //  sloodleavname = the name of the avatar requesting object authorisation
+    //
+    //
+    // If the check is successful, the status code will be 1.
+    // The data line will contain a 1 to indicate if the user can authorise objects on this course, or 0 otherwise.
+    //
+    
+    
+    /** Lets Sloodle know we are in a linker script. */
+    define('SLOODLE_LINKER_SCRIPT', true);
+    
+    /** Grab the Sloodle/Moodle configuration. */
+    require_once('../init.php');
+    /** Include the Sloodle PHP API. */
+    require_once(SLOODLE_LIBROOT.'/sloodle_session.php');
+    
+    // Authenticate the request and the user, but do not allow autoreg/autoenrol
+    $sloodle = new SloodleSession();
+    $sloodle->authenticate_request();
+    $sloodle->validate_user(true, true, true);
+    $sloodle->user->login();
+    
+    $can_authorize = false;
+    
+// MOODLE-SPECIFIC //
+    // Check that the user has the Sloodle capability on this course
+    $course_context = get_context_instance(CONTEXT_COURSE, $sloodle->course->get_course_id());
+    $can_authorize = has_capability('mod/sloodle:objectauth' $course_context);
+    
+// END MOODLE-SPECIFIC //
+    
+    // Render the output
+    $sloodle->response->set_status_code(1);
+    $sloodle->response->set_status_descriptor('OK');
+    if ($can_authorize) $sloodle->response->add_data_line('1');
+    else $sloodle->response->add_data_line('0');
+    $sloodle->response->render_to_output();
+?>

+ 143 - 0
classroom/auth_object_linker.php

@@ -0,0 +1,143 @@
+<?php
+    /**
+    * Sloodle object authorization linker.
+    * Allows authorised objects in SL to delegate authorisation to other objects,
+    *  or allows new objects in SL to initiate their own authorisation.
+    * (Creates a new entry in the 'sloodle_active_object' DB table.)
+    *
+    * @package sloodleclassroom
+    * @copyright Copyright (c) 2007-8 Sloodle (various contributors)
+    * @license http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
+    *
+    * @contributor Edmund Edgar
+    * @contributor Peter R. Bloomfield
+    *
+    */
+    
+    // If fully authorising a new object ('delegating' trust),
+    //  then the following parameters are required:
+    //
+    //  sloodlecontrollerid = the ID of the controller through which the current object may access Sloodle
+    //  sloodlepwd = the prim password or object-specific session key to authenticate access
+    //  sloodleobjuuid = the UUID of the object being authorised
+    //  sloodleobjname = the name of the object being authorised
+    //  sloodleobjpwd = a password for the new object
+    //  sloodlehttpinurl = an http-in url we can use to talk to the new object
+    //
+    // The following parameters are optional:
+    //
+    //  sloodleobjtype = the type identifier for the object being authorised. Can be overridden later.
+    //
+    // With the above information, a new entry is made, indicating that the object is fully authorised.
+    // The new object can ONLY be authorised against the controller the request is received on.
+    // If successful, the status code returned is 1, and the data line will contain the authorisation ID of the object which has been authorised.
+    
+    // If an object needs the user to perform web-authorisation, then it can create an unauthorised entry.
+    // To do this, the following parameters are required:
+    //
+    //  sloodleobjuuid = the UUID of the object being authorised
+    //  sloodleobjname = the name of the object being authorised
+    //  sloodleobjpwd = a new password for the object (NOT including its UUID)
+    //
+    // The following parameter is optional:
+    //
+    //  sloodleobjtype = the type identifier for the object. Can be overridden later.
+    //
+    // With this information, a new entry is made which is not linked to a particular user account.
+    // As such, the entry is deemed 'unauthorised' and cannot be used until authorised.
+    // If successful, status code 1 is returned, and the ID of the active object entry is returned on the data line.
+    // The object should use this to build a URL to send the user to Sloodle for manual object authorisation.
+    // Unauthorised entries will expire within 5 minutes and be deleted.
+    
+    
+    /** Lets Sloodle know we are in a linker script. */
+    define('SLOODLE_LINKER_SCRIPT', true);
+    
+    /** Grab the Sloodle/Moodle configuration. */
+    require_once('../init.php');
+    /** Include the Sloodle PHP API. */
+    require_once(SLOODLE_LIBROOT.'/sloodle_session.php');
+    
+    // Attempt to authenticate the request
+    // (only require authentication if controller ID and/or password is set)
+    $authrequired = (isset($_REQUEST['sloodlecontrollerid']) || isset($_REQUEST['sloodlepwd']));
+    $sloodle = new SloodleSession();
+    $request_auth = $sloodle->authenticate_request($authrequired);
+    
+    // Get the extra parameters
+    $sloodleobjuuid = $sloodle->request->required_param('sloodleobjuuid');
+    $sloodleobjname = $sloodle->request->required_param('sloodleobjname');
+    $sloodleobjpwd = $sloodle->request->required_param('sloodleobjpwd');
+    $sloodleobjtype = $sloodle->request->optional_param('sloodleobjtype', '');
+    $sloodlecloneconfig = $sloodle->request->optional_param('sloodlecloneconfig', ''); // uuid of an object whose config we want to clone. combined with a layout id of 0. used for rezzing a mothership from a set
+    $sloodlehttpinurl = $sloodle->request->optional_param('sloodlehttpinurl','');
+
+    // When the set rezzes an item from a layout, it can pass this parameter saying what layout entry the object represented.
+    // We'll use that to auto-configure the object based on the layout entry configurations.
+    $sloodlelayoutentryid = $sloodle->request->optional_param('sloodlelayoutentryid',-1,PARAM_INT);
+    
+    // If the request was authenticated, then the object is being fully authorised.
+    // Otherwise, it is simply a 'pending' authorisation.
+    if ($request_auth) {
+        // If the request is coming from an authorised object, then use that user as the authoriser for this one
+        $sloodlepwd = $sloodle->request->required_param('sloodlepwd');
+        $pwdparts = explode('|', $sloodlepwd, 2);
+        if (count($pwdparts) >= 2 && strlen($pwdparts[0]) == 36) { // Do we have a UUID?
+            $userid = $sloodle->course->controller->get_authorizing_user($pwdparts[0]);
+            if ($userid) $sloodle->user->load_user($userid);
+        }
+
+	    $httpinpassword = sloodle_random_prim_password();
+        
+        // Authorise the object on the controller
+        $authid = $sloodle->course->controller->register_object($sloodleobjuuid, $sloodleobjname, $sloodle->user, $sloodleobjpwd, $httpinpassword, $sloodleobjtype);
+        $alreadyconfigured = "0";
+        if ($sloodlelayoutentryid > 0) {
+            if ($sloodle->course->controller->configure_object_from_layout_entry($authid, $sloodlelayoutentryid)) {
+                // This flag will tell the rezzer to tell the object that it's already configured
+                // That way the object will know not to tell the user to configure it.
+                $alreadyconfigured = "1";
+            }
+        } else if ( ($sloodlelayoutentryid == 0) && ($sloodlecloneconfig != '') ) { // use 0 to mean we want to configure based on the parent who authorized us, rather than on a layout. Doing this to make the mothership worked when rezzed by a Sloodle Set, but we may want to do the same kind of thing with Registration Booths etc.
+            if ($result = $sloodle->course->controller->configure_object_from_parent($authid, $sloodlecloneconfig)) {
+                // This flag will tell the rezzer to tell the object that it's already configured
+                // That way the object will know not to tell the user to configure it.
+$alreadyconfigured = $result;
+                $alreadyconfigured = "1";
+            } else {
+                $alreadyconfigured = "0";
+            }
+
+	}
+        if ($authid) {
+            $sloodle->response->set_status_code(1);
+            $sloodle->response->set_status_descriptor('OK');
+            $sloodle->response->add_data_line($authid);
+            $sloodle->response->add_data_line($alreadyconfigured);
+        } else {
+            $sloodle->response->set_status_code(-201);
+            $sloodle->response->set_status_descriptor('OBJECT_AUTH');
+            $sloodle->response->add_data_line('Failed to register new active object.');
+        }
+    } else {
+        // Create a new unauthorised entry
+	    $httpinpassword = sloodle_random_prim_password();
+        $authid = $sloodle->course->controller->register_unauth_object($sloodleobjuuid, $sloodleobjname, $sloodleobjpwd, $sloodleobjtype, null, $sloodlehttpinurl, $httpinpassword);
+        if ($authid != 0) {
+            $sloodle->response->set_status_code(1);
+            $sloodle->response->set_status_descriptor('OK');
+            $sloodle->response->add_data_line($authid);
+            $sloodle->response->add_data_line($alreadyconfigured="0");
+        } else {
+            $sloodle->response->set_status_code(-201);
+            $sloodle->response->set_status_descriptor('OBJECT_AUTH');
+            $sloodle->response->add_data_line('Failed to register new active object.');
+        }
+    }
+    
+    // Render the output
+    sloodle_debug('<pre>');
+    $sloodle->response->render_to_output();
+    sloodle_debug('</pre>');
+
+?>

+ 329 - 0
classroom/configure_object.php

@@ -0,0 +1,329 @@
+<?php
+    /**
+    * Sloodle object configuration/authorization page.
+    *
+    * Allows users in Moodle to authorize or deny in-world objects' access to Moodle,
+    *  and to select the configuration settings.
+    *
+    * @package sloodleclassroom
+    * @copyright Copyright (c) 2007-8 Sloodle (various contributors)
+    * @license http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
+    *
+    * @contributor Edmund Edgar
+    * @contributor Peter R. Bloomfield
+    *
+    */
+    
+    // Initially, the script should be accessed with a single parameter:
+    //
+    //  sloodleauthid = the integer ID of an (unauthorised) entry in the Sloodle 'active objects' table
+    //
+    // When the user clicks "yes" or "no" to confirm or deny the object's authorisation,
+    //  the following additional parameters will be added:
+    //
+    //  sloodlecontrollerid = the ID of the controller which is to be used
+    //  action = either 'auth' or 'cancel'
+    //
+    // If the action is 'confirm' then the object is authorised. Otherwise, its entry is deleted.
+    //
+    // The following parameter will usually also need to be specified:
+    //
+    //  sloodleobjtype = gives the type identifier of the object (not necessary if type is already registered)
+    
+    /** Grab the Sloodle/Moodle configuration. */
+    require_once('../init.php');
+    /** Include the Sloodle PHP API. */
+    require_once(SLOODLE_LIBROOT.'/sloodle_session.php');
+    require_once(SLOODLE_LIBROOT.'/general.php');
+    
+    // Make sure the user is logged-in
+    sloodle_require_login_no_guest();
+    
+    // We need to know which object is being authorised and/or configured
+    $sloodleauthid = required_param('sloodleauthid', PARAM_INT);
+    $auth_obj = SloodleController::get_object($sloodleauthid);
+    if (!$auth_obj) {
+        error(get_string('objectauthnotfound','sloodle'));
+        exit();
+    }
+    // Is the object already authorised?
+    $object_authorised = $auth_obj->course->controller->is_loaded();
+    
+    // Get the object type (only necessary if it was not already in the database)
+    $sloodleobjtype = null;
+    if (empty($auth_obj->type)) {
+        $sloodleobjtype = required_param('sloodleobjtype', PARAM_TEXT);
+    } else {
+        $sloodleobjtype = optional_param('sloodleobjtype', null, PARAM_TEXT);
+    }
+    
+    // Treat this like a Sloodle session, but do not authenticate anything
+    $sloodle = new SloodleSession();
+    // Was a controller loaded by the request?
+    $loaded_controller = false;
+    if ($sloodle->course->controller->is_loaded()) {
+        $loaded_controller = true;
+    } else {
+        // If a controller was already specified for the object, then use that
+        if ($auth_obj->course->controller->is_loaded()) {
+            $loaded_controller = true;
+            $sloodle->course = $auth_obj->course;
+        }
+    }
+    
+    // Load the user
+    $sloodle->user->load_user($USER->id);
+    $sloodle->user->load_linked_avatar();
+    
+    // Fetch localization strings
+    $stryes = get_string('yes');
+    $strno = get_string('no');
+    
+    // Check if the confirmation action has been specified
+    $action = optional_param('action', '', PARAM_TEXT);
+    // If the action was not recognised, then ignore it
+    if ($action != 'auth' && $action != 'cancel') $action = '';
+    
+    // Determine the page name - it should be authorisation or configuration
+    $pagename = get_string('objectauth','sloodle');
+    if ($object_authorised == true || $action == 'auth') $pagename = get_string('objectconfiguration','sloodle');
+    
+    // Construct a breadcrumb navigation menu
+    $nav = get_string('modulename', 'sloodle').' -> ';
+    $nav .= $pagename;
+    // Display the page header
+    sloodle_print_header($pagename, $pagename, $nav, '', '', false);
+    
+    
+//------------------------------------------------------------
+// AUTHORISATION STEP
+    
+    // Was an action specified? (Don't do this if the object was already authorised)
+    $action_success = false;
+    if (!empty($action) && $object_authorised == false) {
+        // Check if we were instructed to authorise, and also that we have a controller to authorise on
+        if ($action == 'auth' && $loaded_controller == true) {
+            // Make sure the user is allowed to authorise objects
+            $module_context = get_context_instance(CONTEXT_MODULE, $sloodle->course->controller->get_id());
+            require_capability('mod/sloodle:objectauth', $module_context);
+        
+            // Indicate that we are attempting authorisation
+            echo '<div style="text-align:center;">';
+            echo get_string('authorizingfor', 'sloodle').$sloodle->course->get_full_name().' &gt; '.$sloodle->course->controller->get_name().'<br><br>';
+        
+            // Attempt to authorise the entry
+            if ($sloodle->course->controller->authorise_object($auth_obj->uuid, $sloodle->user, $sloodleobjtype)) {
+                echo '<span style="font-weight:bold; color:green;">'.get_string('objectauthsuccessful','sloodle').'</span><br>';
+                $action_success = true;
+                $object_authorised = true;
+                // Reload the object data
+                $auth_obj = SloodleController::get_object($sloodleauthid);
+                
+            } else {
+                echo '<span style="font-weight:bold; color:red;">'.get_string('objectauthfailed','sloodle').'</span><br>';
+            }
+            
+            echo "</div><br>\n";
+            
+        } else if ($action == 'cancel') {
+            // Attempt to delete the entry
+            $sloodle->course->controller->remove_object($sloodleauthid);
+            // Display the result
+            sloodle_print_box(get_string('objectauthcancelled', 'sloodle'), 'generalbox boxaligncenter boxwidthnarrow');
+            $action_success = true;
+        }
+    } else if ($object_authorised) {
+        // If the object has already been authorised, then display a message about it
+        sloodle_print_box_start('generalbox boxwidthnarrow boxaligncenter');
+        echo '<span style="color:red; font-weight:bold; text-align:center;">'.get_string('objectauthalready','sloodle').'</span>';
+        sloodle_print_box_end();
+    }
+    
+//------------------------------------------------------------
+// OBJECT INFO
+    
+    // Display the information about the object
+    sloodle_print_box_start('generalbox boxaligncenter boxwidthnarrow');
+    echo '<div style="text-align:center;">';
+    echo '<span style="font-weight:bold; font-size:110%;">'.get_string('objectdetails','sloodle').'</span><br>';
+    
+    echo get_string('objectname','sloodle').': '.$auth_obj->name.'<br>';
+    echo get_string('objectuuid','sloodle').': '.$auth_obj->uuid.'<br>';
+    echo get_string('objecttype','sloodle').': ';
+    if (!empty($sloodleobjtype)) echo $sloodleobjtype.'<br>';
+    else if (!empty($auth_obj->type)) echo $auth_obj->type.'<br>';
+    else echo '-<br>';
+    
+    echo '</div>';
+    sloodle_print_box_end();
+    echo '<br>';
+    
+//------------------------------------------------------------
+// AUTHORISATION FORM
+    
+    // If the action was not performed or successful, then display the authorisation form
+    if ($action_success == false && $object_authorised == false) {
+        sloodle_print_box_start('generalbox boxaligncenter boxwidthnormal');
+        echo '<div style="text-align:center;">';
+        echo '<h3>'.get_string('objectauth','sloodle').'</h3>';
+
+        // If an authorisation action was attempted but the controller failed to load, then display an error message
+        if ($action == 'auth' && $loaded_controller = false) {
+            echo '<span style="font-weight:bold;color:red;">'.get_string('failedauth-trydifferent','sloodle').'</span>';
+        }
+        
+        // Get a list of controllers which the user is permitted to authorise objects on
+        // (It will be a 2d array - top level will be course ID's, 2nd level will be coursemodule ID's, containing database objects from the 'sloodle' table)
+        $controllers = array();
+        $recs = sloodle_get_records('sloodle', 'type', SLOODLE_TYPE_CTRL);
+        // Make sure we have at least one controller
+        if ($recs == false || count($recs) == 0) {
+            error(get_string('objectauthnocontrollers','sloodle'));
+            exit();
+        }
+        foreach ($recs as $r) {
+            // Fetch the course module
+            $cm = get_coursemodule_from_instance('sloodle', $r->id);
+            // Check that the person can authorise objects of this module
+            if (has_capability('mod/sloodle:objectauth', get_context_instance(CONTEXT_MODULE, $cm->id))) {
+                // Store this controller
+                $controllers[$cm->course][$cm->id] = $r;
+            }
+        }
+        
+        // Make sure that permission came through on at least one controller
+        if (count($controllers) == 0) {
+            error(get_string('objectauthnopermission','sloodle'));
+            exit();
+        }
+        
+        // Construct the list of course names
+        $coursenames = array();
+        foreach ($controllers as $cid => $ctrl) {
+            $courserec = sloodle_get_record('course', 'id', $cid, '','', '','', 'id,shortname,fullname');
+            if (!$courserec) $coursenames[$cid] = "(unknown)";
+            else $coursenames[$cid] = $courserec->fullname;
+        }
+        
+        // Sort the list alphabetically
+        natcasesort($coursenames);
+        
+        // Attempt to get our current controller, if possible, so we can pre-select it in the list
+        $cur_controller_id = 0;
+        if ($loaded_controller) {
+            $cur_controller_id = $sloodle->course->controller->get_id();
+        }
+        
+        // Display an authorisation form
+        echo '<form action="" method="GET">';
+        if (SLOODLE_DEBUG) echo '<input type="hidden" name="sloodledebug" value="true"/>';
+        if (!empty($sloodleauthid)) echo '<input type="hidden" name="sloodleauthid" value="'.$sloodleauthid.'"/>';
+        if (!empty($sloodleobjtype)) echo '<input type="hidden" name="sloodleobjtype" value="'.$sloodleobjtype.'"/>';
+        
+        // Ask the user to select a controller to authorise on
+        echo get_string('selectcontroller','sloodle').':<br><br>';
+        echo '<select name="sloodlecontrollerid" size="6">';
+        // Go through each course
+        foreach ($coursenames as $cid => $cname) { // We want to use the alphabetic sorting; get the course ID and name
+            // Output a disabled entry to act as a course header
+            echo "<option disabled=\"true\" style=\"font-weight:bold; font-style:italic; background-color:#eeeeee;\">{$cid}. $cname</option>\n";
+            
+            // Go through each controller in this course
+            foreach ($controllers[$cid] as $cmid => $ctrl) { // Get each controller in the course; get the course module ID and controller data
+                echo "<option ";
+                if ($cmid == $cur_controller_id) echo 'selected="true" ';
+                echo "style=\"margin-left:8px;\" value=\"$cmid\">{$ctrl->name}</option>\n";
+            }
+        }
+        echo '</select><br><br>';
+        
+        // Ask the user if they want to confirm or deny the authorisation
+        echo get_string('confirmobjectauth', 'sloodle').' ';
+        echo '<select name="action" size="1">';
+        echo '<option style="color:green; font-weight:bold;" value="auth" selected="true">'.$stryes.'</option>';
+        echo '<option style="color:red; font-weight:bold;" value="cancel">'.$strno.'</option>';
+        echo '</select><br><br>';
+        
+        echo '<input type="submit" value="'.get_string('submit','sloodle').'"/>';
+        echo "</form>\n";
+        
+        echo '</div>';
+        sloodle_print_box_end();
+    }
+    
+    
+//------------------------------------------------------------
+// CONFIGURATION FORM
+
+    // Get the type
+    $usetype = "";
+    if (!empty($auth_obj->type)) $usetype = $auth_obj->type;
+    else $usetype = $sloodleobjtype;    
+    
+
+    // If the object is now authorised, we can display its configuration form
+    if ($object_authorised && !empty($usetype)) {
+        // Make sure the user has permission to manage activities on this course
+        $course_context = get_context_instance(CONTEXT_COURSE, $auth_obj->course->get_course_id());
+        require_capability('moodle/course:manageactivities', $course_context);
+        
+        // Make sure the type value is stored in the database too
+        $sloodle->course->controller->update_object_type($auth_obj->uuid, $usetype);
+	$auth_obj->type = $usetype;
+    
+        // Make sure the type exists
+        $objectpath = SLOODLE_DIRROOT."/mod/$usetype";
+        if (!file_exists($objectpath)) error(get_string('objectnotinstalled','sloodle'));
+        // Determine if we have a custom configuration page
+        $hascustomconfig = $auth_obj->has_custom_config();
+        
+        // Display the configuration section
+        sloodle_print_box_start('generalbox boxwidthnormal boxaligncenter');
+        echo '<div style="text-align:center;"><h2>'.get_string('objectconfiguration','sloodle').'</h2>';
+        
+        // Do we have custom configuration settings?
+        if ($hascustomconfig) {
+            // Display our configuration form
+            echo '<form action="'.SLOODLE_WWWROOT.'/classroom/store_object_config.php" method="POST">';
+            // Add a hidden field to store the object's type
+            echo '<input type="hidden" name="sloodleobjtype" value="'.$usetype.'"/>';
+            
+            
+            // Include the form elements
+            include('object_configuration_form_template.php');
+
+            
+            
+            // Add this object's authorisation ID, and a submit button
+            echo '<br><input type="hidden" name="sloodleauthid" value="'.$sloodleauthid.'"/>';
+            if (SLOODLE_DEBUG) echo '<input type="hidden" name="sloodledebug" value="true"/>';
+            echo '<input type="submit" value="'.get_string('submit','sloodle').'"/>';
+            echo '</form>';
+            
+        } else {
+            // No configuration settings for this object
+            print_string('noobjectconfig','sloodle');
+            
+            // Add or udpate a configuration value to store this object's type
+            $cfgtype = sloodle_get_record('sloodle_object_config', 'object', $sloodleauthid, 'name', 'sloodleobjtype');
+            if (!$cfgtype) {
+                $cfgtype = new stdClass();
+                $cfgtype->object = $sloodleauthid;
+                $cfgtype->name = 'sloodleobjtype';
+                $cfgtype->value = $usetype;
+                sloodle_insert_record('sloodle_object_config', $cfgtype);
+            } else {
+                $cfgtype->value =  $usetype;
+                sloodle_update_record('sloodle_object_config', $cfgtype);
+            }
+        }
+        
+        echo '</div>';
+        sloodle_print_box_end();
+    }
+    
+    
+    // Finish
+    sloodle_print_footer();
+
+?>

+ 95 - 0
classroom/confirm_active_objects.php

@@ -0,0 +1,95 @@
+<?php
+    /**
+    * Sloodle active object confirm.
+    * Allows the rezzer to check for any objects that are no longer there, because they have been manually derezzed.
+    * With sloodlecmd=requestconfirmable, expects a list of uuids of objects that haven't checked in recently.
+    * With sloodlecmd=reportdisappeared, supplies a list of uuids of objects that have been found to be absent.
+    * If an object is found to be absent, its rezzer uuid will be removed.
+    * The object is left in the active object table in case it has been taken into inventory rather than deleted.
+    *
+    * @package sloodleclassroom
+    * @copyright Copyright (c) 2008 Sloodle (various contributors)
+    * @license http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
+    *
+    * @contributor Peter R. Bloomfield
+    * @contributor Edmund Edgar
+    *
+    */
+    
+    // The following parameters are required:
+    //
+    //  sloodlecontrollerid = the ID of the controller through which the current object may access Sloodle
+    //  sloodlepwd = the prim password or object-specific session key to authenticate access
+    //  sloodleobjuuid = the UUID of the object which is active
+    //  sloodlecmd = the task - either requestconfirmable or reportdisappeared
+    //
+    // If the check is successful, the status code will be 1.
+    // If the object was not found, status code -103 is returned.
+    //
+    //
+    
+    /** Lets Sloodle know we are in a linker script. */
+    define('SLOODLE_LINKER_SCRIPT', true);
+    
+    /** Grab the Sloodle/Moodle configuration. */
+    require_once('../init.php');
+    /** Include the Sloodle PHP API. */
+    require_once(SLOODLE_LIBROOT.'/sloodle_session.php');
+
+    $task = required_param('sloodlecmd', PARAM_TEXT);
+    
+    // Authenticate the request
+    $sloodle = new SloodleSession();
+
+    // The access record happens when we authenticate.
+    // If it fails, it should error out and exit.
+    $sloodle->authenticate_request();
+
+    $rezzer = $sloodle->active_object;
+
+    if ($task == 'requestconfirmable') {
+
+        $uuids = $rezzer->uuids_of_children_missing_since( 0, time() + 900 );
+        
+        $sloodle->response->set_status_code(1);
+        $sloodle->response->set_status_descriptor('OK');
+        $sloodle->response->set_refresh_seconds(SLOODLE_REZZER_STATUS_CONFIRM_INTERVAL);
+           
+
+        if (count($uuids) > 0) {
+            foreach($uuids as $uuid) {
+                $sloodle->response->add_data_line(array($uuid));
+            }
+        }
+
+    } else {
+
+       $uuidstr = required_param('sloodlemissinguuids', PARAM_TEXT);
+       if ($uuidstr != '') {
+           $uuids = explode('|', $uuidstr);
+           if (count($uuids) > 0) {
+               foreach($uuids as $uuid) {
+                   if ($uuid == '') {
+                       continue;
+                   }
+                   $ao = new SloodleActiveObject();
+                   if ( $ao->loadByUUID( $uuid ) ) {
+                       if ($ao->rezzeruuid == $rezzer->uuid) {
+                           $ao->rezzeruuid = '';
+                           $ao->save();
+                       }
+                   }
+               }
+           }
+
+       }
+
+        $sloodle->response->set_status_code(1);
+        $sloodle->response->set_status_descriptor('OK');
+
+    }
+    
+    // Output the response
+    $sloodle->response->render_to_output();
+
+?>

+ 63 - 0
classroom/course_info_linker.php

@@ -0,0 +1,63 @@
+<?php
+    /**
+    * Course information linker.
+    * Allows objects in-world to query for information about a particular course.
+    *
+    * @package sloodleclassroom
+    * @copyright Copyright (c) 2008 Sloodle (various contributors)
+    * @license http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
+    *
+    * @contributor Peter R. Bloomfield
+    *
+    */
+    
+    // The following parameters are required:
+    //
+    //  sloodlecontrollerid = the ID of the controller through which the current object may access Sloodle
+    //  sloodlepwd = the prim password or object-specific session key to authenticate access
+    //
+    // If everything is OK, the script will return status code, with the following information on the data lines:
+    //  coursename_short|coursename_full
+    //  autoreg_enabled|autoenrol_enabled
+    //
+    // The information will relate to whatever course the accessed controller belongs to.
+    // (This is for security, to ensure course data cannot be retrieved unauthorised).
+    // The autoreg and autoenrol values will be 0 or 1, indicate whether each feature is disabled or enabled on the course.
+    
+    
+    /** Lets Sloodle know we are in a linker script. */
+    define('SLOODLE_LINKER_SCRIPT', true);
+    
+    /** Grab the Sloodle/Moodle configuration. */
+    require_once('../init.php');
+    /** Include the Sloodle PHP API. */
+    require_once(SLOODLE_LIBROOT.'/sloodle_session.php');
+    
+    // Authenticate the request
+    $sloodle = new SloodleSession();
+    $sloodle->authenticate_request();
+    
+    // Make sure Sloodle is actually installed
+    $moduleinfo = sloodle_get_record('modules', 'name', 'sloodle');
+    if (!$moduleinfo) {
+        sloodle_debug('ERROR: Sloodle not installed<br/>');
+        $sloodle->response->quick_output(-106, 'SYSTEM', 'The Sloodle module is not installed on this Moodle site.', false);
+        exit();
+    }
+    
+    // Check out autoreg and autoenrol settings
+    $autoreg = '0';
+    $autoenrol = '0';
+    if ($sloodle->course->check_autoreg()) $autoreg = '1';
+    if ($sloodle->course->check_autoenrol()) $autoenrol = '1';
+    
+    // Prepare the output
+    $sloodle->response->set_status_code(1);
+    $sloodle->response->set_status_descriptor('OK');
+    $sloodle->response->add_data_line(array($sloodle->course->get_short_name(), $sloodle->course->get_full_name()));
+    $sloodle->response->add_data_line(array($autoreg, $autoenrol));
+
+    // Render the output
+    $sloodle->response->render_to_output();
+    exit();
+?>

+ 152 - 0
classroom/httpin_config_linker.php

@@ -0,0 +1,152 @@
+<?php
+/*  Released under the GNU GPL 3.0
+*  This script can be used in your scripts, but you must include this copyright header as per the GPL Licence
+*  For more information about GPL 3.0 - see: http://www.gnu.org/copyleft/gpl.html
+*  This script is part of the SLOODLE Project see http://sloodle.org
+*
+*  sloodle/classroom/httpin_config_linker.php
+*
+*  Copyright:
+*  @contributers Paul G. Preibisch (Fire Centaur in SL)
+*  @contributers Edmund Edgar
+*
+*  The purpose of this file is to receive a message from a rezzer which is passing an httpinurl for a child object requesting its config.
+*  this file will search for the child object in active_object table, save its httpin which was sent, and then pass it its config using curl 
+* 
+*/ 
+/** Lets Sloodle know we are in a linker script. */
+define('SLOODLE_LINKER_SCRIPT', true);
+
+/** Grab the Sloodle/Moodle configuration. */
+require_once('../init.php');
+/** Include the Sloodle PHP API. */
+require_once(SLOODLE_LIBROOT.'/sloodle_session.php');
+require_once(SLOODLE_LIBROOT.'/general.php');
+
+// Attempt to authenticate the request
+// (only require authentication if controller ID and/or password is set)
+$authrequired = (isset($_REQUEST['sloodlecontrollerid']) || isset($_REQUEST['sloodlepwd']));
+$sloodle = new SloodleSession();
+$request_auth = $sloodle->authenticate_request($authrequired);
+
+// Get the extra parameters
+$rezzeruuid = $sloodle->request->required_param('sloodleobjuuid');
+$httpinurl = $sloodle->request->required_param('httpinurl');    
+$childobjectuuid= $sloodle->request->required_param('childobjectuuid');
+$controllerid = $sloodle->request->required_param('sloodlecontrollerid');
+$objectname = $sloodle->request->optional_param('sloodleobjname');
+$extraParams = ($rezzeruuid == $childobjectuuid) ? array() : array( 'sloodlerezzeruuid' => $rezzeruuid );   
+
+// An object authorizing itself.
+if ($childobjectuuid == $rezzeruuid) {
+
+    // configs come from a notecard
+    $configs = array();
+    $configs['sloodlecontrollerid'] = $controllerid;
+    foreach($_POST as $n=>$v) {
+        if (preg_match('/^set\:(.*)$/', $n, $matches) ) {
+            $configs[ $matches[1] ] = $v;
+        }
+    }
+    $objecttype = isset($configs['sloodleobjtype']) ? $configs['sloodleobjtype'] : 'unknown';
+
+    // This is an object trying to register itself with a notecard.
+    // Go ahead an do it - it has a legitimate prim password or it would have been stopped already.
+    $controller = new SloodleController();
+    if (!$controller->load( $controllerid )) {
+        $sloodle->response->set_status_code(-217);//Could not save HTTP In URL for rezzed object
+        $sloodle->response->set_status_descriptor('OBJECT_AUTH');
+        $sloodle->response->add_data_line('Could not save HTTP In URL for rezzed object'); 
+    }
+
+    // TODO: Refactor and move this stuff into activeobject
+
+    // first time we way it
+    $ao = new SloodleActiveObject();
+    if(!$ao->loadByUUID( $childobjectuuid )) {
+        $primpassword = sloodle_random_prim_password();
+        $httpinpassword = sloodle_random_prim_password();
+        if ( !$authid = $controller->register_object($childobjectuuid, $objectname, $sloodle->user, $primpassword, $httpinpassword, $objecttype) ) {
+            $sloodle->response->set_status_code(-217);//Could not save HTTP In URL for rezzed object
+            $sloodle->response->set_status_descriptor('OBJECT_AUTH');
+            $sloodle->response->add_data_line('Could not save HTTP In URL for rezzed object'); 
+        }
+    } else {
+        $authid = $ao->id;
+        // already got it - clean out old configs
+        sloodle_delete_records('sloodle_object_config', 'object', $authid); 
+    }
+
+    if (count($configs) > 0) {
+        foreach($configs as $n => $v) {
+            $config->id = null;
+            $config->object = $authid;
+            $config->name = $n;
+            $config->value = $v;
+            if (!sloodle_insert_record('sloodle_object_config',$config)) {
+                $ok = false;
+            }
+        }
+    }
+
+}
+
+//search for active_object table
+//create new active object for found record
+$active_object= new SloodleActiveObject();
+//loadByUUid will search and find the active object
+$sloodle->response->add_data_line($childobjectuuid);
+
+if(!$active_object->loadByUUID( $childobjectuuid )){
+
+    // Not a full registration - just the httpinurl and the uuid
+    $active_object->httpinurl=$httpinurl;
+    $active_object->uuid=$childobjectuuid;
+    $active_object->save();
+
+    /*
+    $active_object->response = new SloodleResponse();
+    $active_object->response->set_status_code(-201);
+    $active_object->response->set_status_descriptor('OBJECT_AUTH');
+    $active_object->response->add_data_line('Failed to register new active object.');
+    */
+    $renderStr="";
+    //create message
+    //send message to httpin
+    $sloodle->response->set_status_code(1);
+    $sloodle->response->set_status_descriptor('OBJECT_AUTH');
+    $sloodle->response->render_to_output();
+
+} else{
+
+     // If an object we've never seen before wants to tell us its http-in url
+     // ...we'll record it for now, but not do the rest of the registration.
+     // This means the object will get configured even if the request from the object gets to the server
+     // ...before the object that rezzed it has told us about it.
+     $active_object->httpinurl=$httpinurl;
+     if (!$active_object->save()) {                
+        $sloodle->response->set_status_code(-217);//Could not save HTTP In URL for rezzed object
+        $sloodle->response->set_status_descriptor('OBJECT_AUTH');
+        $sloodle->response->add_data_line('Could not save HTTP In URL for rezzed object'); 
+     }
+
+    $async_config = ( defined('SLOODLE_ASYNC_SEND_CONFIG') && SLOODLE_ASYNC_SEND_CONFIG);
+
+    //active object is loaded, send config to the object, and also send the rezzeruuid to the object so 
+    //our object also knows its rezzer uuid 
+    $result = $active_object->sendConfig($extraParams, $async_config, $async_config);
+    //result is an array(curlinfo,curlresult)
+    //lsl script returns an OK or FAIL
+    if ( in_array( $result["result"], array('OK','QUEUED') ) ) { 
+        $sloodle->response->set_status_code(1);
+        $sloodle->response->set_status_descriptor('OK');//httpin config sent properly
+    }else{
+        $sloodle->response->set_status_code(-219);//Sending configuration to object via HTTP-in URL failed
+        $sloodle->response->set_status_descriptor('OBJECT_AUTH');
+        $sloodle->response->add_data_line('Sending configuration to object via HTTP-in URL failed'); 
+    }
+
+    $sloodle->response->render_to_output();
+
+}//end else
+?>

+ 48 - 0
classroom/httpin_url_update_linker.php

@@ -0,0 +1,48 @@
+<?php
+/*  Released under the GNU GPL 3.0
+*  This script can be used in your scripts, but you must include this copyright header as per the GPL Licence
+*  For more information about GPL 3.0 - see: http://www.gnu.org/copyleft/gpl.html
+*  This script is part of the SLOODLE Project see http://sloodle.org
+*
+*  sloodle/classroom/httpin_url_update_linker.php
+*
+*  Copyright:
+*  @contributers Paul G. Preibisch (Fire Centaur in SL)
+*  @contributers Edmund Edgar
+*
+*  When an object's http-in url changes on a sim restart or crossing, it lets us know here.
+* 
+*/ 
+    /** Lets Sloodle know we are in a linker script. */
+    define('SLOODLE_LINKER_SCRIPT', true);
+
+    /** Grab the Sloodle/Moodle configuration. */
+    require_once('../init.php');
+    /** Include the Sloodle PHP API. */
+    require_once(SLOODLE_LIBROOT.'/sloodle_session.php');
+    require_once(SLOODLE_LIBROOT.'/general.php');
+    require_once(SLOODLE_LIBROOT.'/active_object.php');
+
+    // Attempt to authenticate the request
+    // (only require authentication if controller ID and/or password is set)
+    $sloodle = new SloodleSession();
+    $request_auth = $sloodle->authenticate_request(true);
+    
+    $httpinurl = $sloodle->request->required_param('httpinurl');    
+    $objuuid = $sloodle->request->required_param('sloodleobjuuid');
+
+$ao = new SloodleActiveObject();
+    if($ao->loadByUUID( $objuuid)) {
+        $ao->httpinurl = $httpinurl;
+        $ao->save();
+        $sloodle->response->set_status_code(1);
+        $sloodle->response->set_status_descriptor('OK');//httpin config sent properly
+    } else {
+        $sloodle->response->set_status_code(-217);//Could not save HTTP In URL for rezzed object
+        $sloodle->response->set_status_descriptor('OBJECT_AUTH');
+        $sloodle->response->add_data_line('Could not save HTTP In URL for rezzed object'); 
+    }
+
+    $sloodle->response->render_to_output();
+
+?>

+ 174 - 0
classroom/loginzone.php

@@ -0,0 +1,174 @@
+<?php
+    // This file is part of the Sloodle project (www.sloodle.org)
+
+    /**
+    * This page allows users to get/check their LoginZone allocation.
+    * Should be directly accessed as an interface script.
+    *
+    * @package sloodle
+    * @copyright Copyright (c) 2008 Sloodle (various contributors)
+    * @license http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
+    *
+    * @contributor Peter R. Bloomfield
+    *
+    */
+
+    /** Sloodle/Moodle configuration script. */
+    require_once('../init.php');
+    /** Main Sloodle API. */
+    require_once(SLOODLE_LIBROOT.'/sloodle_session.php');
+    
+
+    // Fetch our request parameters
+    $id = optional_param('id', 0, PARAM_INT); // Course Module instance ID
+        
+    // Attempt to fetch the course module instance
+    if ($id) {
+        if (!$course = sloodle_get_record('course', 'id', $id)) {
+            error('Could not find course');
+        }
+    } else {
+        error('Must specify a course ID');
+    }
+    
+    // Get the Sloodle course data
+    $sloodle_course = new SloodleCourse();
+    if (!$sloodle_course->load($course)) error(get_string('failedcourseload','sloodle'));
+    
+    // Ensure that the user is logged-in to this course
+    require_login($course->id);
+    $course_context = get_context_instance(CONTEXT_COURSE, $course->id);
+    
+    // Do not allow guest access
+    if (isguestuser()) {
+        error(get_string('noguestaccess', 'sloodle'));
+        exit();
+    }
+    
+    // This is really a dummy Sloodle Session
+    $sloodle = new SloodleSession(false);
+    $sloodle->user->load_user($USER->id);
+    $has_avatar = $sloodle->user->load_linked_avatar();
+    
+    // Log the view
+    add_to_log($course->id, 'sloodle', 'view loginzone', "mod/sloodle/classroom/loginzone.php?id={$course->id}", "$course->id");
+
+    // Display the page header
+    $navigation = "<a href=\"{$CFG->wwwroot}/mod/sloodle/classroom/loginzone.php?id={$course->id}\">".get_string('loginzone','sloodle')."</a>";
+    sloodle_print_header_simple(get_string('loginzone','sloodle'), "&nbsp;", $navigation, "", "", true, '', navmenu($course));
+    
+    echo '<h1 style="text-align:center;">'.get_string('loginzone','sloodle').'</h1>';
+    
+//------------------------------------------------------    
+ 
+    // Loginzone information
+    sloodle_print_box_start('generalbox boxaligncenter boxwidthnormal');
+    echo '<div style="text-align:center;"><h3>'.get_string('loginzonedata','sloodle').'</h3>';
+    
+    // Does the user have an avatar?
+    if ($has_avatar) {
+        sloodle_print_box_start('generalbox boxaligncenter boxwidthwide');
+        echo get_string('loginzone:alreadyregistered','sloodle').'<br><br>';
+        echo "<a href=\"{$CFG->wwwroot}/mod/sloodle/view.php?_type=user&amp;id={$USER->id}&course={$course->id}\">".get_string('viewmyavatar', 'sloodle')."</a><br>\n";
+        sloodle_print_box_end();
+    }
+    
+    // Check we have all the data we need
+    $data_error = array();
+    if ($sloodle_course->get_loginzone_position() == '') $data_error[] = get_string('position','sloodle');
+    if ($sloodle_course->get_loginzone_size() == '') $data_error[] = get_string('size','sloodle');
+    if ($sloodle_course->get_loginzone_region() == '') $data_error[] = get_string('region','sloodle');
+    
+    // Do we have all the data?
+    if (count($data_error) > 0) {
+        // No - display the error, and stop
+        echo '<span style="font-weight:bold;color:red;">'.get_string('loginzone:datamissing','sloodle').'</span><br><i>(';
+        $isfirst = true;
+        foreach ($data_error as $de) {
+            if ($isfirst) $isfirst = false;
+            else echo ", ";
+            echo "$de";
+        }
+        echo ')</i><br><br>';
+        print_string('loginzone:mayneedrez','sloodle');
+        sloodle_print_box_end();
+        sloodle_print_footer();
+        exit();
+    }
+    
+    // Store a string indicating the time/date of the last loginzone update
+    $lastupdated = '('.get_string('unknown','sloodle').')';
+    if ($sloodle_course->get_loginzone_time_updated() > 0) $lastupdated = date('Y-m-d H:i:s', $sloodle_course->get_loginzone_time_updated());
+    
+    sloodle_print_box_start('generalbox boxaligncenter boxwidthnarrow');
+    echo get_string('position','sloodle').': '.$sloodle_course->get_loginzone_position().'<br>';
+    echo get_string('size','sloodle').': '.$sloodle_course->get_loginzone_size().'<br>';
+    echo get_string('region','sloodle').': '.$sloodle_course->get_loginzone_region().'<br>';
+    echo get_string('lastupdated','sloodle').': '.$lastupdated.'<br>';
+    sloodle_print_box_end();
+    echo '<br>';
+    
+    // How long ago was the loginzone rezzed?
+    $time_difference = time() - $sloodle_course->get_loginzone_time_updated();
+    if ($time_difference > 1800) {
+        echo '<span style="font-weight:bold;color:red;">'.get_string('loginzone:olddata','sloodle').'</span><br>';
+        echo get_string('loginzone:mayneedrez','sloodle').'<br><br>';
+    }
+    
+    // If the user already had an avatar, then there's nothing else to do
+    if ($has_avatar) {
+        sloodle_print_box_end();
+        sloodle_print_footer();
+        exit();
+    }
+    
+    // Make sure the user is allowed to register an avatar
+    require_capability('mod/sloodle:registeravatar', get_context_instance(CONTEXT_SYSTEM));
+    
+    // Has a new allocation been requested?
+    if (isset($_REQUEST['allocate_loginzone'])) {
+        // Yes - generate one
+        if ($sloodle_course->generate_loginzone_allocation($sloodle->user)) {
+            echo '<span style="font-weight:bold;color:green;">'.get_string('loginzone:allocationsucceeded','sloodle').'</span><br>';
+            echo get_string('loginzone:expirynote','sloodle').'<br>';
+        } else {
+            echo get_string('loginzone:allocationfailed','sloodle').'<br>';
+        }
+        echo '<br>';
+    }
+    
+    // Does the user already have a loginzone?
+    $alloc = $sloodle_course->get_loginzone_allocation($sloodle->user);
+    if ($alloc) {
+        // Yes - show the teleport link
+        echo '<span style="font-size:150%; font-weight:bold;">';
+        echo '<a href="'.$alloc.'">'.get_string('loginzone:teleport','sloodle').'</a>';
+        echo '</span><br><br>';
+    } else {
+        echo '<span style="color:orange; font-weight:bold;">';
+        print_string('loginzone:needallocation','sloodle');
+        echo '</span><br><br>';
+    }
+    
+    // Create a form
+    echo "<br><form action=\"loginzone.php\" method=\"GET\">\n";
+    echo "<input type=\"hidden\" name=\"id\" value=\"{$course->id}\">\n";
+    echo "<input type=\"hidden\" name=\"allocate_loginzone\" value=\"true\">\n";
+    
+    // Let the user opt to generate a new LoginZone allocation
+    echo "<input type=\"submit\" value=\"".get_string('loginzone:newallocation','sloodle')."\"/>\n";
+    
+    // Close the form
+    echo "</form>\n";
+    
+    
+    echo '</div>';
+    sloodle_print_box_end();
+
+    
+
+//------------------------------------------------------
+    
+    sloodle_print_footer($course);
+    
+?>

+ 137 - 0
classroom/notecard_configuration_form.php

@@ -0,0 +1,137 @@
+<?php
+    // This file is part of the Sloodle project (www.sloodle.org)
+
+    /**
+    * This page lets an educator/admin create a configuration notecard.
+    * It will be accessed by a link from a Controller page, and will
+    *  display a form based on scripts in the Sloodle "mod" folder.
+    *
+    * @package sloodle
+    * @copyright Copyright (c) 2008 Sloodle (various contributors)
+    * @license http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
+    *
+    * @contributor Peter R. Bloomfield
+    *
+    */
+    
+    /** Sloodle/Moodle configuration script. */
+    require_once('../init.php');
+    /** Sloodle core library functionality */
+    require_once(SLOODLE_DIRROOT.'/lib.php');
+    /** Sloodle API. */
+    require_once(SLOODLE_LIBROOT.'/sloodle_session.php');
+    require_once(SLOODLE_LIBROOT.'/active_object.php');
+    require_once(SLOODLE_LIBROOT.'/object_configs.php');
+
+    // Fetch our required parameters
+    $sloodlecontrollerid = required_param('sloodlecontrollerid', PARAM_INT);
+    $sloodleobjtype = required_param('sloodleobjtype', PARAM_TEXT);
+    
+    // Fetch string table text
+    $strsloodle = get_string('modulename', 'sloodle');
+    $strsloodles = get_string('modulenameplural', 'sloodle');
+    $pagename = get_string('objectconfig:header', 'sloodle');
+    $strsavechanges = get_string('savechanges');
+    $stryes = get_string('yes');
+    $strno = get_string('no');
+    
+    
+    // Attempt to fetch the course module instance
+    if (! $cm = get_coursemodule_from_id('sloodle', $sloodlecontrollerid)) {
+        error("Failed to load course module");
+    }
+    // Get the course data
+    if (! $course = sloodle_get_record("course", "id", $cm->course)) {
+        error("Course is misconfigured");
+    }
+    // Get the Sloodle instance
+    if (! $sloodle = sloodle_get_record('sloodle', 'id', $cm->instance)) {
+        error('Failed to find Sloodle module instance.');
+    }
+    
+    // Get the Sloodle course data
+    $sloodle_course = new SloodleCourse();
+    if (!$sloodle_course->load($course)) error(get_string('failedcourseload','sloodle'));
+    if (!$sloodle_course->controller->load($sloodlecontrollerid)) error('Failed to load Sloodle Controller.');
+    
+    // Ensure that the user is logged-in for this course
+    require_course_login($course, true, $cm);
+    // Is the user allowed to edit the module?
+    $module_context = get_context_instance(CONTEXT_MODULE, $cm->id);
+    $course_context = get_context_instance(CONTEXT_COURSE, $course->id);
+    require_capability('moodle/course:manageactivities', $module_context);
+
+    // Display the page header
+    //$navigation = "<a href=\"{$CFG->wwwroot}/mod/sloodle/index.php?id=$course->id\">$strsloodles</a> ->";
+    $navigation = "<a href=\"{$CFG->wwwroot}/mod/sloodle/view.php?id=$sloodlecontrollerid\">".format_string($sloodle->name)."</a> ->";
+    sloodle_print_header_simple($pagename, "&nbsp;", "$navigation ". $pagename, "", "", true, '', navmenu($course, $cm));
+
+    // We can display the Sloodle module info... log the view
+    add_to_log($course->id, 'sloodle', 'view sloodle config', "classroom/notecard_configuration.php?sloodlecontrollerid=$sloodlecontrollerid&sloodleobjtype=$sloodleobjtype", $sloodleobjtype, $cm->id);
+    
+    
+    // Make sure the object type is recognised
+    $objectpath = SLOODLE_DIRROOT."/mod/$sloodleobjtype";
+    if (!file_exists($objectpath)) error("ERROR: object \"$sloodleobjtype\" is not installed.");
+    // Determine if we have a custom configuration page
+    
+    // Split up the object identifier into name and version number, then get the translated name
+    list($objectname, $objectversion) = SloodleObjectConfig::ParseModIdentifier($sloodleobjtype);
+
+    $strobjectname = get_string("object:$objectname", 'sloodle');
+    
+//---------------------------------------------------------------------------------
+    
+    // Display intro information
+    sloodle_print_box_start('generalbox boxwidthwide boxaligncenter');
+    echo '<div style="text-align:center;">';
+    
+    echo "<h1>$pagename</h1>";
+    echo "<h2>$strobjectname $objectversion</h2>";
+    
+    // Display our configuration form
+    echo '<form action="'.SLOODLE_WWWROOT.'/classroom/notecard_configuration_view.php" method="POST">';
+    echo '<input type="hidden" name="formsubmitted" value="true">';
+    
+    
+    // We need to create some dummy data for the form
+    // (basically pretend we have an authorised object, and hope nobody tries to use the missing data anywhere!)
+    // (not the best idea, I know, but the notecard stuff was added late... :-\)
+    $sloodleauthid = 0;
+    $auth_obj = new SloodleActiveObject();
+    $auth_obj->course = $sloodle_course;
+    $auth_obj->type = $sloodleobjtype;
+    $hascustomconfig = $auth_obj->has_custom_config();
+       
+    // Are there any custom configuration options?
+    if ($hascustomconfig) {
+
+	// TODO: Do I really need all this?
+        $dummysession = new SloodleSession(false);
+        $dummysession->user->load_user($USER->id);
+        $dummysession->user->load_linked_avatar();
+        $auth_obj->user = $dummysession->user;
+ 
+	include('object_configuration_form_template.php');
+        // Include the form elements
+        //require($customconfig);
+        
+    } else {
+        // No configuration settings for this object
+        print_string('noobjectconfig','sloodle');
+        echo '<br><br>';
+    }
+    
+    // Add our other necessary form elements
+    echo '<input type="hidden" name="sloodlecontrollerid" value="'.$sloodlecontrollerid.'">';
+    echo '<input type="hidden" name="sloodleobjtype" value="'.$sloodleobjtype.'">';
+    echo '<input type="submit" value="'.get_string('cfgnotecard:generate','sloodle').'">';
+    echo '</form>';
+    
+    echo '</div>';
+    sloodle_print_box_end();
+    
+//---------------------------------------------------------------------------------    
+    
+    sloodle_print_footer();
+?>

+ 174 - 0
classroom/notecard_configuration_view.php

@@ -0,0 +1,174 @@
+<?php
+    // This file is part of the Sloodle project (www.sloodle.org)
+
+    /**
+    * This page lets an educator/admin view a configuration notecard.
+    * The data will first arrive as POST data, but will be converted
+    *  over to SESSION data, and the page reloaded.
+    *
+    * @package sloodle
+    * @copyright Copyright (c) 2008 Sloodle (various contributors)
+    * @license http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
+    *
+    * @contributor Peter R. Bloomfield
+    *
+    */
+    
+    /** Sloodle/Moodle configuration script. */
+    require_once('../init.php');
+    /** Sloodle core library functionality */
+    require_once(SLOODLE_DIRROOT.'/lib.php');
+    /** Sloodle API. */
+    require_once(SLOODLE_LIBROOT.'/sloodle_session.php');
+
+    // Fetch our required parameters
+    $sloodlecontrollerid = required_param('sloodlecontrollerid', PARAM_INT);
+    $sloodleobjtype = required_param('sloodleobjtype', PARAM_TEXT);
+    
+    
+    // Has HTTP form data been submitted?
+    if (isset($_REQUEST['formsubmitted'])) {
+        // Convert all the form data to session data
+        foreach ($_REQUEST as $k => $v) {
+            // Ignore anything that doesn't start with 'sloodle'
+            if (strpos($k, 'sloodle') !== 0) {
+                continue;
+            }
+            // Store it in session data
+            $_SESSION[$k] = $v;
+        }
+        
+        // Make sure we know about the form in session data
+        $_SESSION['formsubmitted'] = true;
+        
+        // Reload this page
+        redirect("{$CFG->wwwroot}/mod/sloodle/classroom/notecard_configuration_view.php?sloodlecontrollerid=$sloodlecontrollerid&sloodleobjtype=$sloodleobjtype");
+        exit();
+        
+    } else if (!isset($_SESSION['formsubmitted'])) {
+        // No, and there is no session form data either.
+        // Re-direct back to the notecard page
+        redirect("{$CFG->wwwroot}/mod/sloodle/classroom/notecard_configuration_form.php?sloodlecontrollerid=$sloodlecontrollerid&sloodleobjtype=$sloodleobjtype");
+        exit();
+    }
+    
+    // Clear the form submission
+    unset($_SESSION['formsubmitted']);
+    
+    
+    // Fetch string table text
+    $strsloodle = get_string('modulename', 'sloodle');
+    $strsloodles = get_string('modulenameplural', 'sloodle');
+    $pagename = get_string('cfgnotecard:header', 'sloodle');
+    $strsavechanges = get_string('savechanges');
+    $stryes = get_string('yes');
+    $strno = get_string('no');
+    
+    
+    // Attempt to fetch the course module instance
+    if (! $cm = get_coursemodule_from_id('sloodle', $sloodlecontrollerid)) {
+        error("Failed to load course module");
+    }
+    // Get the course data
+    if (! $course = sloodle_get_record("course", "id", $cm->course)) {
+        error("Course is misconfigured");
+    }
+    // Get the Sloodle instance
+    if (! $sloodle = sloodle_get_record('sloodle', 'id', $cm->instance)) {
+        error('Failed to find Sloodle module instance.');
+    }
+    
+    // Get the Sloodle course data
+    $sloodle_course = new SloodleCourse();
+    if (!$sloodle_course->load($course)) error(get_string('failedcourseload','sloodle'));
+    if (!$sloodle_course->controller->load($sloodlecontrollerid)) error('Failed to load Controller data');
+    
+    // Ensure that the user is logged-in for this course
+    require_course_login($course, true, $cm);
+    // Is the user allowed to edit the module?
+    $module_context = get_context_instance(CONTEXT_MODULE, $cm->id);
+    $course_context = get_context_instance(CONTEXT_COURSE, $course->id);
+    require_capability('moodle/course:manageactivities', $module_context);
+
+    // Display the page header
+    //$navigation = "<a href=\"{$CFG->wwwroot}/mod/sloodle/index.php?id=$course->id\">$strsloodles</a> ->";
+    $navigation = "<a href=\"{$CFG->wwwroot}/mod/sloodle/view.php?id=$sloodlecontrollerid\">".format_string($sloodle->name)."</a> ->";
+    sloodle_print_header_simple($pagename, "&nbsp;", "$navigation ". $pagename, "", "", true, '', navmenu($course, $cm));
+
+    // We can display the Sloodle module info... log the view
+    add_to_log($course->id, 'sloodle', 'view sloodle config', "classroom/notecard_configuration.php?sloodlecontrollerid=$sloodlecontrollerid&sloodleobjtype=$sloodleobjtype", $sloodleobjtype, $cm->id);
+    
+    // Make sure the object type is recognised
+    $objectpath = SLOODLE_DIRROOT."/mod/$sloodleobjtype";
+    if (!file_exists($objectpath)) error("ERROR: object \"$sloodleobjtype\" is not installed.");
+    // Determine if we have a custom configuration page
+    $customconfig = $objectpath.'/object_config.php';
+    $hascustomconfig = file_exists($customconfig);
+    
+    // Split up the object identifier into name and version number, then get the translated name
+    list($objectname, $objectversion) = SloodleObjectConfig::ParseModIdentifier($sloodleobjtype);
+
+    $strobjectname = get_string("object:$objectname", 'sloodle');
+    
+    
+//---------------------------------------------------------------------------------
+    
+    // Display intro information
+    sloodle_print_box_start('generalbox boxwidthwide boxaligncenter');
+    echo '<div style="text-align:center;">';
+    
+    echo "<h1>$pagename</h1>";
+    echo "<h2>$strobjectname $objectversion</h2>";
+    print_string('cfgnotecard:instructions', 'sloodle');
+    echo '<br><br>';
+    print_string('cfgnotecard:security', 'sloodle');
+    echo '<br><br><br>';
+    
+    
+    // Fetch all our custom configuration settings into a separate array
+    $customsettings = array();
+    if (count($_SESSION) > 0) {
+        foreach ($_SESSION as $k => $v) {
+            // Ignore anything that doesn't start with 'sloodle'
+            if (strpos($k, 'sloodle') !== 0) continue;
+            // Ignore anything we will add in later
+            if ($k != 'sloodleobjtype' && $k != 'sloodlecontrollerid') $customsettings[$k] = $v;
+            // Remove the setting so it doesn't appear later
+            unset($_SESSION[$k]);
+        }
+    }
+    // Figure out how many lines we need
+    $numlines = count($customsettings) + 6;
+    
+    
+    // The configuration text
+    echo '<textarea cols="60" rows="'.$numlines.'" readonly="true" onclick="">';
+    
+    // Add the standard stuff in
+    echo "set:sloodleobjtype|$sloodleobjtype\n";
+    echo "set:sloodleserverroot|{$CFG->wwwroot}\n";
+    echo "set:sloodlecontrollerid|$sloodlecontrollerid\n";
+    echo "set:sloodlepwd|".$sloodle_course->controller->get_password()."\n";
+    echo "set:sloodlecoursename_short|".$sloodle_course->get_short_name()."\n";
+    echo "set:sloodlecoursename_full|".$sloodle_course->get_full_name()."\n";
+    
+    // Go through each session parameter
+    foreach ($customsettings as $k => $v) {
+        // Output the value
+        echo "set:$k|$v\n";
+    }
+    
+    echo '</textarea><br><br>';
+    
+    echo "<a href=\"notecard_configuration_form.php?sloodlecontrollerid=$sloodlecontrollerid&sloodleobjtype=$sloodleobjtype\">&lt;&lt; ".get_string('objectconfig:backtoform','sloodle')."</a>";
+    
+    echo '</div>';
+    sloodle_print_box_end();
+    
+    
+//---------------------------------------------------------------------------------
+    
+    
+    
+    sloodle_print_footer();
+?>

+ 78 - 0
classroom/object_config_linker.php

@@ -0,0 +1,78 @@
+<?php
+    /**
+    * Sloodle object configuration linker.
+    *
+    * Allows objects in-world to download their configuration settings.
+    *
+    * @package sloodleclassroom
+    * @copyright Copyright (c) 2008 Sloodle (various contributors)
+    * @license http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
+    *
+    * @contributor Peter R. Bloomfield
+    *
+    */
+    
+    // This script should be called with the following parameters:
+    //
+    //  sloodlepwd = password for authentication
+    //  sloodleauthid = the object whose configuration is being downloaded
+    //
+    // The controller is identified by the object's authorisation.
+    // If succesful, status code 1 is returned, and each data line contains a name/value pair, like so:
+    //
+    //  1|OK
+    //  name|value
+    //
+    // Returns status code -103 if the object was not found or has not been configured yet.
+    //
+    
+    /** Grab the Sloodle/Moodle configuration. */
+    require_once('../init.php');
+    /** Include the Sloodle PHP API. */
+    require_once(SLOODLE_LIBROOT.'/sloodle_session.php');
+    
+    // Start a new Sloodle session
+    $sloodle = new SloodleSession();
+    
+    // Get the object ID
+    $sloodleauthid = (int)$sloodle->request->required_param('sloodleauthid');
+    $auth_obj = SloodleController::get_object($sloodleauthid);
+    if (!$auth_obj) {
+        $sloodle->response->quick_output(-103, 'SYSTEM', 'Object not found', false);
+        exit();
+    }
+    // Is the object authorised?
+    if ($auth_obj->course->controller->is_loaded() == false) {
+        $sloodle->response->quick_output(-103, 'SYSTEM', 'Object not authorised', false);
+        exit();
+    }
+    
+    // Authenticate the request
+    $sloodle->course = $auth_obj->course; // The object doesn't know it's controller yet, but the database does.
+    $_REQUEST['sloodlecontrollerid'] = $auth_obj->course->controller->get_id();
+    $sloodle->authenticate_request();
+    
+    // Add a note of the controller and course names to the outgoing data
+    $sloodle->response->add_data_line(array('sloodlecontrollerid', $auth_obj->course->controller->get_id()));
+    $sloodle->response->add_data_line(array('sloodlecoursename_short', $auth_obj->course->get_short_name()));
+    $sloodle->response->add_data_line(array('sloodlecoursename_full', $auth_obj->course->get_short_name()));//$auth_obj->course->get_full_name())); // Shortened... was causing memory errors!
+    
+    // Fetch all the configuration settings
+    $settings = sloodle_get_records('sloodle_object_config', 'object', $sloodleauthid);
+    if (!$settings) {
+        // Error: no configuration settings... there should be at least one indicating the type
+        $sloodle->response->quick_output(-103, 'SYSTEM', 'Object not configured yet.', false);
+        exit();
+    }
+    
+    // Output each setting
+    foreach ($settings as $s) {
+        $sloodle->response->add_data_line(array($s->name, $s->value));
+    }
+    
+    $sloodle->response->set_status_code(1);
+    $sloodle->response->set_status_descriptor('OK');
+    $sloodle->response->render_to_output();
+
+    exit();
+?>

+ 80 - 0
classroom/object_configuration_form_template.php

@@ -0,0 +1,80 @@
+<?php
+    /**
+    * Choice 1.0 configuration form.
+    *
+    * This is a fragment of HTML which gives the form elements for configuration of a choice object, v1.0.
+    * ONLY the basic form elements should be included.
+    * The "form" tags and submit button are already specified outside.
+    * The $auth_obj and $sloodleauthid variables will identify the object being configured.
+    *
+    * @package sloodlechoice
+    * @copyright Copyright (c) 2008 Sloodle (various contributors)
+    * @license http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
+    *
+    * @contributor Peter R. Bloomfield
+    * @contributor Edmund Edgar
+    *
+    */
+    
+    // IMPORTANT: make sure this is called from within a Sloodle script
+    if (!defined('SLOODLE_VERSION')) {
+        error('Not called from within a Sloodle script.');
+        exit();
+    }
+    
+    // Execute everything within a function to ensure we don't mess up the data in the other file
+    sloodle_display_config_form($sloodleauthid, $auth_obj);
+    
+    
+    
+    function sloodle_display_config_form($sloodleauthid, $auth_obj)
+    {
+    //--------------------------------------------------------
+    // SETUP
+
+	$def = $auth_obj->objectDefinition();
+	$fieldsets = $def->field_sets;
+
+        $settings = $auth_obj->config_name_value_hash();
+	if ($auth_obj->id > 0) {
+		$def->populateDefaults( $settings );
+	}
+
+        $courseid = $auth_obj->course->get_course_id();
+
+	if ( $module_choice = $def->module_choice($courseid) ) {
+		// Historically, these forms have put the module choice under "General Configuration"
+		// This seems wrong, but for now we'll levae it as it is.
+		if (!isset($fieldsets['generalconfiguration'])) {
+			$fieldsets = array_merge(array('generalconfiguration'=>array()), $fieldsets);
+		}
+		array_unshift($fieldsets['generalconfiguration'], $module_choice);
+	}
+        
+	foreach($fieldsets as $fieldsetname => $fieldset) {
+
+		sloodle_print_box_start('generalbox boxaligncenter');
+		echo '<h3>'.get_string($fieldsetname,'sloodle').'</h3>';
+
+		foreach($fieldset as $field) {
+			echo get_string($field->title,'sloodle').': ';
+			if ($field->type == 'yesno' ) {
+				choose_from_menu_yesno($field->fieldname, $field->default);
+			} else if ( ($options = $field->translatedOptions()) && is_array($options) ) {
+				choose_from_menu($options, $field->fieldname, $field->default, '');
+			} else {
+				echo '<input type="text" name="'.$field->fieldname.'" value="'.htmlspecialchars( $field->default, ENT_QUOTES) .'" size="8" maxlength="8" />';
+
+			}
+			echo "<br><br>\n";
+		}
+
+
+		sloodle_print_box_end();
+
+	}
+
+        
+    }
+    
+?>

+ 114 - 0
classroom/store_object_config.php

@@ -0,0 +1,114 @@
+<?php
+
+    /**
+    * Sloodle object configuration storage script.
+    *
+    * When passed a list of object configuration settings, it will store them.
+    * User must be logged-in and authorised to manage activities on the specified course.
+    *
+    * @package sloodleclassroom
+    * @copyright Copyright (c) 2007-8 Sloodle (various contributors)
+    * @license http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
+    *
+    * @contributor Peter R. Bloomfield
+    *
+    */
+    
+    // The following parameter is required:
+    //
+    //  sloodleauthid = identifies which authorisation (active obejct) entry is being configured
+    //
+    // All other parameters which start with "sloodle", except for "sloodledebug", are treated
+    //  as configuration settings and stored accordingly.
+    //
+    
+    /** Include the Sloodle/Moodle configuration. */
+    require_once('../init.php');
+    /** Include the Sloodle API. */
+    require_once(SLOODLE_LIBROOT.'/sloodle_session.php');
+    
+    // Make sure the user is logged-in and is not a guest
+    if (isloggedin() == false || isguestuser() == true) {
+        error(get_string('noguestaccess','sloodle'));
+        exit();
+    }
+    
+    // We need to know which object is being configured
+    $sloodleauthid = required_param('sloodleauthid', PARAM_INT);
+    $auth_obj = SloodleController::get_object($sloodleauthid);
+    if (!$auth_obj) {
+        error(get_string('objectauthnotfound','sloodle'));
+        exit();
+    }
+    // Make sure the object is authorised
+    if ($auth_obj->course->controller->is_loaded() == false) {
+        //TODO: more appropriate error message?
+        error(get_string('objectauthnotfound','sloodle'));
+        exit();
+    }
+    
+    // Make sure the user has permission to manage activities on this course
+    $course_context = get_context_instance(CONTEXT_COURSE, $auth_obj->course->get_course_id());
+    require_capability('moodle/course:manageactivities', $course_context);
+    
+    // Delete all configuration options already associated with the object
+    sloodle_delete_records('sloodle_object_config', 'object', $sloodleauthid);
+    
+    // Define parameter names we will ignore
+    $IGNORE_PARAMS = array('sloodleauthid', 'sloodledebug');
+    // This structure will store our values
+    $config_setting = new stdClass();
+    $config_setting->object = $sloodleauthid;
+    
+    // Add the new ones
+    $numstored = 0;
+    foreach ($_REQUEST as $k => $v) {
+        // Ignore anything which does not start with "sloodle"
+        if (strpos($k, 'sloodle') !== 0) continue;
+        // Ignore certain parameters
+        if (in_array($k, $IGNORE_PARAMS)) continue;
+        
+        // Store the setting
+        $config_setting->name = $k;
+        $config_setting->value = $v;
+        if (sloodle_insert_record('sloodle_object_config', $config_setting)) {
+            $numstored++;
+        }
+    }
+    
+    // Construct a breadcrumb navigation menu
+    $nav = get_string('modulename', 'sloodle').' -> ';
+    $nav .= get_string('objectconfiguration','sloodle');
+    // Display the page header
+    sloodle_print_header(get_string('objectconfiguration','sloodle'), get_string('objectconfiguration','sloodle'), $nav, '', '', false);
+    
+    
+    // Display the information about the object
+    sloodle_print_box_start('generalbox boxaligncenter boxwidthnarrow');
+    echo '<div style="text-align:center;">';
+    echo '<span style="font-weight:bold; font-size:110%;">'.get_string('objectdetails','sloodle').'</span><br>';
+    
+    echo get_string('objectname','sloodle').': '.$auth_obj->name.'<br>';
+    echo get_string('objectuuid','sloodle').': '.$auth_obj->uuid.'<br>';
+    echo get_string('objecttype','sloodle').': '.$auth_obj->type.'<br>';
+    echo get_string('authorizedfor', 'sloodle').$auth_obj->course->get_full_name().' &gt; '.$auth_obj->course->controller->get_name().'<br>';
+    
+    // Indicate how many settings were stored
+    echo '<br><span style="font-weight:bold;">';
+    print_string('numsettingsstored','sloodle');
+    echo " $numstored</span><br>\n";
+    
+    echo '</div>';
+    sloodle_print_box_end();
+    echo '<br>';
+    
+    // Print a continue button, to go back to the course
+    echo '<div style="text-align:center;">';
+    echo "<form action=\"{$CFG->wwwroot}/course/view.php\" method=\"GET\">";
+    echo '<input type="hidden" name="id" value="'.$auth_obj->course->get_course_id().'"/>';
+    echo '<input type="submit" value="'.get_string('continue').'"/>';
+    echo '</form></div>';
+    
+    sloodle_print_footer();
+    
+?>

+ 138 - 0
config.html

@@ -0,0 +1,138 @@
+<?php
+    /**
+    * Sloodle module configuration fragment.
+    *
+    * This PHP/HTML fragment shows a web-form for configuring the Sloodle module.
+    *
+    * @package sloodle
+    * @copyright Copyright (c) 2007 Sloodle (various contributors)
+    * @license http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
+    *
+    * @contributor (various)
+    * @contributor Peter R. Bloomfield
+    *
+    */
+    
+
+    // Get our Sloodle configuration
+    require_once($CFG->dirroot.'/mod/sloodle/sl_config.php');
+    require_once(SLOODLE_LIBROOT.'/general.php');
+    
+    // Attempt to get the configuration settings
+    $allow_autoreg = (sloodle_autoreg_enabled_site()) ? 1 : 0;
+    $allow_autoenrol = (sloodle_autoenrol_enabled_site()) ? 1 : 0;
+    $active_object_lifetime = 7; // Give active objects a week by default
+    if (!empty($CFG->sloodle_active_object_lifetime)) $active_object_lifetime = $CFG->sloodle_active_object_lifetime;
+    $user_object_lifetime = 21; // Give user objects 3 weeks by default
+    if (!empty($CFG->sloodle_user_object_lifetime)) $user_object_lifetime = $CFG->sloodle_user_object_lifetime;
+    // Display the Sloodle version
+    sloodle_print_heading(get_string('sloodleversion','sloodle').': '.(string)SLOODLE_VERSION, 'center', 3);
+    // Get and display the module version number (from version.php)
+    $sloodlemodule = sloodle_get_record('modules', 'name', 'sloodle');
+    $releasenum = 0;
+    if ($sloodlemodule !== FALSE) $releasenum = $sloodlemodule->version;
+    sloodle_print_heading(get_string('releasenum','sloodle').': '.(string)$releasenum, 'center', 5);
+    
+    // Display a help button regarding the version numbers
+    echo '<div style="text-align:center;">(';
+    sloodle_helpbutton('version_numbers', get_string('help:versionnumbers', 'sloodle'), 'sloodle', true, true);
+    echo ')</div>';
+?>
+
+
+<!-- Sloodle configuration form -->
+
+<form method="post" action="module.php" id="form">
+<!--<div>-->
+<!-- Session key and module name -->
+<input type="hidden" name="sesskey" value="<?php echo $USER->sesskey ?>" />
+<input type="hidden" name="module" value="sloodle" />
+
+<table cellpadding="9" cellspacing="0" >
+
+<!-- Notecard -->
+<!-- N.B: ****** possibly add a note here to say how to find the new notecard configuration pages? *******
+<tr>
+    <td colspan="3" align="left">
+            <h3><?php print_string("objectconfig:header", "sloodle"); ?></h3>
+            <p>
+             <?php print_string("objectconfig:body", "sloodle"); ?>
+             <a href="<?php echo SLOODLE_WWWROOT; ?>/sl_setup_notecard.php"><?php print_string("createnotecard", "sloodle"); ?></a>
+            </p>
+    </td>
+</tr>-->
+
+<!-- Auto-registration -->
+<tr>
+    <td colspan="3" align="left">
+
+        <h3><?php print_string('autoreg', 'sloodle'); ?> <?php echo sloodle_helpbutton('auto_registration', get_string('help:autoreg','sloodle'), 'sloodle', true, false, '', true); ?></h3>
+        <p>
+         <?php
+            print_string("autoreg:allowforsite", "sloodle");
+            echo ': ';
+            choose_from_menu_yesno('allow_autoreg', $allow_autoreg);
+         ?>
+        </p>
+    </td>
+</tr>
+
+<!-- Auto-enrolment -->
+<tr>
+    <td colspan="3" align="left">
+
+        <h3><?php print_string('autoenrol', 'sloodle'); ?> <?php echo sloodle_helpbutton('auto_enrolment', get_string('help:autoenrol','sloodle'), 'sloodle', true, false, '', true); ?></h3>
+        <p>
+         <?php
+            print_string("autoenrol:allowforsite", "sloodle");
+            echo ': ';
+            choose_from_menu_yesno('allow_autoenrol', $allow_autoenrol);
+         ?>
+        </p>
+    </td>
+</tr>
+
+
+<!-- Active object lifetime -->
+<tr>
+    <td colspan="3" align="left">
+
+        <h3><?php print_string('activeobjectlifetime', 'sloodle'); ?> <?php echo sloodle_helpbutton('object_authorization', get_string('activeobjects','sloodle'), 'sloodle', true, false, '', true); ?></h3>
+        <p>
+         <?php
+            print_string("activeobjectlifetime:info", "sloodle");
+            echo ': ';
+            echo '<input type="text" name="active_object_lifetime" value="'.$active_object_lifetime.'" >';
+         ?>
+        </p>
+    </td>
+</tr>
+
+
+<!-- User object lifetime -->
+<tr>
+    <td colspan="3" align="left">
+
+        <h3><?php print_string('userobjectlifetime', 'sloodle'); ?> <?php echo sloodle_helpbutton('user_objects', get_string('userobjects','sloodle'), 'sloodle', true, false, '', true); ?></h3>
+        <p>
+         <?php
+            print_string("userobjectlifetime:info", "sloodle");
+            echo ': ';
+            echo '<input type="text" name="user_object_lifetime" value="'.$user_object_lifetime.'" >';
+         ?>
+        </p>
+    </td>
+</tr>
+
+
+<!-- Form Submission -->
+<tr>
+    <td colspan="3" align="center">
+        <input type="submit" value="<?php print_string("savechanges") ?>" />
+    </td>
+</tr>
+</table>
+<!--</div>-->
+</form>
+
+<!-- End Sloodle configuration form -->

+ 193 - 0
db/access.php

@@ -0,0 +1,193 @@
+<?php
+
+/**
+* Moodle capability definitions for the Sloodle module.
+*
+* The capabilities are loaded into the database table when the module is
+* installed or updated. Whenever the capability definitions are updated,
+* the module version number should be bumped up.
+*
+* @package sloodle
+* @copyright Copyright (c) 2008 Sloodle (various contributors)
+* @license http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
+* @since Sloodle 0.3
+*
+* @contributor Peter R. Bloomfield
+* @contributor Edmund Edgar
+*
+*/
+
+//
+// The system has four possible values for a capability:
+// CAP_ALLOW, CAP_PREVENT, CAP_PROHIBIT, and inherit (not set).
+//
+//
+// CAPABILITY NAMING CONVENTION
+//
+// It is important that capability names are unique. The naming convention
+// for capabilities that are specific to modules and blocks is as follows:
+//   [mod/block]/<component_name>:<capabilityname>
+//
+// component_name should be the same as the directory name of the mod or block.
+//
+// Core moodle capabilities are defined thus:
+//    moodle/<capabilityclass>:<capabilityname>
+//
+// Examples: mod/forum:viewpost
+//           block/recent_activity:view
+//           moodle/site:deleteuser
+//
+// The variable name for the capability definitions array follows the format
+//   $<componenttype>_<component_name>_capabilities
+//
+// For the core capabilities, the variable is $moodle_capabilities.
+
+// NOTE: many Sloodle components relate directly to Moodle components.
+// In these cases, the standard capabilities for those components apply.
+// For example, Sloodle module configuration is covered by the Moodle standard capability:
+//  moodle/course:manageactivities
+
+// Viewing and editing avatar details will be handled by the user profile capabilities.
+
+
+$mod_sloodle_capabilities = array(
+
+    // Considered a 'staff' member in SL
+    'mod/sloodle:staff' => array(
+        'captype' => 'write',
+        'contextlevel' => CONTEXT_MODULE,
+        'legacy' => array(
+            'teacher' => CAP_ALLOW,
+            'editingteacher' => CAP_ALLOW,
+            'coursecreator' => CAP_ALLOW,
+            'admin' => CAP_ALLOW
+        )
+    ),
+
+    // Authorising objects to access Sloodle
+    'mod/sloodle:objectauth' => array(
+        'riskbitmask' => RISK_CONFIG,
+        'captype' => 'write',
+        'contextlevel' => CONTEXT_MODULE,
+        'legacy' => array(
+            'editingteacher' => CAP_ALLOW,
+            'coursecreator' => CAP_ALLOW,
+            'admin' => CAP_ALLOW
+        )
+    ),
+    
+    // Authorising user objects to access user-specific parts of Sloodle (e.g. blogging)
+    'mod/sloodle:userobjectauth' => array(
+        'captype' => 'write',
+        'contextlevel' => CONTEXT_MODULE,
+        'legacy' => array(
+            'user' => CAP_ALLOW,
+            'guest' => CAP_PREVENT,
+            'student' => CAP_ALLOW,
+            'teacher' => CAP_ALLOW,
+            'editingteacher' => CAP_ALLOW,
+            'coursecreator' => CAP_ALLOW,
+            'admin' => CAP_ALLOW
+        )
+    ),
+    
+    
+    // Access and use classroom layout profiles
+    'mod/sloodle:uselayouts' => array(
+        'captype' => 'read',
+        'contextlevel' => CONTEXT_MODULE,
+        'legacy' => array(
+            'teacher' => CAP_ALLOW,
+            'editingteacher' => CAP_ALLOW,
+            'coursecreator' => CAP_ALLOW,
+            'admin' => CAP_ALLOW
+        )
+    ),
+    
+    // Edit and delete classroom layout profiles
+    'mod/sloodle:editlayouts' => array(
+        'captype' => 'write',
+        'contextlevel' => CONTEXT_MODULE,
+        'legacy' => array(
+            'editingteacher' => CAP_ALLOW,
+            'coursecreator' => CAP_ALLOW,
+            'admin' => CAP_ALLOW
+        )
+    ),
+    
+    
+    // Register an avatar with their own account
+    'mod/sloodle:registeravatar' => array(
+        'captype' => 'write',
+        'contextlevel' => CONTEXT_MODULE,
+        'legacy' => array(
+            'user' => CAP_ALLOW,
+            'guest' => CAP_PREVENT,
+            'student' => CAP_ALLOW,
+            'teacher' => CAP_ALLOW,
+            'editingteacher' => CAP_ALLOW,
+            'coursecreator' => CAP_ALLOW,
+            'admin' => CAP_ALLOW
+        )
+    ),
+    
+    // Distribute objects to own avatar
+    'mod/sloodle:distributeself' => array(
+        'captype' => 'write',
+        'contextlevel' => CONTEXT_MODULE,
+        'legacy' => array(
+            'guest' => CAP_PREVENT,
+            'student' => CAP_ALLOW,
+            'teacher' => CAP_ALLOW,
+            'editingteacher' => CAP_ALLOW,
+            'coursecreator' => CAP_ALLOW,
+            'admin' => CAP_ALLOW
+        )
+    ),
+    
+    // Distribute objects to other avatars
+    'mod/sloodle:distributeothers' => array(
+        'riskbitmask' => RISK_SPAM,
+        'captype' => 'write',
+        'contextlevel' => CONTEXT_MODULE,
+        'legacy' => array(
+            'guest' => CAP_PREVENT,
+            'editingteacher' => CAP_ALLOW,
+            'coursecreator' => CAP_ALLOW,
+            'admin' => CAP_ALLOW
+        )
+    ),
+
+    // Indicates that the user is involved in the course. Replaces pre-Moodle-2 course:view
+    'mod/sloodle:courseparticipate' => array(
+        'captype' => 'write',
+        'contextlevel' => CONTEXT_COURSE,
+        'legacy' => array(
+            'guest' => CAP_PREVENT,
+            'student' => CAP_ALLOW,
+            'teacher' => CAP_ALLOW,
+            'editingteacher' => CAP_ALLOW,
+            'coursecreator' => CAP_ALLOW,
+            'admin' => CAP_ALLOW
+        )
+    ),
+ 
+    // Indicates that the user is allowed to use a controller.
+    // Intended to replace courseparticipate where permissions can sensibly be set at the controller level.
+    'mod/sloodle:controllerparticipate' => array(
+        'captype' => 'write',
+        'contextlevel' => CONTEXT_MODULE,
+        'legacy' => array(
+            'guest' => CAP_PREVENT,
+            'student' => CAP_ALLOW,
+            'teacher' => CAP_ALLOW,
+            'editingteacher' => CAP_ALLOW,
+            'coursecreator' => CAP_ALLOW,
+            'admin' => CAP_ALLOW
+        )
+    ),
+
+
+);
+
+?>

+ 21 - 0
db/install.php

@@ -0,0 +1,21 @@
+<?php
+
+// This file replaces:
+//   * STATEMENTS section in db/install.xml
+//   * lib.php/modulename_install() post installation hook
+//   * partially defaults.php
+
+function xmldb_sloodle_install() {
+    global $DB;
+ 
+    // Moodle 2 only - on Moodle 1.x you'll have to insert them manually...
+    if ($DB != null) {
+
+        $newCurrency = new stdClass();
+        $newCurrency->name="Credits";
+
+        $DB->insert_record('sloodle_currency_types', $newCurrency, false);
+
+    }
+
+}

+ 410 - 0
db/install.xml

@@ -0,0 +1,410 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<XMLDB PATH="mod/sloodle/db" VERSION="20110820" COMMENT="XMLDB file for Sloodle"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
+>
+  <TABLES>
+    <TABLE NAME="sloodle" COMMENT="Defines instances of Sloodle modules." NEXT="sloodle_controller">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" NEXT="course"/>
+        <FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" PREVIOUS="id" NEXT="type"/>
+        <FIELD NAME="type" TYPE="char" LENGTH="50" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="Shortname of the type of this Sloodle module." PREVIOUS="course" NEXT="name"/>
+        <FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="The name of this Sloodle instance" PREVIOUS="type" NEXT="intro"/>
+        <FIELD NAME="intro" TYPE="text" LENGTH="medium" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="Intro text for this Sloodle isntance" PREVIOUS="name" NEXT="introformat"/>
+
+        <FIELD NAME="introformat" TYPE="int" LENGTH="4" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" ENUM="false" COMMENT="Stores format of intro field." PREVIOUS="intro" NEXT="timecreated"/>
+        <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" PREVIOUS="introformat" NEXT="timemodified"/>
+        <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" PREVIOUS="timecreated"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Primary key for sloodle"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="course" UNIQUE="false" FIELDS="course"/>
+
+      </INDEXES>
+    </TABLE>
+    <TABLE NAME="sloodle_controller" COMMENT="Defines instances of Sloodle controllers." PREVIOUS="sloodle" NEXT="sloodle_distributor">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" NEXT="sloodleid"/>
+        <FIELD NAME="sloodleid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" COMMENT="Identifies which Sloodle instance this refers to." PREVIOUS="id" NEXT="enabled"/>
+        <FIELD NAME="enabled" TYPE="int" LENGTH="1" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" COMMENT="Indicates whether or not this Controller is enabled.." PREVIOUS="sloodleid" NEXT="password"/>
+        <FIELD NAME="password" TYPE="char" LENGTH="9" NOTNULL="false" SEQUENCE="false" ENUM="false" COMMENT="Prim Password for this instance. If left blank, it should use the site default." PREVIOUS="enabled"/>
+      </FIELDS>
+
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Primary key for table"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="sloodleid" UNIQUE="true" FIELDS="sloodleid"/>
+      </INDEXES>
+    </TABLE>
+    <TABLE NAME="sloodle_distributor" COMMENT="Defines instances of Sloodle distributors." PREVIOUS="sloodle_controller" NEXT="sloodle_distributor_entry">
+      <FIELDS>
+
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" NEXT="sloodleid"/>
+        <FIELD NAME="sloodleid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" COMMENT="Identifies which Sloodle instance this refers to." PREVIOUS="id" NEXT="channel"/>
+        <FIELD NAME="channel" TYPE="char" LENGTH="36" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="UUID of the XMLRPC channel opened by the Distributor object in-world" PREVIOUS="sloodleid" NEXT="timeupdated"/>
+        <FIELD NAME="timeupdated" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" PREVIOUS="channel"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Primary key for table"/>
+      </KEYS>
+    </TABLE>
+
+    <TABLE NAME="sloodle_distributor_entry" COMMENT="Defines entries in Sloodle Distributors." PREVIOUS="sloodle_distributor" NEXT="sloodle_users">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" NEXT="distributorid"/>
+        <FIELD NAME="distributorid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" COMMENT="Identifies which Distributor instance this refers to." PREVIOUS="id" NEXT="name"/>
+        <FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="The name of this Sloodle distributor entry" PREVIOUS="distributorid"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Primary key for table"/>
+      </KEYS>
+
+    </TABLE>
+    <TABLE NAME="sloodle_users" COMMENT="Associates Moodle user IDs with Second Life avatar UUIDs and names" PREVIOUS="sloodle_distributor_entry" NEXT="sloodle_course">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" COMMENT="Primary index field" NEXT="userid"/>
+        <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" COMMENT="Index of the associated Moodle user account." PREVIOUS="id" NEXT="uuid"/>
+        <FIELD NAME="uuid" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="UUID of the user agent" PREVIOUS="userid" NEXT="avname"/>
+        <FIELD NAME="avname" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="Name of the avatar" PREVIOUS="uuid" NEXT="profilepic"/>
+        <FIELD NAME="profilepic" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" ENUM="false" COMMENT="Profile url of the avatar" PREVIOUS="avname" NEXT="lastactive"/>
+        <FIELD NAME="lastactive" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" COMMENT="Timestamp of the last time the avatar did any Sloodle activity" PREVIOUS="profilepic"/>
+
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Primary key for sloodle_users"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="uuid" UNIQUE="false" FIELDS="uuid" NEXT="userid"/>
+        <INDEX NAME="userid" UNIQUE="false" FIELDS="userid" PREVIOUS="uuid"/>
+      </INDEXES>
+    </TABLE>
+
+    <TABLE NAME="sloodle_course" COMMENT="Stores Sloodle data about each course" PREVIOUS="sloodle_users" NEXT="sloodle_pending_avatars">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" COMMENT="Primary index field" NEXT="course"/>
+        <FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" COMMENT="Index of the Moodle course this record relates to" PREVIOUS="id" NEXT="autoreg"/>
+        <FIELD NAME="autoreg" TYPE="int" LENGTH="1" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" COMMENT="Indicates whether or not auto-registration is enabled for this course" PREVIOUS="course" NEXT="autoenrol"/>
+        <FIELD NAME="autoenrol" TYPE="int" LENGTH="1" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" COMMENT="Indicates whether or not auto-enrolment is enabled for this course" PREVIOUS="autoreg" NEXT="loginzonepos"/>
+        <FIELD NAME="loginzonepos" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" ENUM="false" COMMENT="Position vector of the LoginZone for this course" PREVIOUS="autoenrol" NEXT="loginzonesize"/>
+        <FIELD NAME="loginzonesize" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" ENUM="false" COMMENT="Size vector of the LoginZone for this course" PREVIOUS="loginzonepos" NEXT="loginzoneregion"/>
+        <FIELD NAME="loginzoneregion" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" ENUM="false" COMMENT="Name of the region where the LoginZone is rezzed" PREVIOUS="loginzonesize" NEXT="loginzoneupdated"/>
+
+        <FIELD NAME="loginzoneupdated" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" COMMENT="Timestamp of the last time the LoginZone was updated" PREVIOUS="loginzoneregion"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Primary key for sloodle_users"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="course" UNIQUE="false" FIELDS="course"/>
+      </INDEXES>
+    </TABLE>
+
+    <TABLE NAME="sloodle_pending_avatars" COMMENT="Lists avatars which are awaiting a link to a Moodle account" PREVIOUS="sloodle_course" NEXT="sloodle_active_object">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" COMMENT="Primary index field" NEXT="uuid"/>
+        <FIELD NAME="uuid" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="UUID of the user agent" PREVIOUS="id" NEXT="avname"/>
+        <FIELD NAME="avname" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="Name of the avatar" PREVIOUS="uuid" NEXT="lst"/>
+        <FIELD NAME="lst" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="The Login Security Token to secure the registration." PREVIOUS="avname" NEXT="timeupdated"/>
+        <FIELD NAME="timeupdated" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" COMMENT="Timestamp for when this entry was created or updated. Entries expire after a certain period." PREVIOUS="lst"/>
+      </FIELDS>
+      <KEYS>
+
+        <KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Primary key for table"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="uuid" UNIQUE="false" FIELDS="uuid"/>
+      </INDEXES>
+    </TABLE>
+    <TABLE NAME="sloodle_active_object" COMMENT="Lists objects which have been authorized to access specific controllers" PREVIOUS="sloodle_pending_avatars" NEXT="sloodle_object_config">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" COMMENT="Primary index field" NEXT="controllerid"/>
+
+        <FIELD NAME="controllerid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" COMMENT="The controller which this object is authorised for." PREVIOUS="id" NEXT="userid"/>
+        <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" COMMENT="ID of the user who authorised this object, directly or indirectly. If 0, then it is not yet authorised." PREVIOUS="controllerid" NEXT="uuid"/>
+        <FIELD NAME="uuid" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="UUID of the object" PREVIOUS="userid" NEXT="password"/>
+        <FIELD NAME="password" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="Object-specific password" PREVIOUS="uuid" NEXT="name"/>
+        <FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="Name of the object" PREVIOUS="password" NEXT="type"/>
+        <FIELD NAME="type" TYPE="char" LENGTH="50" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="Type identifier of the object. Used to determine which configuration script to use." PREVIOUS="name" NEXT="timeupdated"/>
+        <FIELD NAME="timeupdated" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" COMMENT="Timestamp for when this entry was created or updated. Entries expire after a certain period." PREVIOUS="type" NEXT="httpinurl"/>
+        <FIELD NAME="httpinurl" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" ENUM="false" COMMENT="Use this field to save and update the httpin url, which is to be used to send the object configuration data from Sloodle Server code, and potentially other info" PREVIOUS="timeupdated" NEXT="mediakey"/>
+        <FIELD NAME="mediakey" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" ENUM="false" COMMENT="secret key to be used in shared media urls.  This key will be used to identify the object" PREVIOUS="httpinurl" NEXT="lastmessagetimestamp"/>
+
+        <FIELD NAME="lastmessagetimestamp" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" ENUM="false" COMMENT="timestamp of last message sent from the server to this object, this is used in cases where we want to make sure the object has up to date information before giving it more." PREVIOUS="mediakey" NEXT="httpinpassword"/>
+        <FIELD NAME="httpinpassword" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" ENUM="false" COMMENT="secret key used to prove that an httpin request comes from the server" PREVIOUS="lastmessagetimestamp" NEXT="layoutentryid"/>
+        <FIELD NAME="layoutentryid" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" ENUM="false" PREVIOUS="httpinpassword" NEXT="rezzeruuid"/>
+        <FIELD NAME="rezzeruuid" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" ENUM="false" PREVIOUS="layoutentryid" NEXT="position"/>
+        <FIELD NAME="position" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" ENUM="false" PREVIOUS="rezzeruuid" NEXT="rotation"/>
+        <FIELD NAME="rotation" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" ENUM="false" PREVIOUS="position" NEXT="region"/>
+        <FIELD NAME="region" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" ENUM="false" PREVIOUS="rotation"/>
+      </FIELDS>
+      <KEYS>
+
+        <KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Primary key for table"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="uuid" UNIQUE="true" FIELDS="uuid" COMMENT="Objects cannot be authorised more than once, although their authorisation can change." NEXT="idx_active_object_rezzeruuid"/>
+        <INDEX NAME="idx_active_object_rezzeruuid" UNIQUE="false" FIELDS="rezzeruuid" PREVIOUS="uuid"/>
+      </INDEXES>
+    </TABLE>
+    <TABLE NAME="sloodle_object_config" COMMENT="Configuration settings for registered active objects" PREVIOUS="sloodle_active_object" NEXT="sloodle_login_notifications">
+      <FIELDS>
+
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" COMMENT="Primary index field" NEXT="object"/>
+        <FIELD NAME="object" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" COMMENT="Identifies which object this setting refers to." PREVIOUS="id" NEXT="name"/>
+        <FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="Name of the setting" PREVIOUS="object" NEXT="value"/>
+        <FIELD NAME="value" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="Value of the setting" PREVIOUS="name"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Primary key for table"/>
+      </KEYS>
+      <INDEXES>
+
+        <INDEX NAME="object-name" UNIQUE="true" FIELDS="object, name" COMMENT="Setting names must be unique for a given object"/>
+      </INDEXES>
+    </TABLE>
+    <TABLE NAME="sloodle_login_notifications" COMMENT="Contains the details of auto-registration login details which are to be sent" PREVIOUS="sloodle_object_config" NEXT="sloodle_layout">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" COMMENT="Primary index field" NEXT="destination"/>
+        <FIELD NAME="destination" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="UUID of the object which will relay the notification" PREVIOUS="id" NEXT="avatar"/>
+        <FIELD NAME="avatar" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="UUID of the user agent to whom the notification is being sent" PREVIOUS="destination" NEXT="username"/>
+        <FIELD NAME="username" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="Username of the new account" PREVIOUS="avatar" NEXT="password"/>
+
+        <FIELD NAME="password" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="Plaintext password of the new account" PREVIOUS="username"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Primary key for table"/>
+      </KEYS>
+    </TABLE>
+    <TABLE NAME="sloodle_layout" COMMENT="Defines classroom layout profiles" PREVIOUS="sloodle_login_notifications" NEXT="sloodle_layout_entry">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" NEXT="course"/>
+
+        <FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" COMMENT="Identifies which course this profile belongs to. Relates to the Moodle course table" PREVIOUS="id" NEXT="name"/>
+        <FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="The name of this layout profile" PREVIOUS="course" NEXT="timeupdated"/>
+        <FIELD NAME="timeupdated" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" COMMENT="Timestamps for when this profile was last updated" PREVIOUS="name" NEXT="controllerid"/>
+        <FIELD NAME="controllerid" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" ENUM="false" PREVIOUS="timeupdated"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Primary key for table"/>
+      </KEYS>
+      <INDEXES>
+
+        <INDEX NAME="course-name" UNIQUE="true" FIELDS="course, name" COMMENT="Profile names must be unique within each course"/>
+      </INDEXES>
+    </TABLE>
+    <TABLE NAME="sloodle_layout_entry" COMMENT="Defines an object entry in a classroom layout" PREVIOUS="sloodle_layout" NEXT="sloodle_layout_entry_config">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" NEXT="layout"/>
+        <FIELD NAME="layout" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" COMMENT="Identifies which layout profile this entry belongs to" PREVIOUS="id" NEXT="name"/>
+        <FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="The name of the object" PREVIOUS="layout" NEXT="position"/>
+        <FIELD NAME="position" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="The position of the object, as a 3d SL-style vector" PREVIOUS="name" NEXT="rotation"/>
+
+        <FIELD NAME="rotation" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="The rotation of the object, stored as a 3d SL-style vector of Euler angles" PREVIOUS="position"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Primary key for table"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="layout" UNIQUE="false" FIELDS="layout"/>
+      </INDEXES>
+    </TABLE>
+
+    <TABLE NAME="sloodle_layout_entry_config" COMMENT="Defines configuration parameters for layout entries, providing defaults for active_object configurations" PREVIOUS="sloodle_layout_entry" NEXT="sloodle_loginzone_allocation">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" NEXT="layout_entry"/>
+        <FIELD NAME="layout_entry" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" ENUM="false" COMMENT="Foreign key of layout_entry item" PREVIOUS="id" NEXT="name"/>
+        <FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" ENUM="false" COMMENT="name of paramter" PREVIOUS="layout_entry" NEXT="value"/>
+        <FIELD NAME="value" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" ENUM="false" PREVIOUS="name"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+
+      </KEYS>
+    </TABLE>
+    <TABLE NAME="sloodle_loginzone_allocation" COMMENT="Gives allocation of a loginzone for a given user" PREVIOUS="sloodle_layout_entry_config" NEXT="sloodle_user_object">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" NEXT="course"/>
+        <FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" COMMENT="Identifies which course this allocation is for" PREVIOUS="id" NEXT="userid"/>
+        <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" COMMENT="Identifies which user this allocation is for" PREVIOUS="course" NEXT="position"/>
+        <FIELD NAME="position" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="The position of the allocation, relative to the LoginZone centre" PREVIOUS="userid" NEXT="timecreated"/>
+        <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" COMMENT="Timestamps for when this allocation was created" PREVIOUS="position"/>
+
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Primary key for table"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="course" UNIQUE="false" FIELDS="course" NEXT="userid"/>
+        <INDEX NAME="userid" UNIQUE="true" FIELDS="userid" COMMENT="Each user can have only one allocation" PREVIOUS="course"/>
+      </INDEXES>
+    </TABLE>
+
+    <TABLE NAME="sloodle_user_object" COMMENT="Lists user-centric objects which individual users have authorised for use with their own accounts, such as blogging" PREVIOUS="sloodle_loginzone_allocation" NEXT="sloodle_presenter">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" COMMENT="Primary index field" NEXT="avuuid"/>
+        <FIELD NAME="avuuid" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="UUID of the avatar" PREVIOUS="id" NEXT="objuuid"/>
+        <FIELD NAME="objuuid" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="UUID of the object" PREVIOUS="avuuid" NEXT="objname"/>
+        <FIELD NAME="objname" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="Name of the object" PREVIOUS="objuuid" NEXT="password"/>
+        <FIELD NAME="password" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" COMMENT="Object- and avatar-specific password" PREVIOUS="objname" NEXT="authorised"/>
+        <FIELD NAME="authorised" TYPE="int" LENGTH="1" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" COMMENT="Indicates whether or not this object has been authorised.." PREVIOUS="password" NEXT="timeupdated"/>
+        <FIELD NAME="timeupdated" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" COMMENT="Timestamp for when this entry was created or updated. Entries expire after a certain period." PREVIOUS="authorised"/>
+
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Primary key for table"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="objuuid" UNIQUE="true" FIELDS="objuuid" COMMENT="User objects can only be authorised against one avatar at a time."/>
+      </INDEXES>
+    </TABLE>
+    <TABLE NAME="sloodle_presenter" COMMENT="Each record stores data about a Presenter instance." PREVIOUS="sloodle_user_object" NEXT="sloodle_presenter_entry">
+
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" NEXT="sloodleid"/>
+        <FIELD NAME="sloodleid" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" ENUM="false" COMMENT="Indicates which SLOODLE module instance this Presenter information relates to." PREVIOUS="id" NEXT="framewidth"/>
+        <FIELD NAME="framewidth" TYPE="int" LENGTH="4" NOTNULL="false" UNSIGNED="true" DEFAULT="512" SEQUENCE="false" ENUM="false" COMMENT="Indicates the width of the frame for displaying content in Moodle." PREVIOUS="sloodleid" NEXT="frameheight"/>
+        <FIELD NAME="frameheight" TYPE="int" LENGTH="4" NOTNULL="false" UNSIGNED="true" DEFAULT="512" SEQUENCE="false" ENUM="false" COMMENT="Indicates the height of the frame for displaying content in Moodle." PREVIOUS="framewidth"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id" NEXT="sloodleid"/>
+        <KEY NAME="sloodleid" TYPE="unique" FIELDS="sloodleid" PREVIOUS="primary"/>
+
+      </KEYS>
+    </TABLE>
+    <TABLE NAME="sloodle_presenter_entry" COMMENT="sloodle_presenter_entry table retrofitted from MySQL" PREVIOUS="sloodle_presenter" NEXT="sloodle_logs">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" NEXT="sloodleid"/>
+        <FIELD NAME="sloodleid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" PREVIOUS="id" NEXT="name"/>
+        <FIELD NAME="name" TYPE="text" LENGTH="small" NOTNULL="false" SEQUENCE="false" ENUM="false" COMMENT="Specifies the name for this entry in the presentation." PREVIOUS="sloodleid" NEXT="source"/>
+        <FIELD NAME="source" TYPE="text" LENGTH="medium" NOTNULL="true" SEQUENCE="false" ENUM="false" PREVIOUS="name" NEXT="type"/>
+        <FIELD NAME="type" TYPE="char" LENGTH="255" NOTNULL="true" DEFAULT="web" SEQUENCE="false" ENUM="false" PREVIOUS="source" NEXT="ordering"/>
+
+        <FIELD NAME="ordering" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" PREVIOUS="type"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="mdl_sloopresentr_slo_ix" UNIQUE="false" FIELDS="sloodleid" NEXT="mdl_sloopresentr_typ_ix"/>
+        <INDEX NAME="mdl_sloopresentr_typ_ix" UNIQUE="false" FIELDS="type" PREVIOUS="mdl_sloopresentr_slo_ix"/>
+      </INDEXES>
+
+    </TABLE>
+    <TABLE NAME="sloodle_logs" COMMENT="sloodle log for avatar tracking" PREVIOUS="sloodle_presenter_entry" NEXT="sloodle_currency_types">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="11" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" NEXT="userid"/>
+        <FIELD NAME="userid" TYPE="int" LENGTH="11" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" PREVIOUS="id" NEXT="avuuid"/>
+        <FIELD NAME="avuuid" TYPE="char" LENGTH="50" NOTNULL="false" SEQUENCE="false" ENUM="false" PREVIOUS="userid" NEXT="avname"/>
+        <FIELD NAME="avname" TYPE="char" LENGTH="70" NOTNULL="false" SEQUENCE="false" ENUM="false" PREVIOUS="avuuid" NEXT="course"/>
+
+        <FIELD NAME="course" TYPE="int" LENGTH="11" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" PREVIOUS="avname" NEXT="action"/>
+        <FIELD NAME="action" TYPE="char" LENGTH="150" NOTNULL="false" SEQUENCE="false" ENUM="false" PREVIOUS="course" NEXT="url"/>
+        <FIELD NAME="url" TYPE="char" LENGTH="150" NOTNULL="false" SEQUENCE="false" ENUM="false" PREVIOUS="action" NEXT="slurl"/>
+        <FIELD NAME="slurl" TYPE="char" LENGTH="150" NOTNULL="false" SEQUENCE="false" ENUM="false" PREVIOUS="url" NEXT="timemodified"/>
+        <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" PREVIOUS="slurl"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+      </KEYS>
+
+      <INDEXES>
+        <INDEX NAME="mdl_sloostiptran_slo_ix" UNIQUE="false" FIELDS="id"/>
+      </INDEXES>
+    </TABLE>
+    <TABLE NAME="sloodle_currency_types" COMMENT="sloodle currency types" PREVIOUS="sloodle_logs" NEXT="sloodle_award_points">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="11" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" NEXT="name"/>
+        <FIELD NAME="name" TYPE="char" LENGTH="50" NOTNULL="true" SEQUENCE="false" ENUM="false" PREVIOUS="id" NEXT="timemodified"/>
+        <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" PREVIOUS="name" NEXT="imageurl"/>
+        <FIELD NAME="imageurl" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" ENUM="false" COMMENT="image url of the icon for this currency" PREVIOUS="timemodified" NEXT="displayorder"/>
+        <FIELD NAME="displayorder" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" ENUM="false" COMMENT="displayorder is used for views when displaying currencies." PREVIOUS="imageurl"/>
+
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="mdl_sloostiptran_slo_ix" UNIQUE="false" FIELDS="id"/>
+      </INDEXES>
+    </TABLE>
+    <TABLE NAME="sloodle_award_points" COMMENT="Awards Points" PREVIOUS="sloodle_currency_types" NEXT="sloodle_award_rounds">
+
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" NEXT="userid"/>
+        <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" PREVIOUS="id" NEXT="currencyid"/>
+        <FIELD NAME="currencyid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" PREVIOUS="userid" NEXT="amount"/>
+        <FIELD NAME="amount" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="false" SEQUENCE="false" ENUM="false" COMMENT="Amount awarded - positive for a debit, negative for a credit." PREVIOUS="currencyid" NEXT="timeawarded"/>
+        <FIELD NAME="timeawarded" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" PREVIOUS="amount" NEXT="roundid"/>
+        <FIELD NAME="roundid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" COMMENT="ID of an entry in mdl_sloodle_award_rounds" PREVIOUS="timeawarded" NEXT="description"/>
+        <FIELD NAME="description" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" ENUM="false" COMMENT="A description of why the points were awarded." PREVIOUS="roundid"/>
+      </FIELDS>
+
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id" NEXT="poiroufk"/>
+        <KEY NAME="poiroufk" TYPE="foreign" FIELDS="roundid" REFTABLE="sloodle_award_rounds" REFFIELDS="id" COMMENT="This is a foreign key in the rounds table" PREVIOUS="primary" NEXT="poiusefk"/>
+        <KEY NAME="poiusefk" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id" COMMENT="foreign key of the userid in the user table" PREVIOUS="poiroufk" NEXT="poicurfk"/>
+        <KEY NAME="poicurfk" TYPE="foreign" FIELDS="currencyid" REFTABLE="sloodle_award_currency" REFFIELDS="id" COMMENT="Foreign key referencing the currencyid in the currency table" PREVIOUS="poiusefk"/>
+      </KEYS>
+    </TABLE>
+    <TABLE NAME="sloodle_award_rounds" COMMENT="A time-limited period, which may be tied to a controller. Allows us to filter for points awarded during a particular round or sub-section of a game or simulation." PREVIOUS="sloodle_award_points" NEXT="sloodle_activity_tool">
+      <FIELDS>
+
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" NEXT="timestarted"/>
+        <FIELD NAME="timestarted" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" COMMENT="Timestamp for start or a round, or 0 for not yet started." PREVIOUS="id" NEXT="timeended"/>
+        <FIELD NAME="timeended" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" COMMENT="Timestamp for the end of the round, or 0 for not yet ended." PREVIOUS="timestarted" NEXT="name"/>
+        <FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" ENUM="false" COMMENT="Title of round for display purposes, or null if none is set." PREVIOUS="timeended" NEXT="controllerid"/>
+        <FIELD NAME="controllerid" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" ENUM="false" COMMENT="The ID of the controller to which the round is tied, or null for all controllers on the site." PREVIOUS="name" NEXT="courseid"/>
+        <FIELD NAME="courseid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" COMMENT="The courseid is usually available via the controllerid, unless the controller has been deleted" PREVIOUS="controllerid"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+
+      </KEYS>
+    </TABLE>
+    <TABLE NAME="sloodle_activity_tool" COMMENT="sloodle_activity_tool table retrofitted from MySQL" PREVIOUS="sloodle_award_rounds" NEXT="sloodle_activity_tracker">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" NEXT="trackerid"/>
+        <FIELD NAME="trackerid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" PREVIOUS="id" NEXT="uuid"/>
+        <FIELD NAME="uuid" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" PREVIOUS="trackerid" NEXT="description"/>
+        <FIELD NAME="description" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" PREVIOUS="uuid" NEXT="taskname"/>
+        <FIELD NAME="taskname" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" PREVIOUS="description" NEXT="name"/>
+
+        <FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" PREVIOUS="taskname" NEXT="type"/>
+        <FIELD NAME="type" TYPE="char" LENGTH="50" NOTNULL="true" SEQUENCE="false" ENUM="false" PREVIOUS="name" NEXT="timeupdated"/>
+        <FIELD NAME="timeupdated" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" PREVIOUS="type"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id" NEXT="mdl_slooactitool_uui_uix"/>
+        <KEY NAME="mdl_slooactitool_uui_uix" TYPE="unique" FIELDS="uuid" PREVIOUS="primary"/>
+      </KEYS>
+    </TABLE>
+
+    <TABLE NAME="sloodle_activity_tracker" COMMENT="sloodle_activity_tracker table retrofitted from MySQL" PREVIOUS="sloodle_activity_tool" NEXT="sloodle_tracker">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" NEXT="trackerid"/>
+        <FIELD NAME="trackerid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" PREVIOUS="id" NEXT="objuuid"/>
+        <FIELD NAME="objuuid" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" PREVIOUS="trackerid" NEXT="avuuid"/>
+        <FIELD NAME="avuuid" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" ENUM="false" PREVIOUS="objuuid" NEXT="timeupdated"/>
+        <FIELD NAME="timeupdated" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" PREVIOUS="avuuid"/>
+      </FIELDS>
+      <KEYS>
+
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+      </KEYS>
+    </TABLE>
+    <TABLE NAME="sloodle_tracker" COMMENT="Each record provides secondary data about a SLOODLE module instance of the Tracker type - currently just a placeholder table" PREVIOUS="sloodle_activity_tracker">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" NEXT="sloodleid"/>
+        <FIELD NAME="sloodleid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" PREVIOUS="id"/>
+      </FIELDS>
+      <KEYS>
+
+        <KEY NAME="primary" TYPE="primary" FIELDS="id" NEXT="sloodleid"/>
+        <KEY NAME="sloodleid" TYPE="unique" FIELDS="sloodleid" PREVIOUS="primary"/>
+      </KEYS>
+    </TABLE>
+  </TABLES>
+</XMLDB>

+ 788 - 0
db/upgrade.php

@@ -0,0 +1,788 @@
+<?php
+
+require(dirname(__FILE__).'/../lib/db.php');
+
+/**
+* Database upgrade script for Moodle's db-independent XMLDB.
+* @ignore
+* @package sloodle
+*/
+
+
+// This file keeps track of upgrades to
+// the sloodle module
+//
+// Sometimes, changes between versions involve
+// alterations to database structures and other
+// major things that may break installations.
+//
+// The upgrade function in this file will attempt
+// to perform all the necessary actions to upgrade
+// your older installation to the current version.
+//
+// If there's something it cannot do itself, it
+// will tell you what you need to do.
+//
+// The commands in here will all be database-neutral,
+// using the functions defined in lib/ddllib.php
+
+function xmldb_sloodle_upgrade($oldversion=0) {
+
+    global $CFG, $THEME, $db;
+    $result = true;
+    
+    // Note: any upgrade to Sloodle 0.3 is a major process, due to the huge change of architecture.
+    // As such, the only data worth preserving is the avatar table ('sloodle_users').
+    
+    // All other tables will be dropped and re-inserted.
+    
+    // Is this an upgrade from pre-0.3?
+    if ($result && $oldversion < 2008052800) {
+        // Drop all other tables
+        echo "Dropping old tables<br/>";
+        // (We can ignore failed drops)
+        
+    /// Drop 'sloodle' table
+        $table = new XMLDBTable('sloodle');
+        drop_table($table);
+        
+    /// Drop 'sloodle_config' table
+        $table = new XMLDBTable('sloodle_config');
+        
+        
+    /// Drop 'sloodle_active_object' table
+        $table = new XMLDBTable('sloodle_active_object');
+        drop_table($table);
+        
+    /// Drop 'sloodle_classroom_setup_profile' table
+        $table = new XMLDBTable('sloodle_classroom_setup_profile');
+        drop_table($table);
+        
+    /// Drop 'sloodle_classroom_setup_profile_entry' table
+        $table = new XMLDBTable('sloodle_classroom_setup_profile_entry');
+        drop_table($table);
+        
+        
+        // Insert all the new tables
+        echo "Inserting new tables...<br/>";
+        
+        
+    /// Insert 'sloodle' table
+        echo " - sloodle<br/>";
+        $table = new XMLDBTable('sloodle');
+
+        $table->addFieldInfo('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
+        $table->addFieldInfo('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0');
+        $table->addFieldInfo('type', XMLDB_TYPE_CHAR, '50', null, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('intro', XMLDB_TYPE_TEXT, 'medium', null, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('timecreated', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0');
+        $table->addFieldInfo('timemodified', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0');
+
+        $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+        $table->addIndexInfo('course', XMLDB_INDEX_NOTUNIQUE, array('course'));
+
+        $result = $result && create_table($table);
+        if (!$result) echo "error<br/>";
+        
+        
+    /// Insert 'sloodle_controller' table
+        echo " - sloodle_controller<br/>";
+        $table = new XMLDBTable('sloodle_controller');
+
+        $table->addFieldInfo('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
+        $table->addFieldInfo('sloodleid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0');
+        $table->addFieldInfo('enabled', XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0');
+        $table->addFieldInfo('password', XMLDB_TYPE_CHAR, '9', null, null, null, null, null, null);
+
+        $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+        $table->addIndexInfo('sloodleid', XMLDB_INDEX_UNIQUE, array('sloodleid'));
+
+        $result = $result && create_table($table);
+        if (!$result) echo "error<br/>";
+        
+        
+    /// Insert 'sloodle_distributor' table
+        echo " - sloodle_distributor<br/>";
+        $table = new XMLDBTable('sloodle_distributor');
+
+        $table->addFieldInfo('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
+        $table->addFieldInfo('sloodleid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0');
+        $table->addFieldInfo('channel', XMLDB_TYPE_CHAR, '36', null, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('timeupdated', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0');
+
+        $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+        $result = $result && create_table($table);
+        if (!$result) echo "error<br/>";
+        
+        
+    /// Insert 'sloodle_distributor_entry' table
+        echo " - sloodle_distributor_entry<br/>";
+        $table = new XMLDBTable('sloodle_distributor_entry');
+
+        $table->addFieldInfo('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
+        $table->addFieldInfo('distributorid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0');
+        $table->addFieldInfo('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);
+
+        $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+        $result = $result && create_table($table);
+        if (!$result) echo "error<br/>";
+        
+        
+    /// Insert 'sloodle_course' table
+        echo " - sloodle_course<br/>";
+        $table = new XMLDBTable('sloodle_course');
+
+        $table->addFieldInfo('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
+        $table->addFieldInfo('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0');
+        $table->addFieldInfo('autoreg', XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0');
+        $table->addFieldInfo('autoenrol', XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0');
+        $table->addFieldInfo('loginzonepos', XMLDB_TYPE_CHAR, '255', null, null, null, null, null, null);
+        $table->addFieldInfo('loginzonesize', XMLDB_TYPE_CHAR, '255', null, null, null, null, null, null);
+        $table->addFieldInfo('loginzoneregion', XMLDB_TYPE_CHAR, '255', null, null, null, null, null, null);
+        $table->addFieldInfo('loginzoneupdated', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0');
+
+        $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+        $table->addIndexInfo('course', XMLDB_INDEX_NOTUNIQUE, array('course'));
+
+        $result = $result && create_table($table);
+        if (!$result) echo "error<br/>";
+        
+        
+    /// Insert 'sloodle_pending_avatars' table
+        echo " - sloodle_pending_avatar<br/>";
+        $table = new XMLDBTable('sloodle_pending_avatars');
+
+        $table->addFieldInfo('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
+        $table->addFieldInfo('uuid', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('avname', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('lst', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('timeupdated', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0');
+
+        $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+        $table->addIndexInfo('uuid', XMLDB_INDEX_NOTUNIQUE, array('uuid'));
+
+        $result = $result && create_table($table);
+        if (!$result) echo "error<br/>";
+        
+        
+    /// Insert 'sloodle_active_object' table
+        echo " - sloodle_active_object<br/>";
+        $table = new XMLDBTable('sloodle_active_object');
+
+        $table->addFieldInfo('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
+        $table->addFieldInfo('controllerid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('userid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('uuid', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('password', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('type', XMLDB_TYPE_CHAR, '50', null, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('timeupdated', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0');
+
+        $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+        $table->addIndexInfo('uuid', XMLDB_INDEX_UNIQUE, array('uuid'));
+        
+        $result = $result && create_table($table);
+        if (!$result) echo "error<br/>";
+        
+        
+    /// Insert 'sloodle_object_config' table
+        echo " - sloodle_object_config<br/>";
+        $table = new XMLDBTable('sloodle_object_config');
+
+        $table->addFieldInfo('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
+        $table->addFieldInfo('object', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('value', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);
+
+        $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+        $table->addIndexInfo('object-name', XMLDB_INDEX_UNIQUE, array('object', 'name'));
+
+        $result = $result && create_table($table);
+        if (!$result) echo "error<br/>";
+        
+        
+    /// Insert 'sloodle_login_notifications' table
+        echo " - sloodle_login_notifications<br/>";
+        $table = new XMLDBTable('sloodle_login_notifications');
+
+        $table->addFieldInfo('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
+        $table->addFieldInfo('destination', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('avatar', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('username', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('password', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);
+
+        $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+        $result = $result && create_table($table);
+        if (!$result) echo "error<br/>";
+        
+        
+    /// Insert 'sloodle_layout' table
+        echo " - sloodle_layout<br/>";
+        $table = new XMLDBTable('sloodle_layout');
+
+        $table->addFieldInfo('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
+        $table->addFieldInfo('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0');
+        $table->addFieldInfo('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('timeupdated', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0');
+
+        $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+        $table->addIndexInfo('course-name', XMLDB_INDEX_UNIQUE, array('course', 'name'));
+
+        $result = $result && create_table($table);
+        
+        
+        if (!$result) echo "error<br/>";
+        
+        
+    /// Insert 'sloodle_layout_entry' table
+        echo " - sloodle_layout_entry<br/>";
+        $table = new XMLDBTable('sloodle_layout_entry');
+
+        $table->addFieldInfo('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
+        $table->addFieldInfo('layout', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0');
+        $table->addFieldInfo('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('position', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('rotation', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);
+
+        $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+        $table->addIndexInfo('layout', XMLDB_INDEX_NOTUNIQUE, array('layout'));
+
+        $result = $result && create_table($table);
+        if (!$result) echo "error<br/>";
+        
+        
+    /// Insert 'sloodle_loginzone_allocation' table
+        echo " - sloodle_loginzone_allocation<br/>";
+        $table = new XMLDBTable('sloodle_loginzone_allocation');
+
+        $table->addFieldInfo('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
+        $table->addFieldInfo('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0');
+        $table->addFieldInfo('userid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0');
+        $table->addFieldInfo('position', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('timecreated', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0');
+
+        $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+        $table->addIndexInfo('course', XMLDB_INDEX_NOTUNIQUE, array('course'));
+        $table->addIndexInfo('userid', XMLDB_INDEX_UNIQUE, array('userid'));
+
+        $result = $result && create_table($table);
+        if (!$result) echo "error<br/>";
+        
+        
+    /// Insert 'sloodle_user_object' table
+        echo " - sloodle_user_object<br/>";
+        $table = new XMLDBTable('sloodle_user_object');
+
+        $table->addFieldInfo('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
+        $table->addFieldInfo('avuuid', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('objuuid', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('objname', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('password', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('authorised', XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0');
+        $table->addFieldInfo('timeupdated', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0');
+
+        $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+        $table->addIndexInfo('objuuid', XMLDB_INDEX_UNIQUE, array('objuuid'));
+
+        $result = $result && create_table($table);
+        if (!$result) echo "error<br/>";
+                
+        
+    /// Upgrade sloodle_users table
+        echo "Upgrading sloodle_users table...<br/>";
+        $table = new XMLDBTable('sloodle_users');
+        
+        echo " - dropping old fields<br/>";
+        // Drop the loginzone fields (we don't care about success or otherwise... not all fields will be present in all versions)
+        $field = new XMLDBField('loginposition');
+        drop_field($table, $field);
+        $field = new XMLDBField('loginpositionexpires');
+        drop_field($table, $field);
+        $field = new XMLDBField('loginpositionregion');
+        drop_field($table, $field);
+        $field = new XMLDBField('loginsecuritytoken');
+        drop_field($table, $field);
+        // Drop the old 'online' field (was going to be a boolean, but was never used)
+        $field = new XMLDBField('online');
+        drop_field($table, $field);
+        
+        // Add the new 'lastactive' field
+        echo " - adding lastactive field<br/>";
+        $field = new XMLDBField('lastactive');
+        $field->setAttributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0', 'avname');
+        $result = $result && add_field($table, $field);
+        if (!$result) echo "error<br/>";
+        
+        
+    /// Purge redundant avatar entries
+        echo "Purging redundant avatar entries...<br/>";
+        $sql = "    DELETE FROM {$CFG->prefix}sloodle_users
+                    WHERE userid = 0 OR uuid = '' OR avname = ''
+        ";
+        execute_sql($sql);
+    }
+    
+    
+    if ($result && $oldversion < 2009020201) {
+
+    /// Define table sloodle_presenter_entry to be created
+        $table = new XMLDBTable('sloodle_presenter_entry');
+
+    /// Adding fields to table sloodle_presenter_entry
+        $table->addFieldInfo('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
+        $table->addFieldInfo('sloodleid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('source', XMLDB_TYPE_TEXT, 'medium', null, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('type', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, 'web');
+        $table->addFieldInfo('ordering', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+
+    /// Adding keys to table sloodle_presenter_entry
+        $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+    /// Adding indexes to table sloodle_presenter_entry
+        $table->addIndexInfo($CFG->prefix.'sloopresentr_slo_ix', XMLDB_INDEX_NOTUNIQUE, array('sloodleid'));
+        $table->addIndexInfo($CFG->prefix.'sloopresentr_typ_ix', XMLDB_INDEX_NOTUNIQUE, array('type'));
+
+    /// Launch create table for sloodle_presenter_entry
+        $result = $result && create_table($table);
+    }
+
+    if ($result && $oldversion < 2009020701) {
+
+    /// Define table sloodle_layout_entry_config to be created
+        $table = new XMLDBTable('sloodle_layout_entry_config');
+
+    /// Adding fields to table sloodle_layout_entry_config
+        $table->addFieldInfo('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
+        $table->addFieldInfo('layout_entry', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null, null, null);
+        $table->addFieldInfo('name', XMLDB_TYPE_CHAR, '255', null, null, null, null, null, null);
+        $table->addFieldInfo('value', XMLDB_TYPE_CHAR, '255', null, null, null, null, null, null);
+
+    /// Adding keys to table sloodle_layout_entry_config
+        $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+    /// Launch create table for sloodle_layout_entry_config
+        $result = $result && create_table($table);
+    }
+
+    // Add a name field to the Presenter entries.
+    if ($result && $oldversion < 2009031002) {
+    /// Define field name to be added to sloodle_presenter_entry
+        $table = new XMLDBTable('sloodle_presenter_entry');
+        $field = new XMLDBField('name');
+        $field->setAttributes(XMLDB_TYPE_TEXT, 'small', null, null, null, null, null, null, 'sloodleid');
+
+    /// Launch add field name
+        $result = $result && add_field($table, $field);
+    }
+
+    // Add the SLOODLE Presenter table (we previously only had entries, but no data about the Presenter itself.)
+    if ($result && $oldversion < 2009031003) {
+
+    /// Define table sloodle_presenter to be created
+        $table = new XMLDBTable('sloodle_presenter');
+
+    /// Adding fields to table sloodle_presenter
+        $table->addFieldInfo('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
+        $table->addFieldInfo('sloodleid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null, null, null);
+        $table->addFieldInfo('framewidth', XMLDB_TYPE_INTEGER, '4', XMLDB_UNSIGNED, null, null, null, null, '512');
+        $table->addFieldInfo('frameheight', XMLDB_TYPE_INTEGER, '4', XMLDB_UNSIGNED, null, null, null, null, '512');
+
+    /// Adding keys to table sloodle_presenter
+        $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
+        $table->addKeyInfo('sloodleid', XMLDB_KEY_UNIQUE, array('sloodleid'));
+
+    /// Launch create table for sloodle_presenter
+        $result = $result && create_table($table);
+        if (!$result) return $result;
+
+        // For sake of people who installed a test version of SLOODLE 0.4, we need to automatically create secondary Presenter instances.
+        // These would normally be created when creating an instance of the module, but this table didn't exist during test versions.
+        // Go through all SLOODLE modules with type "Presenter" and add an empty secondary entry on their behalf, with default values.
+        $sloodlerecords = sloodle_get_records('sloodle', 'type', 'presenter');
+        if (!$sloodlerecords) $sloodlerecords = array();
+        foreach ($sloodlerecords as $sr) {
+            // Construct a default presenter instance for it
+            $presenterrecord = new stdClass();
+            $presenterrecord->sloodleid = $sr->id;
+            sloodle_insert_record('sloodle_presenter', $presenterrecord);
+        }
+
+    }
+
+    if ($result && $oldversion < 2009110500) {
+        echo "Converting Presenter slide type IDs... ";
+        // Standardize any Presenter slides to use type names "image", "web", and "video".
+        // The slide plugins for 1.0 initially used class names, like SloodlePluginPresenterSlideImage.
+        // That's laborious and necessary, so we're reverting back to the original type names.
+        $allslides = sloodle_get_records('sloodle_presenter_entry');
+        $numupdated = 0;
+        if ($allslides) {
+            foreach ($allslides as $slide) {
+                // Update the type name if necessary
+                $updated = true;
+                switch (strtolower($slide->type)) {
+                    // Image slides
+                    case 'sloodlepluginpresenterslideimage': case 'presenterslideimage':
+                        $slide->type = 'image';
+                        break;
+                    // Web slides
+                    case 'sloodlepluginpresenterslideweb': case 'presenterslideweb':
+                        $slide->type = 'web';
+                        break;
+                    // Video slides
+                    case 'sloodlepluginpresenterslidevideo': case 'presenterslidevideo':
+                        $slide->type = 'video';
+                        break;
+                    // Unrecognised type
+                    default:
+                        $updated = false;
+                        break;
+                }
+                
+                // Update the database record
+                if ($updated) {
+                    sloodle_update_record('sloodle_presenter_entry', $slide);
+                    $numupdated++;
+                }
+            }
+        }
+        echo "{$numupdated} slide(s) updated.<br/>";
+    }
+
+    if ($result && !table_exists(new XMLDBTable('sloodle_currency_types'))) {     
+        $table = new XMLDBTable('sloodle_currency_types');
+         echo "creating new currency table for site wide virtual currency<br/>";               
+        $field = new XMLDBField('id');
+        $field->setAttributes(XMLDB_TYPE_INTEGER, '11', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null, null);    
+        $table->addField($field);
+        $field = new XMLDBField('name');
+        $field->setAttributes(XMLDB_TYPE_CHAR, '50', null, XMLDB_NOTNULL, null, null, null, null, 'id');
+        $table->addField($field);        
+        $field = new XMLDBField('timemodified');
+        $field->setAttributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0', 'units');
+        $table->addField($field);   
+        $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
+        $result = $result && create_table($table);           
+
+        $newCurrency = new stdClass();
+        $newCurrency->name="Credits";
+        if (sloodle_insert_record('sloodle_currency_types',$newCurrency))echo "Added Credits currency: OK<br>";
+    }
+    
+
+ if ($result && $oldversion < 2010110311) {      
+        $table = new XMLDBTable('sloodle_users'); 
+         // Add the new 'profilepic' field
+         echo " - adding \'profilepic\' field<br/>";
+         $field = new XMLDBField('profilepic');
+         $field->setAttributes(XMLDB_TYPE_CHAR, '255', null, null, null, null, null, '', 'avname');
+         $result = $result && add_field($table,$field);                    
+ }
+
+    if ($result && $oldversion < 2010110501) {
+
+    /// Define field httpinurl to be added to sloodle_active_object
+        $table = new XMLDBTable('sloodle_active_object');
+        $field = new XMLDBField('httpinurl');
+        $field->setAttributes(XMLDB_TYPE_CHAR, '255', null, null, null, null, null, null, 'timeupdated');
+
+    /// Launch add field httpinurl
+        $result = $result && add_field($table, $field);
+    }
+
+    if ($result && $oldversion < 2010121703) {
+
+    /// Define field layoutentryid to be added to sloodle_active_object
+        $table = new XMLDBTable('sloodle_active_object');
+        $field = new XMLDBField('layoutentryid');
+        $field->setAttributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null, null, null, 'httpinurl');
+
+    /// Launch add field layoutentryid
+        $result = $result && add_field($table, $field);
+
+    /// Define field rezzeruuid to be added to sloodle_active_object
+        $table = new XMLDBTable('sloodle_active_object');
+        $field = new XMLDBField('rezzeruuid');
+        $field->setAttributes(XMLDB_TYPE_CHAR, '255', null, null, null, null, null, null, 'layoutentryid');
+
+    /// Launch add field rezzeruuid
+        $result = $result && add_field($table, $field);
+
+    /// Define field position to be added to sloodle_active_object
+        $table = new XMLDBTable('sloodle_active_object');
+        $field = new XMLDBField('position');
+        $field->setAttributes(XMLDB_TYPE_CHAR, '255', null, null, null, null, null, null, 'rezzeruuid');
+
+    /// Launch add field position
+        $result = $result && add_field($table, $field);
+
+    /// Define field rotation to be added to sloodle_active_object
+        $table = new XMLDBTable('sloodle_active_object');
+        $field = new XMLDBField('rotation');
+        $field->setAttributes(XMLDB_TYPE_CHAR, '255', null, null, null, null, null, null, 'position');
+
+    /// Launch add field rotation
+        $result = $result && add_field($table, $field);
+
+    /// Define field region to be added to sloodle_active_object
+        $table = new XMLDBTable('sloodle_active_object');
+        $field = new XMLDBField('region');
+        $field->setAttributes(XMLDB_TYPE_CHAR, '255', null, null, null, null, null, null, 'rotation');
+
+    /// Launch add field region
+        $result = $result && add_field($table, $field);
+
+  /// Define index rezzeruuid (not unique) to be added to sloodle_active_object
+        $table = new XMLDBTable('sloodle_active_object');
+        $index = new XMLDBIndex('rezzeruuid');
+        $index->setAttributes(XMLDB_INDEX_NOTUNIQUE, array('rezzeruuid'));
+
+    /// Launch add index rezzeruuid
+        $result = $result && add_index($table, $index);
+
+    /// Define index layoutentryid (not unique) to be added to sloodle_active_object
+        $table = new XMLDBTable('sloodle_active_object');
+        $index = new XMLDBIndex('layoutentryid');
+        $index->setAttributes(XMLDB_INDEX_NOTUNIQUE, array('layoutentryid'));
+
+    /// Launch add index layoutentryid
+        $result = $result && add_index($table, $index);
+
+
+    }
+
+    if ($result && $oldversion < 2011062700) {
+
+    /// Define table sloodle_award_rounds to be created
+        $table = new XMLDBTable('sloodle_award_rounds');
+
+    /// Adding fields to table sloodle_award_rounds
+        $table->addFieldInfo('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
+        $table->addFieldInfo('timestarted', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0');
+        $table->addFieldInfo('timeended', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0');
+        $table->addFieldInfo('name', XMLDB_TYPE_CHAR, '255', null, null, null, null, null, null);
+        $table->addFieldInfo('controllerid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null, null, null);
+
+    /// Adding keys to table sloodle_award_rounds
+        $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+    /// Launch create table for sloodle_award_rounds
+        $result = $result && create_table($table);
+    }
+
+    if ($result && $oldversion < 2011062700) {
+
+    /// Define table sloodle_award_points to be created
+        $table = new XMLDBTable('sloodle_award_points');
+
+    /// Adding fields to table sloodle_award_points
+        $table->addFieldInfo('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
+        $table->addFieldInfo('userid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('currencyid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('amount', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('timeawarded', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+        $table->addFieldInfo('roundid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+
+    /// Adding keys to table sloodle_award_points
+        $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+    /// Launch create table for sloodle_award_points
+        $result = $result && create_table($table);
+    }
+    if ($result && $oldversion < 2011070500 ) {
+
+    /// Define field description to be added to sloodle_award_points
+        $table = new XMLDBTable('sloodle_award_points');
+        $field = new XMLDBField('description');
+        $field->setAttributes(XMLDB_TYPE_CHAR, '255', null, null, null, null, null, null, 'roundid');
+
+    /// Launch add field description
+        $result = $result && add_field($table, $field);
+/// Define key poiroufk (foreign) to be added to sloodle_award_points
+        $table = new XMLDBTable('sloodle_award_points');
+        $key = new XMLDBKey('poiroufk');
+        $key->setAttributes(XMLDB_KEY_FOREIGN, array('roundid'), 'sloodle_award_rounds', array('id'));
+
+    /// Launch add key poiroufk
+        $result = $result && add_key($table, $key);
+ /// Define key poiusefk (foreign) to be added to sloodle_award_points
+        $table = new XMLDBTable('sloodle_award_points');
+        $key = new XMLDBKey('poiusefk');
+        $key->setAttributes(XMLDB_KEY_FOREIGN, array('userid'), 'user', array('id'));
+
+    /// Launch add key poiusefk
+        $result = $result && add_key($table, $key);
+/// Define key poicurfk (foreign) to be added to sloodle_award_points
+        $table = new XMLDBTable('sloodle_award_points');
+        $key = new XMLDBKey('poicurfk');
+        $key->setAttributes(XMLDB_KEY_FOREIGN, array('currencyid'), 'sloodle_award_currency', array('id'));
+
+    /// Launch add key poicurfk
+        $result = $result && add_key($table, $key);
+ /// Define field courseid to be added to sloodle_award_rounds
+        $table = new XMLDBTable('sloodle_award_rounds');
+        $field = new XMLDBField('courseid');
+        $field->setAttributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null, 'controllerid');
+
+    /// Launch add field courseid
+        $result = $result && add_field($table, $field);
+
+    }
+
+
+ if ($result && $oldversion < 2011070501) {
+
+    /// Define field imageurl to be added to sloodle_currency_types
+        $table = new XMLDBTable('sloodle_currency_types');
+        $field = new XMLDBField('imageurl');
+        $field->setAttributes(XMLDB_TYPE_CHAR, '255', null, null, null, null, null, null, 'timemodified');
+
+    /// Launch add field imageurl
+        $result = $result && add_field($table, $field);
+/// Define field displayorder to be added to sloodle_currency_types
+        $table = new XMLDBTable('sloodle_currency_types');
+        $field = new XMLDBField('displayorder');
+        $field->setAttributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null, null, null, 'imageurl');
+
+    /// Launch add field displayorder
+        $result = $result && add_field($table, $field);    
+
+}
+
+if ($result && $oldversion < 2011070900) {
+
+    /// Define field mediakey to be added to sloodle_active_object
+        $table = new XMLDBTable('sloodle_active_object');
+        $field = new XMLDBField('mediakey');
+        $field->setAttributes(XMLDB_TYPE_CHAR, '255', null, null, null, null, null, null, 'httpinurl');
+
+    /// Launch add field mediakey
+        $result = $result && add_field($table, $field);
+ /// Define field lastmessagetimestamp to be added to sloodle_active_object
+        $table = new XMLDBTable('sloodle_active_object');
+        $field = new XMLDBField('lastmessagetimestamp');
+        $field->setAttributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null, null, null, 'mediakey');
+
+    /// Launch add field lastmessagetimestamp
+        $result = $result && add_field($table, $field);
+/// Define field httpinpassword to be added to sloodle_active_object
+        $table = new XMLDBTable('sloodle_active_object');
+        $field = new XMLDBField('httpinpassword');
+        $field->setAttributes(XMLDB_TYPE_CHAR, '255', null, null, null, null, null, null, 'lastmessagetimestamp');
+
+    /// Launch add field httpinpassword
+        $result = $result && add_field($table, $field);    
+}
+
+
+
+    
+    
+    // Basic SLOODLE Tracker tables
+
+    // Sloodle 1.2 snuck in in the middle of development and messed up the normal order.
+    // This should be OK as long as we don't get another release in between...
+    // If we absolutely have to, leaving the space up to 2009073000 to denote 1.2-series releases.
+    if ( $result && !table_exists(new XMLDBTable('sloodle_activity_tool')) ) { 
+                                                                                                                                                                                                                                                            
+    /// Insert 'sloodle_activity_tool' table                                                                                       
+        echo " - sloodle_activity_tool<br/>";                                                                                       
+        $table = new XMLDBTable('sloodle_activity_tool');                                                                         
+                                                                                                                                                                                                                                                            
+        $table->addFieldInfo('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);     
+        $table->addFieldInfo('trackerid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);              
+        $table->addFieldInfo('uuid', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);                          
+        $table->addFieldInfo('description', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);            
+        $table->addFieldInfo('taskname', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);                
+        $table->addFieldInfo('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);                      
+        $table->addFieldInfo('type', XMLDB_TYPE_CHAR, '50', null, XMLDB_NOTNULL, null, null, null, null);                     
+        $table->addFieldInfo('timeupdated', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0');  
+        $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));                                                          
+                                                                                                                                                                                                                                                        
+        $table->addIndexInfo('uuid', XMLDB_INDEX_UNIQUE, array('uuid'));                                                         
+                                                                                                                                                                                                                                                     
+        $result = $result && create_table($table);                                                                               
+        if (!$result) echo "error<br/>";                                                                                     
+                                                                                                                                                                                                        
+     }
+                                                                                                                  
+
+  /// Insert 'sloodle_activity_tracker' table                                                                                  
+    if ( $result && !table_exists(new XMLDBTable('sloodle_activity_tracker')) ) { 
+        echo " - sloodle_activity_tracker<br/>";                                                                              
+        $table = new XMLDBTable('sloodle_activity_tracker');                                                                    
+                                                                                                                                                                                                                                                       
+        $table->addFieldInfo('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
+        $table->addFieldInfo('trackerid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);     
+        $table->addFieldInfo('objuuid', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);                  
+        $table->addFieldInfo('avuuid', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, null, null);                    
+        $table->addFieldInfo('timeupdated', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0');     
+                                                                                                                                                                                                                                                 
+        $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));                                                          
+                                                                                                
+        $result = $result && create_table($table);         
+        if (!$result) echo "error<br/>";
+                
+    }
+
+    if ( $result && !table_exists(new XMLDBTable('sloodle_tracker')) ) { 
+    /// Define table sloodle_tracker to be created
+        $table = new XMLDBTable('sloodle_tracker');
+
+    /// Adding fields to table sloodle_tracker
+        $table->addFieldInfo('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
+        $table->addFieldInfo('sloodleid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+
+    /// Adding keys to table sloodle_tracker
+        $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
+        $table->addKeyInfo('sloodleid', XMLDB_KEY_UNIQUE, array('sloodleid'));
+
+    /// Launch create table for sloodle_tracker
+        $result = $result && create_table($table);
+    }
+
+    if ($result && $oldversion < 2011071101) {
+
+	// needed by moodle 2 (but should already have been in <=1.9)
+	// see http://docs.moodle.org/dev/Text_formats_2.0
+
+        $table = new XMLDBTable('sloodle');
+        $field = new XMLDBField('introformat');
+        $field->setAttributes(XMLDB_TYPE_INTEGER, '4', XMLDB_UNSIGNED, null, null, null, null, null, 'intro');
+
+        if (!field_exists($table, $field)) {
+            add_field($table, $field);
+        }
+
+    }
+
+    if ($result && $oldversion < 2011072301) {
+
+    /// Define field controllerid to be added to sloodle_layout
+        $table = new XMLDBTable('sloodle_layout');
+        $field = new XMLDBField('controllerid');
+        $field->setAttributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null, null, null, 'timeupdated');
+
+    /// Launch add field controllerid
+        $result = $result && add_field($table, $field);
+    }
+
+
+return $result; 
+}
+
+?>

+ 52 - 0
freemail/README.txt

@@ -0,0 +1,52 @@
+Moodle Email Importer Module, SLOODLE Virtual World->Blog version
+
+This is the freemail module, incorporated into SLOODLE so that you won't need the extra install.
+
+
+Copyright (c) various contributors (see below) based on original work by Serafim Panov
+License http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
+Contributors:
+* Serafim Panov
+* Paul Preibisch
+* Edmund Edgar
+
+
+*** SECURITY WARNING ***
+
+Some ways of setting this module up will allow people to send spam to the "drafts" section of your students' blogs.
+If that would be a problem for you, make sure you read and understand the explanation below before turning it on.
+
+
+*** How to use it ***
+
+* Set up an email account that is accessible through IMAP. (POP may work, but I haven't tested it.)
+* If you need to avoid people sending emails that pretend to be your students... 
+  ...make sure the email account you use only accepts email from the servers of the grid that will be sending your email.
+
+* Put the mail server details in the module settings page.
+* If using SLOODLE, make sure your avatar is registered linked to your Moodle account.
+* If not using SLOODLE, make sure your Moodle email account is the same as the one you registered with Second Life.
+* Go to http://yoursite.example.com/moodle/mod/freemail/view.php
+* Send a snapshot to your email address, putting the subject of the blog post in the subject field.
+* Click the "Test" button, and your email should be processed and imported to your blog as a draft.
+* If your Moodle site is running the cron, email should be processed automatically whenever it runs.
+* It is possible to run the readmail.php file from the command line as a daemon, so it will handle emails immediately.
+ 
+
+*** About this code ***
+
+This code will allow you to send snapshots from SL or OpenSim
+...and have them show up as blog entries in your blog.
+
+It is based on Serafim Panov's Freemail module, designed for Moodle 1.x.
+Freemail was a separate module that allowed you to send email for handling by
+...various different bits of Moodle, like blog, profile, gallery and file uploads.
+
+It was modifed by Paul Preibisch to work with SL postcards, but still as a standalone module.
+
+Edmund Edgar then refactored it, ported it to Moodle 2. 
+In the process it was stripped down and a lot of its original functionality removed.
+If you are interested in making a module with this functionality in, take a look at the history at:
+https://github.com/edmundedgar/moodle-mod_freemail
+
+

+ 249 - 0
freemail/lib/email_processors/sloodle_freemail_sloodle_email_processor.php

@@ -0,0 +1,249 @@
+<?php
+/*
+* Sloodle email processfor for the freemail mod (for Sloodle 0.4).
+* Various functions to extract info from sl postcards
+*
+* @package sloodle
+* @copyright Copyright (c) 2009 various contributors 
+* @license http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
+*
+* @contributor Paul G. Preibisch - aka Fire Centaur in Second Life
+* @contributor Edmund Edgar
+*/
+class sloodle_freemail_sloodle_email_processor extends sloodle_freemail_email_processor {
+
+    var $_avatar_uuid = null;
+
+    // Check if SLOODLE is present.
+    // TODO Maybe we should really check if it's installed and active...
+    static function is_available() {
+
+        global $CFG;
+        if (!file_exists($CFG->dirroot.'/mod/sloodle')) {
+            return false;
+        }
+        return true;
+
+    }
+
+    // Return true to say that this processor can process the email.
+    function is_email_processable() {
+        return true;
+    }
+
+    // Return the user ID
+    function get_user_id() {
+        return $this->_userid;
+    }
+
+    function prepare() {
+
+        //extract and build the slurl - FIRE
+        $search_text = $this->_html_body;    
+
+        $sl_info = $this->get_sl_info($search_text);
+
+        $uuid = $sl_info['agent_id'];
+        if ($uuid == '') {
+            return false;
+        }
+        
+        $this->_avatar_uuid = $uuid;
+
+        $simname = $sl_info['sim_name'];
+        $username = $sl_info['username'];
+
+        if (!$this->_userid = $this->get_user_id_for_avatar_uuid($uuid)) {
+            $error = 'Avatar '.$username.' with uuid '.$uuid.' was not registered on this site.';
+            $this->notify_user_by_instant_message($error);
+            return false;
+        }
+
+        $x = intval($sl_info['local_x']);
+        $y = intval($sl_info['local_y']);
+        $z = intval($sl_info['local_z']);
+
+        $slurl = 'http://maps.secondlife.com/secondlife/'.urlencode($simname).'/'.$x.'/'.$y.'/'.$z;
+
+        $messagebody = $this->_plain_body;
+
+        $messagebody = str_replace("Want to try out Second Life for yourself?  Sign up at", "", $messagebody); //SECOND LIFE PATCH--In line
+        $messagebody = str_replace("--", "", $messagebody); //SECOND LIFE PATCH--In line
+        //add delimiter so we can delete the unwanted text - FIRE                        
+        $messagebody .="endhere"; 
+        //find start of unwanted text - FIRE
+
+        $cutoffstart=strpos($messagebody,"http://secondlife.com"); 
+        $cutoffend=strpos($messagebody,"endhere",$cutoffstart+4);
+        $cutlength=$cutoffend-$cutoffstart+7;           
+        $toDelete= substr($messagebody,$cutoffstart,$cutlength);
+
+        //now remove unwanted text - FIRE            
+        $messagebody = str_replace($toDelete," ",$messagebody);           
+
+        $messagebody .= "\n";
+        $messagebody .= '<a href="'.$slurl.'">'.$slurl.'</a>.'."\n";
+
+        // Consolidate any multiple line returns into one.
+        $messagebody = preg_replace("/[\n\r]+/s","\n",$messagebody);           
+
+        $this->_prepared_body = $messagebody;
+        $this->_prepared_subject = $this->_subject;
+
+        $interesting_filenames = array('secondlife-postcard.jpg');
+
+        if (count($this->_attachments)) {
+            foreach($this->_attachments as $filename => $data) {
+                if (!in_array($filename, $interesting_filenames)) {
+                    continue;
+                }
+                $this->add_image($filename, $data);
+            }
+        }
+
+        return true;
+
+    }
+
+    function get_user_id_for_avatar_uuid($avuuid) {
+
+        if ( is_null($avuuid) || ($avuuid == '') ) {
+            return null;
+        }
+
+        global $DB;
+
+        $this->_userid = $DB->get_field('sloodle_users', 'userid', array('uuid'=>$avuuid));
+        return $this->_userid;
+
+    }
+
+    /**
+    * @param string $search_text is the text to be searched - such as the message body of the email
+    * @return string An array containing names and values extracted from the sl postcard - such as sim_name and agent_name 
+    *  
+    */
+    function get_sl_info($search_text){
+
+        /*
+        Text looks like this:
+        <!-- BEGIN POSTCARD DETAILS
+        agent_id=746ad236-d28d-4aab-93de-1e09a076c5f3
+        username="Edmund Earp"
+        region_id=5368d895-55d9-4206-9c1c-8660ce8fa306
+        sim_name="Cypris Village"
+        global_x=274595
+        global_y=271440
+        local_x=163
+        local_y=80
+        local_z=25
+        END POSTCARD DETAILS -->
+        */
+
+        $info = array();
+
+        // start by narrowing down to the postcard section.
+        $detail_start = 'BEGIN POSTCARD DETAILS';
+        $detail_end   = 'END POSTCARD DETAILS';
+
+        if (!preg_match('/^.*'.preg_quote($detail_start).'(.*)'.preg_quote($detail_end).'.*?$/s', $search_text, $matches)) {
+            return null;
+        }
+        $search_text = $matches[1];
+
+        $lines = explode("\n", $search_text);
+        foreach($lines as $line) {
+
+            $line = trim($line);
+            if (!preg_match('/^(.*?)\=(.*)$/', $line, $matches)) {
+                continue;
+            }
+
+            $name = $matches[1];
+            $value = $matches[2];
+
+            // remove quotation marks, if they're there.
+            if (preg_match('/^\"(.*)\"$/', $value, $matches)) {
+                $value= $matches[1];
+            }
+
+            $info[$name] = $value;
+
+        }
+
+        return $info;
+
+    }
+
+    public function notify_user() {
+
+        if (!$importer = $this->_importer) {
+            // print "no importer";
+            return false;
+        }
+
+        if (!$user = $importer->user()) {
+            //print "no user";
+            return false;
+        }
+
+        if (!$messagetext = $importer->user_notification_text()) {
+            //print "No text to notify.";
+            return false;
+        }
+
+        // Try to notify the user by sendint them an in-world message.
+        // This will only work if SLOODLE is installed, and at least one capable of handling it is running in-world.
+        // The first object capable of doing this will probably be in SLOODLE 2.2. 
+        if ($this->notify_user_by_instant_message($messagetext)) {
+            // print "Notify went OK, returning.";
+            return true;
+        }
+
+        if (!$subject = $importer->user_notification_title()) {
+            print "No title.";
+            return false;
+        }
+
+        //print "Instant messaging didn't work, trying email instead.";
+        $supportuser = generate_email_supportuser();
+
+        return email_to_user($user, $supportuser, $subject, $messagetext);
+
+    }
+
+    public function notify_user_by_instant_message($messagetext) {
+
+        global $CFG;
+
+        // Code for sloodle instant message
+        // Should probably be a define() somewhere...
+        $instant_message_code = 163290001;
+
+        // Remove line returns for instant message.
+        $messagetext = preg_replace("/\n/", " ", $messagetext);
+
+        if (!$avuuid = $this->_avatar_uuid) {
+            //print "no avuuid";
+            return false;
+        }
+
+        if (!file_exists($CFG->dirroot.'/mod/sloodle/init.php')) {
+            //print "no sloodle init - sloodle is too old or not installed";
+            return false;
+        }
+
+        require_once($CFG->dirroot.'/mod/sloodle/init.php'); 
+
+        // SLOODLE apparently not installed.
+        if (!class_exists('SloodleActiveObject')) {
+            //print "no SloodleActiveObject class";
+            return false;
+        }
+
+        $params = array('avuuid' => $avuuid, 'message' => $messagetext);
+        return SloodleActiveObject::NotifySubscriberObjects('message_to_user', $instant_message_code, 0, 0, $params, false, true);
+
+    }
+
+}

+ 468 - 0
freemail/lib/freemail_email_processor.php

@@ -0,0 +1,468 @@
+<?php
+/*
+This is a base class for email processors.
+It needs to be extended by a specific email processor, stored in the email_processors directory.
+*/
+class sloodle_freemail_email_processor {
+
+    // The following hold the raw data from the email.
+    var $_subject;
+    var $_from_address;
+    var $_html_body;
+    var $_plain_body;
+    var $_charset;
+    var $_attachments = array();
+
+    // The following are filled during parsing. 
+    var $_images= array();
+    var $_userid = null;
+    var $_prepared_body;
+    var $_prepared_subject;
+
+    var $_importer;
+
+    static function available_email_processors() {
+
+        $processor_dir = dirname(__FILE__).'/email_processors';
+
+        if (!$dh = opendir($processor_dir)) {
+            return false;
+        }
+
+        $processors = array();
+
+        while (($processor_file = readdir($dh)) !== false) {
+
+            if (preg_match('/^(sloodle_freemail_\w+_email_processor).php$/', $processor_file, $matches)) {
+                
+                $clsname = $matches[1];
+                require_once($processor_dir.'/'.$processor_file);
+                if (!class_exists($clsname)) {
+                    continue;
+                }
+                if (!$clsname::is_available()) {
+                    continue;
+                }
+                $processors[] = new $clsname;
+
+            }
+
+        }
+
+        return $processors;
+
+    }
+
+    // Adds an attachment.
+    // Multi-part emails can have lots of random little attachments.
+    // In sloodle we are interested in images
+    // ...and narrow down to provide them in get_images().
+    function add_attachment($attachment_name, $attachment_data) {
+        $this->_attachments[$attachment_name] = $attachment_data;
+    }
+
+    function add_image($filename, $data) {
+        $this->_images[$filename] = $data;
+    }
+
+    // Return true to say that this processor can process the email.
+    // This will include finding an importer that can handle the email.
+    function is_email_processable() {
+        return false;
+    }
+
+    // Import the message or return false on failure
+    function import() {
+
+        if (!$this->_importer) {
+            return false;
+        }
+
+        $this->_importer->set_user_id($this->_userid);
+        $this->_importer->set_title($this->_prepared_subject);
+        $this->_importer->set_body($this->_prepared_body);
+
+        foreach($this->_images as $n => $imgdata) {
+            $this->_importer->add_image($n, $imgdata);
+        }
+
+        return $this->_importer->import();
+
+    }
+
+    // Transform the raw text from the email into whatever we want to put into Moodle.
+    // You probably want to overload this.
+    // In the sloodle case this will consist of stripping Second Life advertising 
+    // ...and adding a URL pointing to the user's location.
+    function prepare() {
+
+        $this->_prepared_body = $this->_plain_body;
+        $this->_prepared_subject = $this->_subject;
+
+        if (!$this->_userid = $this->user_id_for_email($this->_from_address)) {
+            return false;
+        }
+
+        return true;
+
+    }
+
+    // Return the user ID
+    function get_user_id() {
+        return $this->_userid;
+    }
+
+    function set_charset($c) {
+        $this->_charset = $c;
+    }
+
+    function get_charset() {
+        return $this->_charset;
+    }
+
+    function get_plain_body() {
+        return $this->_plain_body;
+    }
+
+    function set_plain_body($b) {
+        $this->_plain_body = $b;
+    }
+
+    // Return the message body
+    function get_html_body() {
+        return $this->_html_body;
+    }
+
+    function set_html_body($m) {
+        $this->_html_body = $m;
+    }
+
+    function set_subject($s) {
+        $this->_subject = $s;
+    }
+
+    function set_from_address($e) {
+        $this->_from_address = $e;
+    }
+
+    function get_subject() {
+        return $this->_subject;
+    }
+
+    function load_importer() {
+        
+        $importers = sloodle_freemail_moodle_importer::available_moodle_importers();
+        if (!count($importers)) {
+            return false;
+        }
+
+        foreach($importers as $importer) {
+            if ($importer->is_email_importable()) {
+                $this->_importer = $importer;
+                return true;
+            }
+        }
+
+        return false;
+    
+    }
+
+    function user_id_for_email($email) {
+
+        if (is_null($email)) {
+            return null;
+        }
+
+        global $DB;
+        $this->_userid = $DB->get_field('user', 'id', array('email'=>$email));
+
+        return $this->_userid;
+
+    }
+
+    // By default, just tell the importer to notify them.
+    // This would normally be an email.
+    // Some handler may want to do their own handling
+    // ..and just get the subject and body from the importer.
+    // For example, in SLOODLE we send an in-world instant message.
+    function notify_user() {
+
+        if (!$importer = $this->_importer) {
+            return false;
+        }
+
+        return $importer->notify_user();
+
+    }
+
+    static function verbose_output($verbose, $msg) {
+
+        if ($verbose) {
+            print $msg."\n";
+        }
+
+    }
+
+    static function read_mail($cfg, $verbose, $daemon, $handler = null, $nodelete = false, $cron = false) {
+
+        // This allows you to hard-code some settings in your config.php and use them in preference to whatever might be set in the web UI.
+        // This is useful to us at Avatar Classroom in a multi-site setting, but probably not to anybody else.
+        $cfg = isset($cfg->sloodle_freemail_force_settings) ? $cfg->sloodle_freemail_force_settings : $cfg;
+
+        if ($cfg->sloodle_freemail_mail_box_settings == '') {
+            return false;
+        }
+
+        $statuses = array(
+            'result' => array(),
+            'errors' => array(),
+            'messages' => array(
+            )
+        );
+
+        $giveup = false;
+        $msgcount = 0;
+
+        $email_processors = sloodle_freemail_email_processor::available_email_processors();
+        if (!count($email_processors)) {
+            sloodle_freemail_email_processor::verbose_output($verbose, "No email processors available, aborting.");
+            $statuses['errors']["-1"] = "No email processors available, aborting.";
+            $giveup = true;
+        }
+
+        // In daemon mode, the handler is kept alive between calls to this function with its connection open.
+        sloodle_freemail_email_processor::verbose_output($verbose, "Trying to get connection...");
+        $handler = !is_null($handler) ? $handler : new sloodle_freemail_imap_message_handler();
+
+        if (!$giveup) {
+            sloodle_freemail_email_processor::verbose_output($verbose, "Connecting...");
+            if (!$handler->connect($cfg->sloodle_freemail_mail_box_settings, $cfg->sloodle_freemail_mail_user_name, $cfg->sloodle_freemail_mail_user_pass)) {
+                sloodle_freemail_email_processor::verbose_output($verbose, "Connection failed.");
+                $statuses['errors']["-2"] = "Connection failed. Could not fetch email.";
+                $giveup = true;
+            }
+        }
+
+        if (!$giveup) {
+            if (!$msgcount = $handler->count()) {
+                // In daemon mode, keep the connection open, and return the handler object so we can reuse it.
+                if ($daemon) {
+                    return $handler;
+                }
+                $handler->close();
+                sloodle_freemail_email_processor::verbose_output($verbose, "No messages found.");
+                $statuses['result']["1"] = "No messages.";
+                $giveup = true;
+            }
+        }
+
+        if (!$giveup) {
+
+            sloodle_freemail_email_processor::verbose_output($verbose, "Got $msgcount messages.");
+
+            if ($msgcount > 0)  {  
+
+                if ($msgcount > $cfg->sloodle_freemail_mail_maxcheck) {
+                    $msgcount = $cfg->sloodle_freemail_mail_maxcheck;
+                }
+
+                for ($mid = 1; $mid <= $msgcount; $mid++) {
+
+                    $statuses['messages'] = array();
+
+                    sloodle_freemail_email_processor::verbose_output($verbose, "Considering loading message with ID :$mid:");
+
+                    // Load the header first so that we can check what we need to know before downloading the rest. 
+                    if (!$handler->load_header($mid)) {
+                        $statuses['messages'][] = array( 
+                            'errors' => array('-101' => 'Could not load header') 
+                        );
+                        continue;
+                    }
+
+                    $subject = $handler->get_subject();
+                    $fromaddress = $handler->get_from_address();
+
+                    $toaddress = $handler->get_to_address();
+                    if (!strtolower($toaddress) == strtolower($cfg->sloodle_freemail_mail_email_address)) {
+                        print "not for us: $toaddress";
+                        // Not for us.
+                        continue;
+                    }
+
+                    $info = array(
+                        'subject' => $subject,
+                        'fromaddress' => $fromaddress
+                    );
+
+                    $size_in_bytes = $handler->get_size_in_bytes();
+                    if ($size_in_bytes > $cfg->sloodle_freemail_mail_maxsize) {
+                        $statuses['messages'][] = array( 
+                            'errors' => array('-101' => 'Could not load header.'),
+                            'info' => $info
+                        );
+                        continue;
+                    }
+                    sloodle_freemail_email_processor::verbose_output($verbose, "Message size :$size_in_bytes: small enough - continuing.");
+
+                    // TODO: Separate load_header and load_body so we don't load the whole thing if it's too big.
+                    if (!$handler->load($mid)) {
+                        $statuses['messages'][] = array( 
+                            'errors' => array('-102' => 'Could not load.'),
+                            'info' => $info
+                        );
+
+                        continue;
+                    }
+                    sloodle_freemail_email_processor::verbose_output($verbose, "Loaded message...");
+
+                    $htmlmsg = $handler->get_html_message();;
+                    $plainmsg = $handler->get_plain_message();; 
+                    $charset = $handler->get_charset();
+                    $attachments = $handler->get_attachments();
+
+                    foreach($email_processors as $processor) {
+
+                        sloodle_freemail_email_processor::verbose_output($verbose, "Trying processor...");
+
+                        $processor->set_subject($subject);
+                        $processor->set_from_address($fromaddress);
+                        $processor->set_plain_body($plainmsg);
+                        $processor->set_html_body($htmlmsg);
+                        $processor->set_charset($charset);
+
+                        foreach($attachments as $attachment_filename => $attachment_body) {
+                            $processor->add_attachment($attachment_filename, $attachment_body);
+                        }
+
+                        sloodle_freemail_email_processor::verbose_output($verbose, "Preparing message...");
+                        // Couldn't make sense of it, skip
+                        if (!$processor->prepare()) {
+
+                            $statuses['messages'][] = array( 
+                                'errors' => array('-103' => 'Could not prepare email.') ,
+                                'info' => $info
+                            );
+                            sloodle_freemail_email_processor::verbose_output($verbose, "Could not prepare email.");
+                            continue;
+                        }
+
+                        // Couldn't find anyone to process it, skip.
+                        if (!$processor->load_importer()) {
+                            sloodle_freemail_email_processor::verbose_output($verbose, "Could not load importer.");
+
+                            $statuses['messages'][] = array( 
+                                'errors' => array('-104' => 'Could not load importer.'),
+                                'info' => $info
+                            );
+                            continue;
+                        }
+
+                        // Processor can't handle this kind of email.
+                        if (!$processor->is_email_processable()) {
+                            sloodle_freemail_email_processor::verbose_output($verbose, "Processor cannot handle this email. Will let others try.");
+
+                            $statuses['messages'][] = array( 
+                                'errors' => array('-104' => 'Could not load importer.'),
+                                'info' => $info
+                            );
+                            continue;
+                        }
+
+                        // TODO: Get this working.
+                        // Ideally we should mark messages as flagged before we start to import them
+                        // ...and skip over messages that are already flagged.
+                        // This should prevent multiple processes running at the same time from tripping over each other and importing the same message multiple times.
+                        // Mark the message as flagged 
+                        // $handler->mark_flagged($mid);
+
+                        sloodle_freemail_email_processor::verbose_output($verbose, "Importing...");
+                        if (!$processor->import()) {
+                            sloodle_freemail_email_processor::verbose_output($verbose, "Importing failed.");
+                            $statuses['messages'][] = array( 
+                                'errors' => array('-105' => 'Importing failed.'),
+                                'info' => $info
+                            );
+                            continue;
+                        }
+
+                        sloodle_freemail_email_processor::verbose_output($verbose, "Notifying user...");
+                        if (!$processor->notify_user()) {
+                            $statuses['messages'][] = array( 
+                                'success' => array('107' => 'Imported, but could not notify user..'),
+                                'errors' => array('-106' => 'Imported, but could not notify user..'),
+                                'info' => $info
+                            );
+                            break;
+                        }
+
+                        sloodle_freemail_email_processor::verbose_output($verbose, "Handling of this email complete.");
+                        $statuses['messages'][] = array( 
+                            'success' => array('107' => 'Imported, but could not notify user..'),
+                            'errors' => array('-106' => 'Imported, but could not notify user..'),
+                            'info' => $info
+                        );
+
+                        break;
+
+                    }
+
+                    // skipping subcommand stuff
+                    // list($subcomm, $messcomm) = freemail_getcommands($msg->header[$mid]['subject'], $messagebody['message'], $commands);
+
+                    if ($nodelete) {
+                        sloodle_freemail_email_processor::verbose_output($verbose, "Skipping deletion of message $mid because you asked for nodelete.");
+                    } else {
+                        sloodle_freemail_email_processor::verbose_output($verbose, "Deleting message $mid.");
+                        if (!$handler->delete($mid)) {
+                            sloodle_freemail_email_processor::verbose_output($verbose, "Deletion of message $mid failed.");
+                        }
+                    }
+                    //imap_delete($mailbox, $mid);
+
+                    //print "skipping user mail content";
+
+                }
+
+            }
+
+            $handler->expunge();
+
+        }
+
+        if ($cfg->sloodle_freemail_mail_admin_email) {
+            // in daemon mode, only send a report if some messages were actually processed.
+            if ( (!$daemon & !$cron) || $msgcount) { 
+                $subject = "Email processing report";
+                $body = sloodle_freemail_email_processor::status_text($statuses);
+                mail($cfg->sloodle_freemail_mail_admin_email, $subject, $body); 
+            }
+        }
+
+        // In daemon mode, keep the handler with its connection alive and return it so it can be used again next time.
+        if ($daemon) {
+            return $handler;
+        }
+
+        $handler->close();
+
+        return true;
+
+    }
+
+    static function status_text($statuses) {
+
+        $str = '';
+
+        if (count($statuses['errors'])) {
+            $str .= 'Fetching email failed:'."\n";
+            $str .= implode("\n", $statuses['errors'])."\n";
+        }
+
+        $str .= count($statuses['messages']).' messages processed.';
+
+        return $str;
+
+    }
+
+}

+ 233 - 0
freemail/lib/freemail_imap_message_handler.php

@@ -0,0 +1,233 @@
+<?php
+class sloodle_freemail_imap_message_handler {
+
+    var $_connection = null;
+
+    var $_html_message;
+    var $_plain_message;
+    var $_charset;
+    var $_attachments = array();
+    var $_header;
+
+    public function connect($connection_string, $username, $password) {
+
+        // Reuse the existing connection if we have one and it's still alive.
+        if ( !is_null($this->_connection)) { 
+            if (@imap_ping($this->_connection) ) {
+                return $this->_connection; 
+            }
+            // Probably won't do anything, but let's be sure.
+            @imap_close($this->_connection);
+        }
+
+        $this->_connection = @imap_open($connection_string, $username, $password);
+        return $this->_connection;
+
+    }
+
+    public function count() {
+
+        if (!$this->_connection) {
+            return null;
+        }
+
+        return @imap_num_msg($this->_connection);
+    
+    }
+
+    public function close() {
+
+        if (!$this->_connection) {
+            return null;
+        }
+
+        return @imap_close($this->_connection);
+
+    }
+
+    public function delete($mid) {
+
+        if (!$this->_connection) {
+            return null;
+        }
+
+        return @imap_delete($this->_connection, $mid);
+
+    }
+
+    public function expunge() {
+
+        if (!$this->_connection) {
+            return null;
+        }
+
+        return @imap_expunge($this->_connection);
+
+    }
+
+    public function load_header($mid) {
+
+        return $this->_header = @imap_header($this->_connection,$mid); 
+
+    }
+
+    public function load($mid) {
+
+        return $this->_get_msg($mid);
+
+    }
+
+    public function mark_flagged($mid) {
+
+        if (!$this->_connection) {
+            return null;
+        }
+
+        return @imap_setflag_full($this->_connection, $mid, "\\Flagged");
+
+    }
+
+    public function get_html_message() {
+        return $this->_html_message;
+    }
+
+    public function get_plain_message() {
+        return $this->_plain_message;
+    }
+
+    public function get_charset() {
+        return $this->_charset;
+    }
+    
+    public function get_attachments() {
+        return $this->_attachments;
+    }
+
+    public function get_header() {
+        return $this->_header;
+    }
+
+    public function get_subject() {
+        return $this->_header->subject;
+    }
+
+    public function get_date() {
+        return $this->_header->date;
+    }
+
+    public function get_to_address() {
+        return $this->_header->toaddress;
+    }
+
+    public function get_from_address() {
+        return $this->_header->fromaddress;
+    }
+
+    public function get_size_in_bytes() {
+        return $this->_header->Size;
+    }
+    
+
+    /*
+    The following methods are adapted from code found at:
+    http://php.net/manual/en/function.imap-fetchstructure.php
+    david at hundsness dot com 02-Sep-2008 02:31
+    ...who said:
+    "Here is code to parse and decode all types of messages, including attachments. I've been using something like this for awhile now, so it's pretty robust."
+    */
+    private function _get_msg($mid) {
+        // input $mbox = IMAP stream, $mid = message id
+        // output all the following:
+
+        if (!$mbox = $this->_connection) {
+            return false;
+        }
+
+        $this->_html_message = $this->_plain_message = $this->_charset = '';
+        $this->_attachments = array();
+
+        // BODY
+        $s = @imap_fetchstructure($mbox,$mid);
+        if (!$s->parts) { // simple
+            $this->_get_part($mid,$s,0);  // pass 0 as part-number
+        } else {  // multipart: cycle through each part
+            foreach ($s->parts as $partno0=>$p) {
+                $this->_get_part($mid,$p,$partno0+1);
+            }
+        }
+
+        return true;
+
+    }
+
+    private function _get_part($mid,$p,$partno) {
+        // $partno = '1', '2', '2.1', '2.1.3', etc if multipart, 0 if not multipart
+
+        if (!$mbox = $this->_connection) {
+            return false;
+        }
+
+        // DECODE DATA
+        $data = ($partno)?
+            @imap_fetchbody($mbox,$mid,$partno):  // multipart
+            @imap_body($mbox,$mid);  // not multipart
+
+        // Any part may be encoded, even plain text messages, so check everything.
+        if ($p->encoding==4) {
+            $data = quoted_printable_decode($data);
+        } elseif ($p->encoding==3) {
+            $data = base64_decode($data);
+            // no need to decode 7-bit, 8-bit, or binary
+        }
+
+        // PARAMETERS
+        // get all parameters, like charset, filenames of attachments, etc.
+        $params = array();
+        if ($p->parameters) {
+            foreach ($p->parameters as $x) {
+                $params[ strtolower( $x->attribute ) ] = $x->value;
+            }
+        }
+        if ($p->dparameters) {
+            foreach ($p->dparameters as $x) {
+                $params[ strtolower( $x->attribute ) ] = $x->value;
+            }
+        }
+
+        // ATTACHMENT
+        // Any part with a filename is an attachment,
+        // so an attached text file (type 0) is not mistaken as the message.
+        if ($params['filename'] || $params['name']) {
+            // filename may be given as 'Filename' or 'Name' or both
+            $filename = ($params['filename'])? $params['filename'] : $params['name'];
+            // filename may be encoded, so see imap_mime_header_decode()
+            $this->_attachments[$filename] = $data;  // this is a problem if two files have same name
+        } elseif ($p->type==0 && $data) {
+        // TEXT
+            // Messages may be split in different parts because of inline attachments,
+            // so append parts together with blank row.
+            if (strtolower($p->subtype)=='plain') {
+                $this->_plain_message .= trim($data) ."\n\n";
+            } else {
+                $this->_html_message .= $data ."<br><br>";
+                $this->_charset = $params['charset'];  // assume all parts are same charset
+            }
+        } elseif ($p->type==2 && $data) {
+            // EMBEDDED MESSAGE
+            // Many bounce notifications embed the original message as type 2,
+            // but AOL uses type 1 (multipart), which is not handled here.
+            // There are no PHP functions to parse embedded messages,
+            // so this just appends the raw source to the main message.
+            $this->_plain_message .= trim($data) ."\n\n";
+        }
+
+        // SUBPART RECURSION
+        if ($p->parts) {
+            foreach ($p->parts as $partno0=>$p2) {
+                $this->_get_part($mid,$p2,$partno.'.'.($partno0+1));  // 1.2, 1.2.1, etc.
+            }
+        }
+    }
+
+}
+

+ 148 - 0
freemail/lib/freemail_moodle_importer.php

@@ -0,0 +1,148 @@
+<?php
+/*
+This is a base class for email processors.
+It needs to be extended by a specific moodle importer, stored in the moodle_importers directory.
+*/
+abstract class sloodle_freemail_moodle_importer {
+
+    var $_title;
+    var $_body;
+    var $_images = array();
+    var $_userid;
+
+    var $_user = null;
+
+    // Return true to say that this processor can process the email.
+    // For example, if we have a rule that blog subjects have to begin with "b:", we'll check for that.
+    function can_process() {
+        return false;
+    }
+
+    // Return the user ID
+    function get_user_id() {
+        return $this->_userid;
+    }
+
+    function set_user_id($u) {
+        $this->_userid = $u;
+    }
+
+    // Return the body body
+    function get_body() {
+        return $this->_body;
+    }
+
+    function set_body($m) {
+        $this->_body = $m;
+    }
+
+    function set_title($s) {
+        $this->_title = $s;
+    }
+
+    function get_title() {
+        return $this->_title;
+    }
+
+    function add_image($filename, $imgdata) {
+        $this->_images[$filename] = $imgdata;
+    }
+
+    function get_images() {
+        return $this->_images;
+    }
+
+    // Fetch, cache and return a Moodle user record, loading if necessary.
+    // Return null on failure.
+    function user($reload = false) {
+
+        if (!$userid = intval($this->_userid)) {
+            return null;
+        }
+
+        if ( !$reload && !is_null($this->_user) ) {
+            return $this->_user;
+        }
+
+        global $DB;
+        $this->_user = $DB->get_record('user', array('id'=>$userid));
+
+        return $this->_user;
+
+    }
+ 
+    // Notify the user that their content has been imported.
+    // In most cases this is probably OK as is. 
+    // but you'll want to override user_notification_title() and user_notification_text() 
+    // ...to customized the content of the email.
+    // In the SLOODLE case we'll do some exotic stuff like sending an in-world instant message, so this will be overloaded..
+    public function notify_user() {
+
+        if (!$user = $this->user()) {
+            return false;
+        }
+
+        if (!$subject = $this->user_notification_title()) {
+            return false;
+        }
+
+        if (!$messagetext = $this->user_notification_text()) {
+            return false;
+        }
+
+        $supportuser = generate_email_supportuser();
+
+        email_to_user($user, $supportuser, $subject, $messagetext);
+
+    }
+
+    // The title of the notification sent to the user to say their content has been imported.
+    // You will probably want to overload this.
+    function user_notification_title() {
+        return 'Content has been imported';
+    }
+
+    // The title of the notification sent to the user to say their content has been imported.
+    // You will probably want to overload this to give them specific instructions about where to find their content
+    // ...and if it's been imported in draft form, where to go to publish it.
+    function user_notification_text() {
+        return 'Your content has been imported into Moodle.'."\n";
+    }
+
+    static function available_moodle_importers() {
+
+        $importer_dir = dirname(__FILE__).'/moodle_importers';
+
+        if (!$dh = opendir($importer_dir)) {
+            return false;
+        }
+
+        $importers = array();
+
+        while (($importer_file = readdir($dh)) !== false) {
+            //print "looking at $importer_file";
+
+            if (preg_match('/^(sloodle_freemail_\w+_moodle_importer).php$/', $importer_file, $matches)) {
+                
+                $clsname = $matches[1];
+                require_once($importer_dir.'/'.$importer_file);
+                if (!class_exists($clsname)) {
+                    continue;
+                }
+                if (!$clsname::is_available()) {
+                    continue;
+                }
+                $importers[] = new $clsname;
+
+            }
+
+        }
+
+        return $importers;
+
+    }
+
+    static function config_options() {
+    }
+
+}

+ 131 - 0
freemail/lib/moodle_importers/sloodle_freemail_blog_moodle_importer.php

@@ -0,0 +1,131 @@
+<?php
+/*
+Importer for getting the results of a message into a blog.
+*/
+class sloodle_freemail_blog_moodle_importer extends sloodle_freemail_moodle_importer {
+
+    /*
+    var $_title;
+    var $_body;
+    var $_images = array();
+    var $_userid;
+    */
+
+    var $_blogid = null;
+
+    function is_available() {
+        return true;
+    }
+
+    function is_email_importable() {
+        return true;
+    }
+
+    // Return true to say that this processor can process the email.
+    // For example, if we have a rule that blog subjects have to begin with "b:", we'll check for that.
+    // For now we'll take anything with a subject and a body.
+    function can_process() {
+
+        if ($this->_title == '') {
+            return false;
+        }
+        if ($this->_body == '') {
+            return false;
+        }
+        if (!$this->_userid) {
+            return false;
+        }
+        return true;
+
+    }
+
+    function import() {
+
+        global $CFG;
+
+        if (!file_exists($CFG->dirroot.'/blog/locallib.php')) {
+            return false;
+        }
+
+        require_once($CFG->dirroot.'/blog/locallib.php');
+        require_once($CFG->dirroot.'/tag/lib.php');
+
+        $format = 0; // Moodle format - some tags and links.
+        $options = array('overflowdiv'=>true); // Blog did this, we probably should too.
+
+        $publishstate = defined('SLOODLE_FREEMAIL_PUBLISH_STATE') ? SLOODLE_FREEMAIL_PUBLISH_STATE : 'draft'; // Can be 'site' or 'public'.
+
+        $data = array(
+            'subject' => format_string($this->_title),
+            'summary' => format_text($this->_body, $format, $options),
+            'userid'  => $this->_userid,
+            'publishstate' => $publishstate
+            //'tags' => array('SLOODLE')
+        );
+
+        $blogentry = new blog_entry(null, $data, $blogeditform);
+        $blogentry->add();
+
+        if (!$id = $blogentry->id) {
+            return false;
+        }
+
+        $this->_blogid = $id;
+
+        $fs = get_file_storage();
+
+        foreach($this->_images as $name => $data) {
+            
+            $fileinfo = array(
+                'contextid' => 1, // Hope this is right...
+                'component' => 'blog',     // 
+                'filearea' => 'attachment',     // 
+                'itemid' => $id,
+                'userid' => $this->_userid,
+                'filepath' => '/',
+                'filename' => time().'-'.$name
+            );
+
+            $fs->create_file_from_string( $fileinfo, $data);
+
+        }
+
+        return true;
+
+    }
+
+    // TODO: Need to either localize this or make it editable.
+    function user_notification_title() {
+
+        return 'Blog entry created by email';
+
+    }
+
+    function user_notification_text() {
+
+        if (!$blogid = intval($this->_blogid)) {
+            print "no blogid";
+            return false;
+        }
+
+        $publishstate = defined('SLOODLE_FREEMAIL_PUBLISH_STATE') ? SLOODLE_FREEMAIL_PUBLISH_STATE : 'draft'; // Can be 'site' or 'public'.
+
+        global $CFG;
+        //$url = $CFG->wwwroot.'/blog/index.php?entryid='.$blogid;
+        $editurl = $CFG->wwwroot.'/blog/edit.php?action=edit&entryid='.$blogid;
+
+        $str  = 'This is the blog import program at '.$CFG->wwwroot."\n";
+
+        if ($publishstate == 'draft') {
+            $str .= 'Your blog entry has been created as a draft'."\n";
+            $str .= 'To publish your blog entry, go to:'."\n";
+        } else {
+            $str .= 'Your blog entry has been created'."\n";
+            $str .= 'To edit it, go to:'."\n";
+        }
+        $str .= $editurl."\n";
+
+        return $str;
+
+    }
+}

+ 56 - 0
freemail/readmail.php

@@ -0,0 +1,56 @@
+<?php
+/**
+* Freemail v1.1 with SL patch
+*
+* @package freemail
+* @copyright Copyright (c) 2008 Serafim Panov
+* @license http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
+* @author Serafim Panov
+* @contributor: Paul G. Preibisch - aka Fire centaur in Second Life
+* @contributor: Edmund Edgar
+*
+*/
+
+if (isset($argv)) {
+    define('CLI_SCRIPT', true);
+}
+require_once "../init.php";
+
+if (!defined('SLOODLE_FREEMAIL_ACTIVATE') || !SLOODLE_FREEMAIL_ACTIVATE) {
+        die("Freemail is turned off. Enable SLOODLE_FREEMAIL_ACTIVATE in sloodle_config to turn it on.");
+}
+
+// Class for handling an imap message connection, and fetching and parsing emails one by one.
+require_once 'lib/freemail_imap_message_handler.php'; 
+
+// The following are base classes, but with static methods to load inherited classes.
+
+// We'll need an email_processor to parse our email - for example if it looks like a Second Life snapshot, we'll want one of those.
+require_once 'lib/freemail_email_processor.php'; 
+
+// It will then need to find something to do with the email, like import it into the blog.
+require_once 'lib/freemail_moodle_importer.php';
+
+$verbose = in_array("-v", $argv);
+$daemon = in_array("-d", $argv);
+$nodelete = in_array("-n", $argv); // Useful when testing. 
+
+if ($nodelete && $daemon) {
+    echo "Refusing to run in daemon mode with nodelete specified, as this will spam you into oblivion.\n";
+    exit;
+}
+
+if ($daemon) {
+
+    while ($handler = sloodle_freemail_email_processor::read_mail($CFG, $verbose, $daemon, $handler, false)) {
+        sloodle_freemail_email_processor::verbose_output($verbose, "Handling run done, sleeping");
+        sleep(2);
+    }
+
+} else {
+
+    sloodle_freemail_email_processor::read_mail($CFG, $verbose, $daemon, null, $nodelete);
+
+}
+
+exit;

+ 58 - 0
freemail/settings.php

@@ -0,0 +1,58 @@
+<?php
+
+// This selection box determines whether or not auto-registration is allowed on the site
+ 
+$settings->add( new admin_setting_configtext(
+                'sloodle_freemail_mail_email_address',
+                get_string('freemail:emailtosend', 'sloodle'),
+                '',
+                ''));
+
+
+$settings->add( new admin_setting_configtext(
+                'sloodle_freemail_mail_user_name',
+                get_string('freemail:mailaccount', 'sloodle'),
+                '',
+                ''));
+                
+$settings->add( new admin_setting_configtext(
+                'sloodle_freemail_mail_user_pass',
+                get_string('freemail:mailaccountpassword', 'sloodle'),
+                '',
+                ''));
+  
+$settings->add( new admin_setting_configtext(
+                'sloodle_freemail_mail_box_settings',
+                get_string('freemail:mailboxsettings', 'sloodle'),
+                get_string('freemail:gmailuse','sloodle').'{imap.gmail.com:993/imap/ssl}INBOX',
+                ''));
+ 
+/*
+$settings->add( new admin_setting_configselect(
+                'sloodle_freemail_pop3_or_imap',
+                get_string('freemail:mailaccounttype','sloodle'),
+                '',
+                0,
+                array('pop3' => 'POP3', 'imap' => 'IMAP')
+));
+*/
+  
+$settings->add( new admin_setting_configtext(
+                'sloodle_freemail_mail_admin_email',
+                get_string('freemail:adminmail', 'sloodle'),
+                '',
+                ''));
+  
+  
+$settings->add( new admin_setting_configtext(
+                'sloodle_freemail_mail_maxcheck',
+                get_string('freemail:maxcheck', 'sloodle'),
+                '',
+                ''));
+ 
+$settings->add( new admin_setting_configtext(
+                'sloodle_freemail_mail_maxsize',
+                get_string('freemail:delete', 'sloodle'),
+                '',
+                ''));
+

+ 136 - 0
freemail/view.php

@@ -0,0 +1,136 @@
+<?php
+ /**
+* Freemail v1.1 with SL patch
+*
+* @package freemail
+* @copyright Copyright (c) 2008 Serafim Panov
+* @license http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
+* @author Serafim Panov
+* @contributor: Paul G. Preibisch - aka Fire centaur in Second Life
+*
+*/
+
+
+require_once "../init.php";
+if (!defined('SLOODLE_FREEMAIL_ACTIVATE') || !SLOODLE_FREEMAIL_ACTIVATE) {
+    die("Freemail is turned off. Enable SLOODLE_FREEMAIL_ACTIVATE in sloodle_config to turn it on.");
+}
+
+$PAGE->set_context(get_system_context());
+$PAGE->set_context(get_context_instance(CONTEXT_COURSE, SITEID));
+
+$PAGE->set_url('/mod/sloodle/freemail/view.php');
+$PAGE->set_title('Postcard Blogger');
+$PAGE->set_heading('Postcard Blogger.');
+
+require_login();
+
+echo $OUTPUT->header();
+
+
+
+//$PAGE->set_context(context_system::instance());
+//$PAGE->set_pagelayout('standard');
+
+$freemail_dir = dirname(__FILE__);
+
+require_once $freemail_dir.'/lib/freemail_imap_message_handler.php'; 
+require_once $freemail_dir.'/lib/freemail_email_processor.php'; 
+require_once $freemail_dir.'/lib/freemail_moodle_importer.php'; 
+
+$freemail_cfg = isset($CFG->sloodle_freemail_force_settings) ? $CFG->sloodle_freemail_force_settings : $CFG;
+
+$noticeTable = new html_table();
+$noticeTable->head = array('SLOODLE Freemail - Postcard Blogger');
+$r = array();
+
+if ( $freemail_cfg->sloodle_freemail_mail_box_settings == '' ) {
+    $body = get_string('freemail:confignotset','sloodle', '');
+
+    $noticeTable->width='100%';
+    $noticeTable->cellpadding=10;
+    $noticeTable->cellspacing=10; 
+    $noticeTable->data[] = $r;  
+    echo html_writer::table($noticeTable);
+
+
+    echo $OUTPUT->footer();
+    exit;
+}
+
+$body = get_string('freemail:explanation_wheretosend','sloodle', '');
+$body .= '<br />';
+$body .= '<br />';
+$body .= '<b>'.htmlentities($freemail_cfg->sloodle_freemail_mail_email_address).'</b>';
+$body .= '<br />';
+$body .= '<br />';
+$body .= get_string('freemail:explanation_howtoblog','sloodle');
+$r[] = $body;
+
+$courseTable = new stdClass();
+$courseTable->class="course-view";
+
+$noticeTable->width='100%';
+$noticeTable->cellpadding=10;
+$noticeTable->cellspacing=10; 
+$noticeTable->data[] = $r;  
+echo html_writer::table($noticeTable);
+
+$nodelete = false;
+if (isset($_POST['nodelete'])) {
+    $nodelete = true;
+}
+
+?>
+<div style="text-align:center; width:100%">
+<form method="POST">
+<input type="hidden" value="1" name="do_test" />
+<input type="checkbox" name="nodelete" value="1" <?php echo $nodelete ? ' checked="checked" ' : ''?>/>
+<?php
+echo get_string('freemail:delete_message', 'sloodle');
+?>
+<br />
+<input type="submit" value="<?php echo get_string('freemail:testbutton', 'sloodle')?>" />
+</form>
+</div>
+
+<p><br /></p>
+<?php
+
+
+if (isset($_POST['do_test'])) {
+
+    $verbose = true;
+    $daemon = false;
+
+    echo '<textarea rows="10" style="width:100%">';
+    sloodle_freemail_email_processor::read_mail($CFG, $verbose, $daemon, null, $nodelete);
+    echo '</textarea>';
+}
+if ($nodelete && $daemon) {
+    echo "Refusing to run in daemon mode with nodelete specified, as this will spam you into oblivion.\n";
+    exit;
+}
+
+
+echo $OUTPUT->footer();
+exit;
+
+
+//echo $OUTPUT->heading('SLOODLE Freemail - Postcard Blogger', 1);
+//exit;
+
+
+
+
+
+
+$PAGE->set_state(1);
+$PAGE->set_state(2);
+
+echo $OUTPUT->footer();
+exit;
+
+//$OUTPUT->footer();
+
+?>


+ 146 - 0
index.php

@@ -0,0 +1,146 @@
+<?php
+
+    // This file is part of the Sloodle project (www.sloodle.org)
+
+    /**
+    * Index page for listing all instances of the Sloodle module.
+    * Used as an interface script by the Moodle framework.
+    *
+    * @package sloodle
+    * @copyright Copyright (c) 2008 Sloodle (various contributors)
+    * @license http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
+    *
+    * @contributor Peter R. Bloomfield
+    *
+    */
+
+    /** Sloodle/Moodle configuration script. */
+    require_once('init.php');
+    /** Sloodle core library functionality */
+    require_once(SLOODLE_DIRROOT.'/lib.php');
+    require_once(SLOODLE_DIRROOT.'/lib/io.php');
+    
+    // Fetch the course ID from request parameters
+    $id = optional_param('id', 0, PARAM_INT);
+    
+    
+    // Fetch the course data
+    $course = null;
+    if ($id) {
+        if (! $course = sloodle_get_record("course", "id", $id)) {
+            error("Course ID is incorrect");
+        }
+    } else {
+        if (! $course = get_site()) {
+            error("Could not find a top-level course!");
+        }
+    }
+
+    // Require that the user logs in
+    require_login($course, false);
+    // Log this page view
+    add_to_log($course->id, "sloodle", "view sloodle modules", "index.php?id=$course->id");
+
+    // Fetch our string table data
+    $strsloodle = get_string('modulename', 'sloodle');
+    $strsloodles = get_string('modulenameplural', 'sloodle');
+    $strid = get_string('ID', 'sloodle');
+    $strname = get_string('name', 'sloodle');
+    $strdescription = get_string('description');
+    $strmoduletype = get_string('moduletype', 'sloodle');
+    
+    // Fetch the full names of each module type
+    $sloodle_type_names = array();
+    foreach ($SLOODLE_TYPES as $ST) {        
+        // Get the module type name
+        $sloodle_type_names[$ST] = get_string("moduletype:$ST", 'sloodle');
+    }
+    
+    // We're going to make one table for each module type
+    $sloodle_tables = array();
+    
+    // Get all Sloodle modules for the current course
+    $sloodles = sloodle_get_records('sloodle', 'course', $course->id, 'name');
+    if (!$sloodles) $sloodles = array();
+    // Go through each module    
+    foreach ($sloodles as $s) {
+        // Find out which course module instance this SLOODLE module belongs to
+        $cm = get_coursemodule_from_instance('sloodle', $s->id);
+        if ($cm === false) continue;
+            
+        // Prepare this line of data
+        $line = array();
+        $line[] = "<a href=\"{$CFG->wwwroot}/mod/sloodle/view.php?id={$cm->id}\">$s->name</a>";
+        $line[] = $s->intro;
+        // Insert it into the appropriate table
+        $sloodle_tables[$s->type]->data[] = $line;
+    }
+    
+    // Add header information to each table
+    // (cannot use "foreach" on the $sloodle_tables array as PHP4 doesn't support alteration of the original array that way)
+    $table_types = array_keys($sloodle_tables);
+    foreach ($table_types as $k) {
+        $sloodle_tables[$k]->head = array($strname, $strdescription);
+        $sloodle_tables[$k]->align = array('left', 'left');
+        $sloodle_tables[$k]->size = array('50%', '50%');
+    }
+
+    // Page header
+    if ($course->id != SITEID) {
+        sloodle_print_header("{$course->shortname}: $strsloodles", $course->fullname,
+                    "<a href=\"../../course/view.php?_type=course&amp;id=$course->id\">$course->shortname</a> -> $strsloodles",
+                    "", "", true, "", navmenu($course));
+    } else {
+        sloodle_print_header("$course->shortname: $strsloodles", $course->fullname, "$strsloodles",
+                    "", "", true, "", navmenu($course));
+    }
+    
+    
+//-----------------------------------------------------
+    // Quick links (top right of page)   
+
+    // Open the section
+    /*echo "<div style=\"text-align:right; font-size:80%;\">\n";
+    
+    // Link to own avatar profile
+    echo "<a href=\"\" title=\"\">View my avatar details</a><br>\n";
+    
+    // Course information
+    if (empty($sloodlecourse->autoreg)) {
+        echo "This course does not allow auto-registration<br>";
+    } else {
+        echo "This course allows auto-registration<br>";
+    }
+    
+    // Display the link for editing course settings
+    $course_context = get_context_instance(CONTEXT_COURSE, $course->id);
+    if (has_capability('moodle/course:update', $course_context)) {
+        echo "<a href=\"\" title=\"\">Edit Sloodle course settings</a><br>\n";
+    }
+    
+    
+    echo "</div>\n";*/
+    
+    
+    
+//-----------------------------------------------------
+    
+
+    // Make sure we got some results
+    if (is_array($sloodle_tables) && count($sloodle_tables) > 0) {
+        // Go through each Sloodle table
+        foreach ($sloodle_tables as $type => $table) {
+            // Output a heading for this type
+            if (!array_key_exists($type, $sloodle_type_names)) $sloodle_type_names[$type] = get_string("moduletype:{$type}", 'sloodle');
+            sloodle_print_heading($sloodle_type_names[$type],  'sloodle');
+            // Display the table
+            sloodle_print_table($table);
+        }
+    } else {
+        sloodle_print_heading(get_string('noentries', 'sloodle'));
+    }
+    
+    // Page footer
+    sloodle_print_footer($course);
+
+?>

+ 220 - 0
init.php

@@ -0,0 +1,220 @@
+<?php
+    /**
+    * Sloodle core script.
+    *
+    * Sets up the basic Sloodle information, and includes the necessary Moodle data/functionality.
+    *
+    * @package sloodle
+    *
+    */
+
+    /*
+    Linker scripts don't maintain cookies.
+    We need to set NO_MOODLE_COOKIES to prevent Moodle from creating a new session on every request.
+    */
+    if (defined('SLOODLE_LINKER_SCRIPT')) {
+        define('NO_MOODLE_COOKIES', true);
+    }
+
+    // Pull in the main moodle config
+    // NB the following is necessary for when we pull in this config.php from a module under sloodle/mod
+    require_once (realpath(dirname(__FILE__) . "/" . "../../config.php"));
+
+    require_once (realpath(dirname(__FILE__) . "/" . "lib/db.php"));
+    require_once (realpath(dirname(__FILE__) . "/" . "lib/compat.php"));
+    
+    // Is this a linker script?
+    if (defined('SLOODLE_LINKER_SCRIPT')) {
+        // If the site is in maintenance mode, then stop the script
+        if (file_exists($CFG->dataroot.'/'.SITEID.'/maintenance.html')) {
+            exit("-111|SYSTEM\nThe Moodle site is in maintenance mode. Please try again later.");
+        }
+        
+        // Use Unicode UTF-8 encoding to support non-English alphabets
+        if (!headers_sent()) header('Content-Type: text/plain; charset=UTF-8');
+    }
+    
+    
+//---------------------------------------------------------------------    
+    
+    /** The path for browsing to the root of the Sloodle folder. */
+    define('SLOODLE_WWWROOT', $CFG->wwwroot.'/mod/sloodle');
+    /** The data path for the root of the Sloodle folder. */
+    define('SLOODLE_DIRROOT', $CFG->dirroot.'/mod/sloodle');
+    /** The data path for the root of the Sloodle library folder. */
+    define('SLOODLE_LIBROOT', $CFG->dirroot.'/mod/sloodle/lib');
+    
+    /** The Sloodle version number. */
+    define('SLOODLE_VERSION', 2.1); // This is the release version, not the module version (which is in version.php)
+
+    // The following tells us whether Moodle is at > version 2  or not. 
+    define('SLOODLE_IS_ENVIRONMENT_MOODLE_2', ($CFG->version >= 2010060800) );
+
+//---------------------------------------------------------------------
+
+    /** The name of the HTTP parameter which can be used to activate Sloodle debug mode. */
+    define('SLOODLE_DEBUG_MODE_PARAM_NAME', 'sloodledebug');
+
+    // Check if debug mode is active
+    $sloodle_debug = 'false';
+    if (isset($_REQUEST[SLOODLE_DEBUG_MODE_PARAM_NAME])) $sloodle_debug = $_REQUEST[SLOODLE_DEBUG_MODE_PARAM_NAME];
+    if (strcasecmp($sloodle_debug, 'true') == 0 || $sloodle_debug == '1') {
+        define('SLOODLE_DEBUG', true);
+    } else {
+        define('SLOODLE_DEBUG', false);
+    }
+
+    
+    // Apply the effects of debug mode
+    if (SLOODLE_DEBUG) {
+        // Report all errors and warnings
+        @ini_set('display_errors', '1');
+        // Since we're in basic UTF8 text mode, disable the HTML in error codes
+        @ini_set('html_errors', '0');
+        //@error_reporting(2047);
+    } else {
+        // Debug mode is NOT active. Are we in a linker script?
+        if (defined('SLOODLE_LINKER_SCRIPT') && SLOODLE_LINKER_SCRIPT == true) {
+            // Yes - suppress the display of messages
+            @ini_set('display_errors', '0');
+        }
+    }
+    
+            @ini_set('display_errors', '0');
+    /**
+    * Outputs messages if in debug mode.
+    * @uses SLOODLE_DEBUG
+    * @param string $msg The debug message to output
+    * @return void
+    */
+    function sloodle_debug($msg)
+    {
+         if (SLOODLE_DEBUG) echo $msg;
+    }
+    
+//---------------------------------------------------------------------
+    // Types of Sloodle module
+    // These correspond to the "type" field in the "Sloodle" DB table
+    // Each name should be lower-case letters only (max 50)
+    // The full name should be specified in the appropriate language file, as "moduletype:type".
+    
+    // Each course needs to have at least one Sloodle Access Controller before it can be accessed from in-world.
+    // This is what grants access to the course as a whole, and sets prim passwords.
+    define('SLOODLE_TYPE_CTRL', 'controller');
+                                   
+    // These are the regular module types
+    define('SLOODLE_TYPE_DISTRIB', 'distributor');
+    define('SLOODLE_TYPE_PRESENTER', 'presenter');
+    define('SLOODLE_TYPE_MAP', 'map');
+    define('SLOODLE_TYPE_TRACKER', 'tracker');
+    
+    // Store the types in an array (used in lists)
+    global $SLOODLE_TYPES;   
+    $SLOODLE_TYPES = array();
+      
+    $SLOODLE_TYPES[] = SLOODLE_TYPE_CTRL;
+    $SLOODLE_TYPES[] = SLOODLE_TYPE_DISTRIB;
+    $SLOODLE_TYPES[] = SLOODLE_TYPE_PRESENTER;
+    $SLOODLE_TYPES[] = SLOODLE_TYPE_TRACKER;
+    
+    
+    
+//---------------------------------------------------------------------
+
+    // Access level constants
+    
+    /** Indicates that anybody may access an object in-world. */
+    define('SLOODLE_OBJECT_ACCESS_LEVEL_PUBLIC', 0);
+    /** Indicates that only the owner may access an object in-world. */
+    define('SLOODLE_OBJECT_ACCESS_LEVEL_OWNER', 1);
+    /** Indicates that only in-world group-members may access an object in-world. */
+    define('SLOODLE_OBJECT_ACCESS_LEVEL_GROUP', 2);
+    
+    /** Indicates that anybody may access a server resource (still requires an authenticated request). */
+    define('SLOODLE_SERVER_ACCESS_LEVEL_PUBLIC', 0);
+    /** Indicates that only those registered and enrolled in a specific course may access a server resource. */
+    define('SLOODLE_SERVER_ACCESS_LEVEL_COURSE', 1);
+    /** Indicates that only those registered on the site may access a server resource. */
+    define('SLOODLE_SERVER_ACCESS_LEVEL_SITE', 2);
+    /** Indicates that only those with Sloodle staff status on a course may access a server resource. */
+    define('SLOODLE_SERVER_ACCESS_LEVEL_STAFF', 3);    
+    
+
+//---------------------------------------------------------------------
+
+    /*
+    Customizable parameters follow.
+    Normally these will be defined in an optional 'sloodle_config.php' file.
+    For ease of upgrading, the sloodle_config.php will not be included in the release, although we will provide a sample.
+    */
+
+    if ( file_exists(SLOODLE_DIRROOT.'/sloodle_config.php') ) {
+        require_once(SLOODLE_DIRROOT.'/sloodle_config.php');
+    }
+
+    /*
+    Constants that need to have a default parameter for people with no config.php should set it below this line
+    ... conditional on it being undefined.
+    */
+
+    /** 
+    The collections of objects the rezzer supports. 
+    This corresponds to the collections parameter in the object definition of each object.
+    NB The array should be serialized because PHP won't do arrays as constants.
+    */
+    if ( !defined('SLOODLE_SUPPORTED_OBJECT_COLLECTIONS') ) {
+        define('SLOODLE_SUPPORTED_OBJECT_COLLECTIONS', serialize(array('SLOODLE 2.0') ) ); //
+    }
+
+    /*
+    How often objects should be told to ping us to let us know they're alive.
+    NB The initial ping from an object will be at a random proportion of this number
+    ...to provide some jitter and prevent the server being hammered by all the scripts at the same time.
+    */
+    if ( !defined('SLOODLE_PING_INTERVAL') ) {
+        define('SLOODLE_PING_INTERVAL', 3600);
+    }
+
+    /*
+    How often the rezzer should poll to check which objects may be rezzed
+    ...and provide a report of which, in any, are missing.
+    Since there is usually only one rezzer, you should be able to make this happen fairly fast without worrying about killing your server.
+    If you aren't worried about server resources, it would be meaningful to bring it down as low as 3 seconds.
+    */
+    if ( !defined('SLOODLE_REZZER_STATUS_CONFIRM_INTERVAL')) {
+        define('SLOODLE_REZZER_STATUS_CONFIRM_INTERVAL', 15);
+    }
+
+    // This activates freemail blogging.
+    // This is off by default in sloodle 2.1, as we haven't tested it properly yet.
+    // We will probably turn it on in 2.2.
+    // NB it will only work on Moodle 2.0 or higher.
+    if ( !defined('SLOODLE_FREEMAIL_ACTIVATE')) {
+        define('SLOODLE_FREEMAIL_ACTIVATE', false);
+    }
+
+//---------------------------------------------------------------------
+
+    /** General functionality. */
+    require_once(SLOODLE_LIBROOT.'/general.php');
+    /** Sloodle core library functionality */
+    require_once(SLOODLE_DIRROOT.'/lib.php');
+    /** Request and response functionality. */
+    require_once(SLOODLE_LIBROOT.'/io.php');
+    /** User functionality. */
+    require_once(SLOODLE_LIBROOT.'/user.php');
+    /** Course functionality. */
+    require_once(SLOODLE_LIBROOT.'/course.php');
+    /** Sloodle Controller functionality. */
+    require_once(SLOODLE_LIBROOT.'/controller.php');
+    /** Module functionality. */
+    require_once(SLOODLE_LIBROOT.'/modules.php');
+    /** Plugin management. */
+    require_once(SLOODLE_LIBROOT.'/plugins.php');
+    /** Active Objects config definitions. */
+    require_once(SLOODLE_LIBROOT.'/object_configs.php');
+    /** Active Objects and their definitions. */
+    require_once(SLOODLE_LIBROOT.'/active_object.php');
+
+
+?>

+ 12 - 0
lang/en/help/sloodle/auto_enrolment.html

@@ -0,0 +1,12 @@
+<h4>User Auto Enrolment</h4>
+<p>
+ Auto enrolment allows you to automatically enrol users from within Second Life.
+ This saves time, and prevents the need for SL users to interact directly with Moodle at all.
+ It must first be enabled at the site-level by an administrator, and it must then be enabled for each
+ course as well.
+</p>
+<h5 style="color:red;">Warning!</h5>
+<p>
+ Auto-enrolment does not support enrolment keys or plugins.
+ Only enable it on courses where enrolment is open.
+</p>

+ 15 - 0
lang/en/help/sloodle/auto_registration.html

@@ -0,0 +1,15 @@
+<h4>User Auto Registration</h4>
+<p>
+ Auto registration allows avatars to have a new Moodle account automatically registered for them
+ by Sloodle. This saves time, and prevents the need for SL users to interact directly with Moodle at all.
+ It must first be enabled at the site-level by an administrator, and it must then be enabled for each
+ course as well
+</p>
+<p>
+ If you enable auto-registration, then you will probably want to enable auto-enrolment as well.
+</p>
+<h5 style="color:red">Warning!</h5>
+<p>
+ Auto-registration only works with normal Moodle accounts.
+ It may not function correctly if you have setup Moodle networking or external user databases.
+</p>

+ 1 - 0
lang/en/help/sloodle/awards_confighelp.html

@@ -0,0 +1 @@
+You must select a "Sloodle Awards System" activity from the drop down menu that you created on your MOODLE site.  If you haven't created a Sloodle Awards System yet, create one now.  To do this, make sure you are logged in as a teacher or administrator.  Go to your course page, and click "Turn editing On".  Then click on the "Add an activity" drop down menu, and choose "Sloodle Awards System".

+ 1 - 0
lang/en/help/sloodle/awards_confighelp2.html

@@ -0,0 +1 @@
+Each Sloodle Awards System can be connected to an "Sloodle Award assignment."  Once you connect a Sloodle Awards System to a Sloodle Award Assignment, all points earned by students get added to the gradebook. If no assignments are listed in the drop down box, you will have to create a new assignment first.  When you create an assignment, you must choose "Sloodle Award" as the assignment type. 

+ 4 - 0
lang/en/help/sloodle/awardsassignment.html

@@ -0,0 +1,4 @@
+<h3>Grade Book Connectivity</h3>
+The Awards system can also be connected to a MOODLE Assignment.  When an assignment is selected in the Sloodle Awards Configuration page, anytime a transaction is made, the students grade for that assignment will get updated with their Sloodle Awards balance.  You may ask however, what happens when students achieve 2000 points or higher in Second Life, when the grade book only stores a max grade of 100? What grade will they receive?  
+<br><br>
+To calculate the percentage earned, the students balance is divided by the "Max Points" setting on the configuration page.  So, if the max points setting is at 1000, then if a student gets 500 points in Second Life for an educational activity, a grade of 50% will get entered into the database.  If though, the game addon is configured to allow the student to get an unlimited amount of points, an points after 1000 will not count towards their grade.  Therefore, if a student gets 5000 points, and the Max Points setting is set at 1000, their grade will be entered as 100% in the gradebook.

+ 2 - 0
lang/en/help/sloodle/icurrency.html

@@ -0,0 +1,2 @@
+ <h3>iCurrency</h3>
+ To configure this object, you will need to choose a currency type - <b>iPoints</b>, or <b>Lindens</b>.  If you choose Lindens, then addons such as the stipend giver, will allow you to give money to your students in Second Life.

+ 2 - 0
lang/en/help/sloodle/maxpoints.html

@@ -0,0 +1,2 @@
+<h3>Max Points</h3>
+To calculate the percentage earned, the students balance is divided by the "Max Points" setting on the configuration page.  So, if the max points setting is at 1000, then if a student gets 500 points in Second Life for an educational activity, a grade of 50% will get entered into the database.  If though, the game addon is configured to allow the student to get an unlimited amount of points, an points after 1000 will not count towards their grade.  Therefore, if a student gets 5000 points, and the Max Points setting is set at 1000, their grade will be entered as 100% in the gradebook.

Неке датотеке нису приказане због велике количине промена