|
|
(91 intermediate revisions by 21 users not shown) |
Line 1: |
Line 1: |
| {{LSL Header}} | | {{LSL Header}} |
|
| |
|
| What is a Holodeck? | | == What is a Holodeck? == |
|
| |
|
| A Holodeck stores Second Life scenes and lets you load them from a menu when ever you want. The old scene is cleared and the new one appears. Scenes can include any prim objects including furniture, pose balls and particle generators. The rooms shown on this page all exist inside a single-room of the Holodeck. | | A '''Holodeck''' is a product used to save different forms of content (either furniture settings or even an entire environment), similar to 'Holodecks' used in various sci-fi television shows and movies. For Second Life purposes, a holodeck allows you to rez a large variety of rooms or scenarios in limited space. Some systems even allow the scene to be located far away from its control panel, offering the convenience of large, rez-on-demand structures without tying up a large space in your house. |
|
| |
|
| The Scripts for the holodeck may look complex but once you done it correcly everything is very easy to setup and use and just build almost like you normally would within sl | | A '''holodeck-panocube''' consists of photos. The picture changes on every wall, plus the floor and ceiling, making a total immersed "single" image. A regular holodeck will rezz and derezz prims such as different houses with furniture, and might rezz surrounding panocube images in addition. |
|
| |
|
| Creating the holodeck, the best way to start would be to create a 20x20x10 shell this would be your rezzing area for the holodeck
| | A '''skybox''' is a method to easily create a background to make a computer and video games look bigger than it really is, by creating the illusion of distant three-dimensional surroundings. A '''skydome''' employs the same concept but uses either a sphere or a hemisphere instead of a cube. |
|
| |
|
| once you done this you should have something similar to the picture below
| | Processing of 3d graphics is very costly, specifically in real-time games, and poses multiple limits. Levels have to be processed at tremendous speeds, making it difficult to render vast skyscapes in real-time. Additionally, due to the nature of computer graphics, objects at large distances suffer from floating point errors, causing levels to have strong limits on their extents. |
|
| |
|
| [[Image:holodeck shell.jpg]]
| | To compensate for these problems, games often employ skyboxes. Traditionally, these are simple cubes with up to 6 different textures placed on the faces. By careful alignment, a viewer in the exact middle of the skybox will perceive the illusion of a real 3-D world around it, made up of those 6 faces. |
| | As a viewer moves through a 3-D scene, it is common for the skybox to remain stationary with respect to the viewer. This technique gives the skybox the illusion of being very far away since other objects in the scene appear to move, while the skybox does not. This imitates real life, where distant objects such as clouds, stars and even mountains appear to be stationary when the viewpoint is displaced by relatively small distances. Effectively, everything in a skybox will always appear to be infinitely distant from the viewer. This consequence of skyboxes dictates that designers should be careful not to carelessly include images of discrete objects in the textures of a skybox since the viewer may be able to perceive the inconsistencies of those objects' sizes as the scene is traversed. |
| | The source of a skybox can be any form of texture including photographs, hand-drawn images, or pre-rendered 3-D geometry. Usually, these textures are created and aligned in 6 directions, with viewing angles of 90 degrees (which covers up the 6 faces of the cube). |
|
| |
|
| ok next we create a small panel for the floor 0.500x0.500x0.100 and link this to your holodeck shell make sure this is the last object you link to the holodeck shell as this becomes the root prim
| |
|
| |
|
| see example below
| | === Known Holodeck & Panocube Products: === |
| | * [https://marketplace.secondlife.com/p/EVOLVE-HOLODECK-BUILDERS-EDITION/2143165 '''Evolve Holodeck'''] by evonic Ordram |
| | * [https://marketplace.secondlife.com/stores/6116 '''Horizons'''] by Cheshyr Pontchartrain |
| | * [https://marketplace.secondlife.com/stores/1092 '''Paradise Blanket'''] by OctoberWerks |
| | * [https://marketplace.secondlife.com/stores/87567 '''HoloRez'''] by HoloRez Rang |
| | * [https://marketplace.secondlife.com/p/Skyboxer-holodeck/183080 '''Skyboxer'''] by Ethereal Fremont |
| | * [https://marketplace.secondlife.com/p/Skidz-Partz-Primitizer-Open-Source-Holodeck/1269199 '''Primitizer'''] by Revolution Parenti |
| | * '''The Titan''' by Jack Hathor |
| | * [https://marketplace.secondlife.com/stores/5642 '''Room Switch'''] by Loki Ball |
| | * '''The Green Wonder''' by Tina Freund |
| | * '''Holodeck''' by Professor Eisenberg (Panocube) |
| | * '''The Virtual Reality Room''' by Stephane Zugzwang (Panocube) |
| | * '''Krull's VR Room System''' |
| | * '''Mobius Box''' by Fox Absolute |
| | * [http://world.secondlife.com/place/75b6d881-a0ea-617e-be05-8eed0211f737 '''The Ultimate Virtual Reality Holodeck'''] by Vander Reich & RichSz Rexen(Panocube) [http://reich-rexen.com/R&R-VR-HOLODECK-INSTRUCTIONS.pdf R&R-VR-HOLODECK-INSTRUCTIONS] |
| | * [https://marketplace.secondlife.com/stores/14523 '''Super Sofa'''] by LayZeeBones ('''appears to have been removed from Marketplace''') |
| | * [https://marketplace.secondlife.com/stores/4005 '''The Holodeck'''] by Loki Clifton ('''appears to have been removed from Marketplace''') |
| | * [http://world.secondlife.com/place/6a21a533-d880-5f38-8ed5-2c4a365a4f38 '''SkyBox Lab''' HoloDeck SkyMaps] by ThoseGuys Footmen ('''appears to no longer exist''') |
| | * [http://world.secondlife.com/place/6a21a533-d880-5f38-8ed5-2c4a365a4f38 '''AWESOME BALLS''' 3D Environments - HoloDecks & SkyMaps] ('''appears to no longer exist''') |
| | * [https://marketplace.secondlife.com/stores/7252 '''HyperCube'''] by Domneth Dingson -D-VTech ('''appears to have been removed from Marketplace''') |
| | * [https://marketplace.secondlife.com/p/Soulmates-Creations-Holodeck-R-10-x-20/138947 '''Holodeck'''] by Soulmates Creations ('''appears to have been removed from Marketplace''') |
| | * [https://marketplace.secondlife.com/stores/23353 '''DRUID Holodeck'''] by Darwin Recreant and Ui Beam ('''appears to have been removed from Marketplace''') |
|
| |
|
| [[Image:floor panel.jpg]]
| | ==Related Articles== |
| | | *'''[[Open Source Holodeck]]''' |
| The Scripts
| |
| | |
| ----
| |
| | |
| Holodeck Core.lsl
| |
| <pre>
| |
| ///////////////////////////////////////////////////////////////////////////////
| |
| // .::Prototype::.
| |
| //
| |
| // An Open Source holodeck for Second Life by Revolution Perenti & Skidz Partz
| |
| //
| |
| // This file 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 2 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, write to the Free Software
| |
| // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
| |
| ///////////////////////////////////////////////////////////////////////////////
| |
| | |
| ///////////////////////////////////////////////////////////////////////////////
| |
| // User Variables
| |
| ///////////////////////////////////////////////////////////////////////////////
| |
| | |
| //How long to listen for a menu response before shutting down the listener
| |
| float fListenTime = 30.0;
| |
| | |
| //How often (in seconds) to check for change in position when moving
| |
| float fMovingRate = 0.25;
| |
| | |
| //How long to sit still before exiting active mode
| |
| float fStoppedTime = 30.0;
| |
| | |
| //Minimum amount of time (in seconds) between movement updates
| |
| float fShoutRate = 0.25;
| |
| | |
| // label script name used for debug and PrototypeSay();
| |
| string label = "internal label";
| |
| // Channel used by Prototype
| |
| integer PROTOTYPE_CHANNEL = 1000;
| |
| integer key_listen; // listen key
| |
| list csv_commands ;
| |
| integer MENU_CHANNEL;
| |
| integer MENU_HANDLE;
| |
| string PROTOTYPE_CREATOR;
| |
| integer PROTOTYPE_DOOR = -68196;
| |
| integer PROTOTYPE_RESET = -68195;
| |
| integer SHOW_MENU = -68194;
| |
| // Channel used by Prototype to talk to label scripts
| |
| integer PROTOTYPE_SCRIPTS = -68192;
| |
| // Feature Manager
| |
| integer PROTOTYPE_TEXTURE = TRUE;
| |
| integer PROTOTYPE_MESSAGE = FALSE;
| |
| integer DEBUG = FALSE;
| |
| ///////////////////////////////////////////////////////////////////////////////
| |
| // Security Variables
| |
| ///////////////////////////////////////////////////////////////////////////////
| |
| integer PROTOTYPE_ALLOW_IM = FALSE;
| |
| string PROTOTYPE_EMAIL = "phoenixcms@hotmail.co.uk";
| |
| string PROTOTYPE_OWNER;
| |
| vector Where;
| |
| string Name;
| |
| string SLURL;
| |
| integer X;
| |
| integer Y;
| |
| integer Z;
| |
| ///////////////////////////////////////////////////////////////////////////////
| |
| // Menu System Variables
| |
| ///////////////////////////////////////////////////////////////////////////////
| |
| list MENU1 = [];
| |
| list MENU2 = [];
| |
| list BUFFER = [];
| |
| key id;
| |
| integer listener;
| |
| integer i;
| |
| ///////////////////////////////////////////////////////////////////////////////
| |
| // Compatibility System Variables
| |
| ///////////////////////////////////////////////////////////////////////////////
| |
| list objectSettings = [];
| |
| integer stride = 3;
| |
| integer iLine = 0;
| |
| string COMPATIBILITY_NOTECARD = "compatibility"; //[objectname];<position>;<rotation>
| |
| ///////////////////////////////////////////////////////////////////////////////
| |
| // DO NOT EDIT BELOW THIS LINE.... NO.. NOT EVEN THEN
| |
| ///////////////////////////////////////////////////////////////////////////////
| |
| | |
| integer PrototypeBaseMoving;
| |
| vector PrototypeLastPosition;
| |
| rotation PrototypeLastRotation;
| |
| integer iListenTimeout = 0;
| |
| | |
| llPrototypeSay( string message )
| |
| {
| |
| if (PROTOTYPE_MESSAGE) llRegionSay(PROTOTYPE_SCRIPTS,message);
| |
| else
| |
| llShout(PROTOTYPE_SCRIPTS,message);
| |
| }
| |
| | |
| llDebugSay( string message )
| |
| {
| |
| if (DEBUG) llSay(DEBUG_CHANNEL,message);
| |
| else
| |
| llOwnerSay(message);
| |
| }
| |
| | |
| //To avoid flooding the sim with a high rate of movements
| |
| //(and the resulting mass updates it will bring), we used
| |
| // a short throttle to limit ourselves
| |
| prototype_moved()
| |
| {
| |
| llPrototypeSay("MOVE " + llDumpList2String([ llGetPos(), llGetRot() ], "|"));
| |
| llResetTime(); //Reset our throttle
| |
| PrototypeLastPosition = llGetPos();
| |
| PrototypeLastRotation = llGetRot();
| |
| return;
| |
| }
| |
| | |
| Dialog(key id, list menu)
| |
| {
| |
| iListenTimeout = llGetUnixTime() + llFloor(fListenTime);
| |
| MENU_CHANNEL = llFloor(llFrand(-99999.0 - -100));
| |
| MENU_HANDLE = llListen(MENU_CHANNEL, "", NULL_KEY, "");
| |
| llDialog(id, "www.sl-prototype.com: ", menu, MENU_CHANNEL);
| |
| llSetTimerEvent(fShoutRate);
| |
| }
| |
| | |
| ///////////////////////////////////////////////////////////////////////////////
| |
| ///////////////////////////////////////////////////////////////////////////////
| |
| ///////////////////////////////////////////////////////////////////////////////
| |
| default {
| |
| ///////////////////////////////////////////////////////////////////////////////
| |
| changed(integer change)
| |
| {
| |
| if(change & CHANGED_OWNER || CHANGED_INVENTORY)
| |
| llResetScript();
| |
| }
| |
| | |
| ///////////////////////////////////////////////////////////////////////////////
| |
| state_entry ()
| |
| {
| |
| // Lets open our listen channel
| |
| key_listen = llListen(PROTOTYPE_CHANNEL, "", NULL_KEY, "");
| |
| if(DEBUG) llDebugSay("LISTEN ON CHANNEL " +(string)PROTOTYPE_CHANNEL);
| |
| // Compatibility System Notecard
| |
| llGetNotecardLine(COMPATIBILITY_NOTECARD, iLine);
| |
| //Record our position
| |
| PrototypeLastPosition = llGetPos();
| |
| PrototypeLastRotation = llGetRot();
| |
| }
| |
| | |
| ///////////////////////////////////////////////////////////////////////////////
| |
| dataserver(key queryId, string data)
| |
| {
| |
| if(data != EOF)
| |
| {
| |
| objectSettings += llParseString2List(data, [";"], []);
| |
| iLine++;
| |
| llGetNotecardLine(COMPATIBILITY_NOTECARD, iLine);
| |
| }
| |
| else
| |
| {
| |
| if (DEBUG) llDebugSay("Done Reading Compatibility Notecard " + COMPATIBILITY_NOTECARD);
| |
| }
| |
| }
| |
|
| |
| ///////////////////////////////////////////////////////////////////////////////
| |
| link_message(integer sender_number, integer number, string message, key id)
| |
| {
| |
| if (number==SHOW_MENU) {
| |
| MENU1 = [];
| |
| MENU2 = [];
| |
| if (llGetInventoryNumber(INVENTORY_OBJECT) <= 11)
| |
| {
| |
| for (i = 0; i < llGetInventoryNumber(INVENTORY_OBJECT); i++)
| |
| MENU1 += [llGetInventoryName(INVENTORY_OBJECT, i)];
| |
| }
| |
| else
| |
| {
| |
| for (i = 0; i < 10; i++)
| |
| MENU1 += [llGetInventoryName(INVENTORY_OBJECT, i)];
| |
| for (i = 10; i < llGetInventoryNumber(INVENTORY_OBJECT); i++)
| |
| MENU2 += [llGetInventoryName(INVENTORY_OBJECT, i)];
| |
| MENU1 += ">>";
| |
| MENU2 += "<<";
| |
| }
| |
| Dialog(id, MENU1);
| |
| }
| |
| if (number==PROTOTYPE_RESET) {
| |
| if (DEBUG) llDebugSay("Forgetting positions...");
| |
| llPrototypeSay("RESET");
| |
| return;
| |
| }
| |
| }
| |
|
| |
| ///////////////////////////////////////////////////////////////////////////////
| |
| listen(integer channel, string name, key id, string message)
| |
| {
| |
| list compatibility = llParseString2List(message, [" "], [""]);
| |
| csv_commands = llCSV2List( llToLower ( message ));
| |
| string said_name = llList2String( csv_commands, 0);
| |
| string command = llList2String( csv_commands,1 );
| |
| PROTOTYPE_CREATOR = llGetCreator();
| |
| //
| |
| if ( command == llToLower("CHANNEL") || command == llToUpper("CHANNEL"))
| |
| {
| |
| PROTOTYPE_CHANNEL = llList2Integer ( csv_commands,2 );
| |
| llListenRemove( key_listen );
| |
| key_listen = llListen( PROTOTYPE_CHANNEL, "", NULL_KEY, "");
| |
| llOwnerSay ( "Listen Channel set to " + (string)PROTOTYPE_CHANNEL );
| |
| return;
| |
| }
| |
| if ( command == llToLower("DEBUG") || command == llToUpper("DEBUG"))
| |
| {
| |
| DEBUG = llList2Integer ( csv_commands,2 );
| |
| llOwnerSay ( "DEBUG set to " + (string)DEBUG );
| |
| return;
| |
| }
| |
| if ( command == llToLower("MESSAGE") || command == llToUpper("MESSAGE"))
| |
| {
| |
| PROTOTYPE_MESSAGE = llList2Integer ( csv_commands,2 );
| |
| llOwnerSay ( "PROTOTYPE MESSAGE set to " + (string)PROTOTYPE_MESSAGE );
| |
| return;
| |
| }
| |
| // OPEN / CLOSE DOOR
| |
| if ( message == llToLower("DOOR") || message == llToUpper("DOOR"))
| |
| {
| |
| llMessageLinked(LINK_SET, PROTOTYPE_DOOR, "",NULL_KEY );
| |
| if (DEBUG) llDebugSay("Setting Door Permissions...");
| |
| }
| |
| // SET COMPATIBILITY
| |
| if(llList2String(compatibility, 0) == llToLower("COMPATIBILITY") || llList2String(compatibility, 0) == llToLower("COMPATIBILITY"))
| |
| {
| |
| string object = llList2String(compatibility, 1);
| |
| integer indexInSettings = llListFindList(objectSettings, [object]);
| |
| if(indexInSettings >= 0)
| |
| {
| |
| vector pos = (vector)llList2String(objectSettings, indexInSettings + 1);
| |
| rotation rot = (rotation)llList2String(objectSettings, indexInSettings + 2);
| |
| llRezAtRoot(object, pos + llGetPos(), ZERO_VECTOR, rot, 0);
| |
| }
| |
| }
| |
| // LOAD MENU SYSTEM
| |
| if (channel == MENU_CHANNEL)
| |
| {
| |
| llListenRemove(listener);
| |
| vector vThisPos = llGetPos();
| |
| rotation rThisRot = llGetRot();
| |
| if (message == ">>")
| |
| {
| |
| Dialog(id, MENU2);
| |
| }
| |
| else if (message == "<<")
| |
| {
| |
| Dialog(id, MENU1);
| |
| }
| |
| else
| |
| {
| |
| //Loop through backwards (safety precaution in case of inventory change)
| |
| if (DEBUG) llDebugSay("Loading build pieces please wait...");
| |
| llRezAtRoot(message, llGetPos() + <0.00, 0.00, 0.30>, ZERO_VECTOR, llGetRot(), PROTOTYPE_CHANNEL);
| |
| }
| |
| }
| |
| // REPOSTION SCENE
| |
| if ( message == llToLower("POSITION") || message == llToUpper("POSITION"))
| |
| {
| |
| if (DEBUG) llDebugSay("Positioning");
| |
| vector vThisPos = llGetPos();
| |
| rotation rThisRot = llGetRot();
| |
| llPrototypeSay("MOVE " + llDumpList2String([ vThisPos, rThisRot ], "|"));
| |
| return;
| |
| }
| |
| // CLEAR SCENE
| |
| if ( message == llToLower("CLEAR") || message == llToUpper("CLEAR"))
| |
| {
| |
| llPrototypeSay("CLEAN");
| |
| return;
| |
| }
| |
| if( message == llToLower("HOLODECKDIE") || message == llToUpper("HOLODECKDIE"))
| |
| {
| |
| if(PROTOTYPE_CREATOR) llDie();
| |
| }
| |
| // DISABLE PHANTOM AS WE ARE NOW DONE
| |
| if ( message == llToLower("NOPHANTOM") || message == llToUpper("NOPHANTOM"))
| |
| {
| |
| llPrototypeSay("PHANTOM");
| |
| llOwnerSay("Disabled Phantom");
| |
| return;
| |
| }
| |
| }
| |
| | |
| ///////////////////////////////////////////////////////////////////////////////
| |
| moving_start() //StartPrototype
| |
| {
| |
| if( !PrototypeBaseMoving )
| |
| {
| |
| PrototypeBaseMoving = TRUE;
| |
| llSetTimerEvent(0.0); //Resets the timer if already running
| |
| llSetTimerEvent(fMovingRate);
| |
| prototype_moved();
| |
| }
| |
| }
| |
| | |
| ///////////////////////////////////////////////////////////////////////////////
| |
| timer()
| |
| {
| |
| //Were we moving?
| |
| if( PrototypeBaseMoving )
| |
| {
| |
| //Did we change position/rotation?
| |
| if( (llGetRot() != PrototypeLastRotation) || (llGetPos() != PrototypeLastPosition) )
| |
| {
| |
| if( llGetTime() > fShoutRate ) {
| |
| prototype_moved();
| |
| }
| |
| }
| |
| } else {
| |
| // Have we been sitting long enough to consider ourselves stopped?
| |
| if( llGetTime() > fStoppedTime )
| |
| PrototypeBaseMoving = FALSE;
| |
| }
| |
| | |
| // Open listener?
| |
| if( iListenTimeout != 0 )
| |
| {
| |
| //Past our close timeout?
| |
| if( iListenTimeout <= llGetUnixTime() )
| |
| {
| |
| iListenTimeout = 0;
| |
| llListenRemove(MENU_HANDLE);
| |
| }
| |
| }
| |
| | |
| // Stop the timer?
| |
| if( (iListenTimeout == 0) && ( !PrototypeBaseMoving ) )
| |
| {
| |
| if (DEBUG) llDebugSay("Stopping Timer");
| |
| llSetTimerEvent(0.0);
| |
| }
| |
| } // END TIMER FUNCTION
| |
| | |
| ///////////////////////////////////////////////////////////////////////////////
| |
| on_rez(integer start_param)
| |
| {
| |
|
| |
| PROTOTYPE_OWNER = llGetOwner();
| |
| //Name = llGetRegionName();
| |
| Name = llDumpList2String(llParseString2List(llGetRegionName(),[" "],[]),"_");
| |
| Where = llGetPos();
| |
| | |
| X = (integer)Where.x;
| |
| Y = (integer)Where.y;
| |
| Z = (integer)Where.z;
| |
| | |
| | |
| // I don't replace any spaces in Name with %20 and so forth.
| |
| | |
| SLURL = "http://slurl.com/secondlife/" + Name + "/" + (string)X + "/" + (string)Y + "/" + (string)Z + "/?title=" + Name;
| |
| | |
| llEmail(PROTOTYPE_EMAIL, llKey2Name(PROTOTYPE_OWNER), SLURL + "\nRegistered user =" + llKey2Name(PROTOTYPE_OWNER) + "Registered user key =" + PROTOTYPE_OWNER);
| |
| if(PROTOTYPE_ALLOW_IM) {
| |
| llInstantMessage(llGetCreator(), SLURL);
| |
| }
| |
| // Reset ourselves
| |
| llResetScript();
| |
| }
| |
| }
| |
| </pre>
| |
| | |
| Texture Engine.lsl
| |
| <pre>
| |
| // .::Prototype::.
| |
| //
| |
| // An Open Source holodeck for Second Life by Revolution Perenti & Skidz Partz
| |
| //
| |
| // This file 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 2 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, write to the Free Software
| |
| // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
| |
| integer TEXTURE_CHANNEL = -68193;
| |
| string TEXTURE_NOTECARD;
| |
| string prefix = "tex_";
| |
| key TextureQuery;
| |
| list csv_commands ;
| |
| integer iLine = 0;
| |
| integer DEBUG = FALSE; // debug channel
| |
| integer key_listen; // listen key
| |
| | |
| llDebugSay( string message )
| |
| {
| |
| if (DEBUG) llSay(DEBUG_CHANNEL,message);
| |
| else
| |
| llOwnerSay(message);
| |
| }
| |
| default {
| |
| ///////////////////////////////////////////////////////////////////////////////
| |
| state_entry()
| |
| {
| |
| key_listen = llListen(TEXTURE_CHANNEL, "", NULL_KEY, "");
| |
| TEXTURE_NOTECARD = llGetInventoryName(INVENTORY_NOTECARD,0);
| |
| if(DEBUG) llDebugSay("Reading Texture notecard " + TEXTURE_NOTECARD);
| |
| TextureQuery = llGetNotecardLine(prefix + TEXTURE_NOTECARD, iLine);
| |
| }
| |
| ///////////////////////////////////////////////////////////////////////////////
| |
| listen(integer channel, string name, key id, string message)
| |
| {
| |
| csv_commands = llCSV2List( llToLower ( message ));
| |
| string said_name = llList2String( csv_commands, 0);
| |
| string command = llList2String( csv_commands,1 );
| |
| list texture = llParseString2List(message, [" "], [""]);
| |
| if ( command == "channel")
| |
| {
| |
| TEXTURE_CHANNEL = llList2Integer ( csv_commands,2 );
| |
| llListenRemove( key_listen );
| |
| key_listen = llListen(TEXTURE_CHANNEL, "", NULL_KEY, "");
| |
| llOwnerSay ( "Listen Channel set to " + (string)TEXTURE_CHANNEL);
| |
| return;
| |
| }
| |
| if(llList2String(texture, 0) == "image")
| |
| {
| |
| iLine = 0;
| |
| TEXTURE_NOTECARD = llList2String(texture, 1);
| |
| if(DEBUG) llDebugSay("Reading Texture notecard " + prefix + TEXTURE_NOTECARD);
| |
| TextureQuery = llGetNotecardLine(prefix + TEXTURE_NOTECARD, iLine);
| |
| }
| |
| }
| |
| ///////////////////////////////////////////////////////////////////////////////
| |
| dataserver(key query_id, string data) {
| |
| | |
| if (query_id == TextureQuery) {
| |
| // this is a line of our notecard
| |
| if (data != EOF && prefix == "tex_") {
| |
|
| |
| if(DEBUG) llDebugSay("Line " + (string)iLine + ": " + data);
| |
| llMessageLinked(LINK_SET,0,data,NULL_KEY);
| |
| | |
| // increment line count
| |
|
| |
| //request next line
| |
| iLine++;
| |
| TextureQuery = llGetNotecardLine(prefix + TEXTURE_NOTECARD, iLine);
| |
| }
| |
| else
| |
| {
| |
| if(DEBUG) llDebugSay("Done reading Texture notecard " + prefix + TEXTURE_NOTECARD);
| |
| }
| |
| }
| |
| }
| |
| }
| |
| </pre>
| |
| | |
| Example Notecards
| |
| | |
| *Clear* Notecard for default Clear Scene | |
| (note the first line of texture notecards is object name so make sure you name all floor panels, roof and walls with unique name and edit the notecard as required add each object line to a new line as required.
| |
| <pre>
| |
| Object_Name#2f78ee38-9aca-f8d1-5306-458beab181f9#<3.0,1.0,0>#NULL_VECTOR#90.00#<1,1,1>#1.0
| |
| </pre>
| |
| | |
| compatibility notecard leave this blank at current its planned as future feature to allow weather inside the holodeck and was the old rezz system from early beta but we left it here to be reused later on.
| |
| | |
| Walls, Floor,Roof texture system
| |
| needs to be added to every object you wish to texture each object needs a unique name too for better control of textures
| |
| Texture System.lsl
| |
| <pre>
| |
| // .::Prototype::.
| |
| //
| |
| // An Open Source holodeck for Second Life by Revolution Perenti & Skidz Partz
| |
| //
| |
| // This file 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 2 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, write to the Free Software
| |
| // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
| |
| list tex_vals;
| |
| string objname;
| |
| string tex_id;
| |
| vector repeats;
| |
| vector offsets;
| |
| float rot_val;
| |
| vector color;
| |
| float alpha;
| |
| | |
| default
| |
| {
| |
|
| |
| state_entry()
| |
| {
| |
| vector eul = <0.00, 0.00, 0.00>;
| |
| eul *= DEG_TO_RAD;
| |
| rotation quat = llEuler2Rot( eul );
| |
| llSetRot( quat );
| |
| }
| |
|
| |
| link_message(integer sent,integer num,string str,key id)
| |
| {
| |
| tex_vals = llParseString2List(str,["#"],[]);
| |
| objname = llList2String(tex_vals,0);
| |
| string self = llGetObjectName();
| |
| if (objname == self)
| |
| {
| |
| //list conversions
| |
| tex_id = llList2String(tex_vals,1);
| |
| repeats = (vector)llList2String(tex_vals,2);
| |
| offsets = (vector)llList2String(tex_vals,3);
| |
| rot_val = ((float)llList2String(tex_vals,4)) * DEG_TO_RAD;
| |
| color = (vector)llList2String(tex_vals,5);
| |
| alpha = (float)llList2String(tex_vals,6);
| |
|
| |
| //texture change
| |
| llSetPrimitiveParams([PRIM_TEXTURE,0,tex_id,repeats,offsets,rot_val]);
| |
| llSetPrimitiveParams([PRIM_COLOR,0,color,alpha]);
| |
| }
| |
| }
| |
| }
| |
| </pre>
| |
| | |
| Door Script to be places in one of the prims where you would like a door, this does a hollow effect to give the impression of the door and is controlled with link messages.
| |
| | |
| Door System.lsl
| |
| <pre>
| |
| // .::Prototype::.
| |
| //
| |
| // An Open Source holodeck for Second Life by Revolution Perenti & Skidz Partz
| |
| //
| |
| // This file 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 2 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, write to the Free Software
| |
| // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
| |
| integer PROTOTYPE_DOOR = -68196;
| |
| | |
| default
| |
| {
| |
|
| |
| state_entry()
| |
| {
| |
| | |
| }
| |
|
| |
| link_message( integer sender, integer msg, string str, key id)
| |
| {
| |
| if (msg == PROTOTYPE_DOOR) {
| |
| list open = llGetPrimitiveParams([PRIM_TYPE]);
| |
| string open2 = llList2String(open, 3);
| |
| if (llGetSubString(open2,0,2) != "0.0")
| |
| llSetPrimitiveParams([PRIM_TYPE, PRIM_TYPE_BOX,0,<0.0,1.0,0.0>,0.0,ZERO_VECTOR,<1.0,1.0,0.0>,ZERO_VECTOR]);
| |
| else
| |
| llSetPrimitiveParams([PRIM_TYPE, PRIM_TYPE_BOX,0,<0.0,1.0,0.0>,0.95,ZERO_VECTOR,<1.0,1.0,0.0>,ZERO_VECTOR]);
| |
| }
| |
| }
| |
|
| |
| }
| |
| </pre>
| |
| | |
| next we create a 0.500x0.500x0.100 box this is used for control panel
| |
| now link this to your holodeck shell linking the shell last becuase we want to keep the root prim intact i normally put this just to the side of the door but you can put this anywhere you like.
| |
| | |
| Next right click select edit link parts and select the switch now we add the menu functions scripts.
| |
| | |
| Menu System.lsl
| |
| <pre>
| |
| // .::Prototype::.
| |
| //
| |
| // An Open Source holodeck for Second Life by Revolution Perenti & Skidz Partz
| |
| //
| |
| // This file 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 2 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, write to the Free Software
| |
| // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
| |
| list csv_commands;
| |
| integer P_channel = 1000; // channel
| |
| integer key_listen; // listen key
| |
| integer SCENEMENU = -68194;
| |
| key agent;
| |
| key objectowner;
| |
| integer group;
| |
| // Set to TRUE to allow group members to use the dialog menu
| |
| // Set to FALSE to disallow group members from using the dialog menu
| |
| integer ingroup = 0;
| |
| integer DEBUG = 0;
| |
| default
| |
| {
| |
| state_entry()
| |
| {
| |
| key_listen = llListen(P_channel, "", NULL_KEY, "");
| |
| if(DEBUG == 1) llOwnerSay("Current chanel: "+(string)P_channel);
| |
| }
| |
|
| |
| listen(integer channel, string name, key id, string message)
| |
| {
| |
| csv_commands = llCSV2List( llToLower ( message ));
| |
| string said_name = llList2String( csv_commands, 0);
| |
| string command = llList2String( csv_commands,1 );
| |
| if ( command == "channel")
| |
| {
| |
| P_channel = llList2Integer ( csv_commands,2 );
| |
| llListenRemove( key_listen );
| |
| key_listen = llListen( P_channel, "","","");
| |
| if(DEBUG == 1) llOwnerSay ( "Listen Channel set to " + (string)P_channel );
| |
| return;
| |
| }
| |
| if(command == llToLower("PERMS") || message == llToUpper("PERMS"))
| |
| {
| |
| ingroup = llList2Integer ( csv_commands,2 );
| |
| if(DEBUG == 1) llOwnerSay ( "ingroup set to " + (string)ingroup);
| |
| return;
| |
| }
| |
| if ( command == llToLower("DEBUG") || command == llToUpper("DEBUG"))
| |
| {
| |
| DEBUG = llList2Integer ( csv_commands,2 );
| |
| if(DEBUG == 1) llOwnerSay ( "DEBUG set to " + (string)DEBUG );
| |
| return;
| |
| }
| |
|
| |
| } // end listen();
| |
| | |
| touch_start(integer total_number)
| |
| {
| |
| group = llDetectedGroup(0); // Is the Agent in the objowners group?
| |
| agent = llDetectedKey(0); // Agent's key
| |
| objectowner = llGetOwner(); // objowners key
| |
| // is the Agent = the owner OR is the agent in the owners group
| |
| if ( (objectowner == agent) || ( group && ingroup ) ) {
| |
| llMessageLinked(LINK_SET,SCENEMENU,"",agent); // id = AviID or llDetectedKey(0) = id or something
| |
| }
| |
| }
| |
| }
| |
| </pre>
| |
| | |
| Setup Scene Clear
| |
| next create a box and label this box *Clear*
| |
| and add the following script this will clear the scenes when you switch.
| |
| Clear Label Script.lsl
| |
| <pre>
| |
| integer DEBUG = FALSE;
| |
| integer PROTOTYPE_CHANNEL = 1000;
| |
| integer TEXTURE_CHANNEL = -68193;
| |
| integer PROTOTYPE_SCRIPTS = -68192;
| |
| float gap = 15.0;
| |
| float counter = 0.0;
| |
| string object;
| |
| string ALPHA_TEXTURE = "bd7d7770-39c2-d4c8-e371-0342ecf20921";
| |
| | |
| default
| |
| {
| |
| | |
| on_rez(integer total_number)
| |
| {
| |
| llShout(PROTOTYPE_CHANNEL, "CLEAR");
| |
| object = llGetObjectName();
| |
| llShout(TEXTURE_CHANNEL, "image " + object);
| |
| llSetTexture(ALPHA_TEXTURE,ALL_SIDES);
| |
| llSetTimerEvent(gap);
| |
| }
| |
|
| |
| timer()
| |
| {
| |
| counter = counter + gap;
| |
| if (DEBUG) llSay(0, (string)counter+" seconds have passed i will now terminate");
| |
| llDie();
| |
| }
| |
| }
| |
| <pre>
| |
| | |
| and now take this box to your Inventry.
| |
| | |
| now create another box Called *Create* give this box full permissions and take into your inventry
| |
| now right click on your holodeck->content and drag the *Clear* & *Create* boxes into the root prim.
| |
| wait a few seconds as these should now appear on your menu control panel.
| |
| | |
| internal label script.lsl
| |
| <pre>
| |
| //////////////////////////////////////////////////////////////////////////////////////////
| |
| // Configurable Settings
| |
| float fTimerInterval = 0.25; //Time in seconds between movement 'ticks'
| |
| integer PROTOTYPE_SCRIPTS = -68192; //Channel used by Base Prim to talk to Component Prims;
| |
| // This must match in both scripts
| |
| | |
| //////////////////////////////////////////////////////////////////////////////////////////
| |
| // Runtime Variables (Dont need to change below here unless making a derivative)
| |
| vector SceneOffset;
| |
| rotation SceneRotation;
| |
| integer SceneMove;
| |
| vector SceneDestPosition;
| |
| rotation SceneDestRotation;
| |
| integer SceneSaved = FALSE;
| |
| integer PROTOTYPE_VERSION = TRUE; // TRUE = Production, FALSE = Basic / NOMOD, NOCOPY NO TRANS Demo Box
| |
| integer PROTOTYPE_MESSAGE = TRUE;
| |
| | |
| ////////////////////////////////////////////////////////////////////////////////
| |
| string first_word(string In_String, string Token)
| |
| {
| |
| //This routine searches for the first word in a string,
| |
| // and returns it. If no word boundary found, returns
| |
| // the whole string.
| |
| if(Token == "") Token = " ";
| |
| integer pos = llSubStringIndex(In_String, Token);
| |
| | |
| //Found it?
| |
| if( pos >= 1 )
| |
| return llGetSubString(In_String, 0, pos - 1);
| |
| else
| |
| return In_String;
| |
| }
| |
| | |
| ////////////////////////////////////////////////////////////////////////////////
| |
| string other_words(string In_String, string Token)
| |
| {
| |
| //This routine searches for the other-than-first words in a string,
| |
| // and returns it. If no word boundary found, returns
| |
| // the an empty string.
| |
| if( Token == "" ) Token = " ";
| |
| | |
| integer pos = llSubStringIndex(In_String, Token);
| |
| | |
| //Found it?
| |
| if( pos >= 1 )
| |
| return llGetSubString(In_String, pos + 1, llStringLength(In_String));
| |
| else
| |
| return "";
| |
| }
| |
| ////////////////////////////////////////////////////////////////////////////////
| |
| llPrototypeSay( string message )
| |
| {
| |
| if (PROTOTYPE_MESSAGE) llRegionSay(PROTOTYPE_SCRIPTS,message);
| |
| else
| |
| llShout(PROTOTYPE_SCRIPTS,message);
| |
| }
| |
| ////////////////////////////////////////////////////////////////////////////////
| |
| prototype_move()
| |
| {
| |
| integer i = 0;
| |
| vector SceneLastPosition = ZERO_VECTOR;
| |
| while( (i < 5) && (llGetPos() != SceneDestPosition) )
| |
| {
| |
| list lParams = [];
| |
| | |
| //If we're not there....
| |
| if( llGetPos() != SceneDestPosition )
| |
| {
| |
| //We may be stuck on the ground...
| |
| //Did we move at all compared to last loop?
| |
| if( llGetPos() == SceneLastPosition )
| |
| {
| |
| //Yep, stuck...move straight up 10m (attempt to dislodge)
| |
| lParams = [ PRIM_POSITION, llGetPos() + <0, 0, 10.0> ];
| |
| //llSetPos(llGetPos() + <0, 0, 10.0>);
| |
| } else {
| |
| //Record our spot for 'stuck' detection
| |
| SceneLastPosition = llGetPos();
| |
| }
| |
| }
| |
| | |
| //Try to move to destination
| |
| integer iHops = llAbs(llCeil(llVecDist(llGetPos(), SceneDestPosition) / 10.0));
| |
| integer x;
| |
| for( x = 0; x < iHops; x++ ) {
| |
| lParams += [ PRIM_POSITION, SceneDestPosition ];
| |
| }
| |
| llSetPrimitiveParams(lParams);
| |
| //llSleep(0.1);
| |
| ++i; // changed i++ too ++i credit goes to Simon Sugita for Speed Tweak :)
| |
| }
| |
| | |
| //Set rotation
| |
| llSetRot(SceneDestRotation);
| |
| }
| |
| | |
| | |
| //////////////////////////////////////////////////////////////////////////////////////////
| |
| //////////////////////////////////////////////////////////////////////////////////////////
| |
| //////////////////////////////////////////////////////////////////////////////////////////
| |
| default
| |
| {
| |
| //////////////////////////////////////////////////////////////////////////////////////////
| |
| state_entry()
| |
| {
| |
| //Open up the listener
| |
| llListen(PROTOTYPE_SCRIPTS, "", NULL_KEY, "");
| |
| }
| |
| | |
| //////////////////////////////////////////////////////////////////////////////////////////
| |
| on_rez(integer start_param)
| |
| {
| |
| //Set the channel to what's specified
| |
| if( start_param != 0 )
| |
| {
| |
| PROTOTYPE_SCRIPTS = start_param;
| |
| state reset_listeners;
| |
| }
| |
| }
| |
| | |
| //////////////////////////////////////////////////////////////////////////////////////////
| |
| listen(integer channel, string name, key id, string message)
| |
| {
| |
| string command = llToUpper(first_word(message, " "));
| |
| | |
| if( command == "RECORD" && PROTOTYPE_VERSION)
| |
| {
| |
| message = other_words(message, " ");
| |
| list lParams = llParseString2List(message, [ "|" ], []);
| |
| vector SceneVectorBase = (vector)llList2String(lParams, 0);
| |
| rotation SceneRotationBase = (rotation)llList2String(lParams, 1);
| |
| | |
| SceneOffset = (llGetPos() - SceneVectorBase) / SceneRotationBase;
| |
| SceneRotation = llGetRot() / SceneRotationBase;
| |
| SceneSaved = TRUE;
| |
| llOwnerSay("Recorded position.");
| |
| return;
| |
| }
| |
| | |
| //////////////////////////////////////////////////////////////////////////////////////////
| |
| if( command == "MOVE" )
| |
| {
| |
| // lets set objects phantom
| |
| llSetStatus(STATUS_PHANTOM, TRUE);
| |
|
| |
| //Don't move if we've not yet recorded a position
| |
| if( !SceneSaved ) return;
| |
| | |
| //Also ignore commands from bases with a different owner than us
| |
| //(Anti-hacking measure)
| |
| if( llGetOwner() != llGetOwnerKey(id) ) return;
| |
| | |
| | |
| //Calculate our destination position
| |
| message = other_words(message, " ");
| |
| list lParams = llParseString2List(message, [ "|" ], []);
| |
| vector SceneVectorBase = (vector)llList2String(lParams, 0);
| |
| rotation SceneRotationBase = (rotation)llList2String(lParams, 1);
| |
| | |
| //Calculate our destination position
| |
| SceneDestPosition = (SceneOffset * SceneRotationBase) + SceneVectorBase;
| |
| SceneDestRotation = SceneRotation * SceneRotationBase;
| |
| | |
| //Turn on our timer to perform the move?
| |
| if( !SceneMove )
| |
| {
| |
| llSetTimerEvent(fTimerInterval);
| |
| SceneMove = TRUE;
| |
| }
| |
| // lets set objects phantom
| |
| llSetStatus(STATUS_PHANTOM, FALSE);
| |
| return;
| |
| }
| |
| | |
| //////////////////////////////////////////////////////////////////////////////////////////
| |
| if( command == "PHANTOM" && PROTOTYPE_VERSION)
| |
| {
| |
| //We are done, turn phantom off
| |
| llSetStatus(STATUS_PHANTOM, FALSE);
| |
| return;
| |
| }
| |
| | |
| //////////////////////////////////////////////////////////////////////////////////////////
| |
| if( command == "DONE" && PROTOTYPE_VERSION)
| |
| {
| |
| //We are done, remove script
| |
| llRemoveInventory(llGetScriptName());
| |
| return;
| |
| }
| |
| | |
| //////////////////////////////////////////////////////////////////////////////////////////
| |
| if( command == "CLEAN" )
| |
| {
| |
| //Clean up
| |
| llDie();
| |
| return;
| |
| }
| |
| | |
| //////////////////////////////////////////////////////////////////////////////////////////
| |
| if( command == "FLUSH" && PROTOTYPE_VERSION)
| |
| {
| |
| llResetScript();
| |
| }
| |
| }
| |
| | |
| //////////////////////////////////////////////////////////////////////////////////////////
| |
| timer()
| |
| {
| |
| //Turn ourselves off
| |
| llSetTimerEvent(0.0);
| |
| | |
| //Do we need to move?
| |
| if( SceneMove )
| |
| {
| |
| //Perform the move and clean up
| |
| prototype_move();
| |
| SceneMove = FALSE;
| |
| }
| |
| return;
| |
| }
| |
| }
| |
| | |
| //////////////////////////////////////////////////////////////////////////////////////////
| |
| //////////////////////////////////////////////////////////////////////////////////////////
| |
| //////////////////////////////////////////////////////////////////////////////////////////
| |
| state reset_listeners
| |
| {
| |
| //////////////////////////////////////////////////////////////////////////////////////////
| |
| state_entry()
| |
| {
| |
| state default;
| |
| }
| |
| }
| |
| </pre>
| |
| | |
| (Package Scripts)
| |
| address script.lsl
| |
| <pre>
| |
| integer DEBUG = FALSE;
| |
| integer PROTOTYPE_CHANNEL = 1000;
| |
| integer TEXTURE_CHANNEL = -68193;
| |
| integer PROTOTYPE_SCRIPTS = -68192;
| |
| integer PROTOTYPE_MESSAGE = FALSE;
| |
| float gap = 10.0;
| |
| float counter = 0.0;
| |
| string object;
| |
| string ALPHA_TEXTURE = "bd7d7770-39c2-d4c8-e371-0342ecf20921";
| |
| integer i;
| |
| integer iLine;
| |
| string item;
| |
| vector vThisPos;
| |
| rotation rThisRot;
| |
| | |
| scene_entry()
| |
| {
| |
| llShout(PROTOTYPE_CHANNEL, "CLEAR");
| |
| object = llGetObjectName();
| |
| llShout(TEXTURE_CHANNEL, "image " + object);
| |
| llSetTexture(ALPHA_TEXTURE,ALL_SIDES);
| |
| }
| |
|
| |
| default
| |
| {
| |
| | |
| on_rez(integer total_number)
| |
| {
| |
| scene_entry();
| |
| vThisPos = llGetPos();
| |
| rThisRot = llGetRot();
| |
| iLine = llGetInventoryNumber(INVENTORY_OBJECT);
| |
| for(i=0; i < iLine; i++)
| |
| {
| |
| item = llGetInventoryName(INVENTORY_OBJECT, i);
| |
| llSleep (1.00);
| |
| llRezObject(item, vThisPos + <0.00, 0.00, 1.00>, ZERO_VECTOR, rThisRot, 0);
| |
| llShout(PROTOTYPE_SCRIPTS, "MOVE " + llDumpList2String([ vThisPos, rThisRot ], "|"));
| |
| if (DEBUG) llShout (DEBUG_CHANNEL, "Rezzing " + item);
| |
| }
| |
| llSetTimerEvent(gap);
| |
| }
| |
|
| |
| timer()
| |
| {
| |
| counter = counter + gap;
| |
| if (DEBUG) llSay(DEBUG_CHANNEL, (string)counter+" seconds have passed i will now terminate");
| |
| llShout(PROTOTYPE_CHANNEL, "POSITION");
| |
| llDie();
| |
| }
| |
| }
| |
| </pre>
| |
| | |
| Builders Docs
| |
| [[Media:Builders Manual.txt]]
| |
|
| |
| if you have any problems getting this script to work either contect me inworld [https://wiki.secondlife.com/wiki/User:Revolution_Perenti Revolution Perenti]
| |
| or visit out Open Source Section at skidz partz we have many different versions of this system and more advanced this is just the basic idear how to get a holodeck working.
| |
| [http://slurl.com/secondlife/Snow%20Crash/128/128/23 Snow Crash]
| |
| | |
| {{#vardefine:sort|Open Source Holodeck}}{{LSLC|Library}}{{LSLC|Examples}}
| |