 So, good morning everybody, most of you know me, I'm Syregia and I'm going to present today on the Linden scripting language. So let's just get into it. Our agenda, it's going to be a fairly brief overview and we will discuss how it's oriented toward event processing, look at some practical scripts, and incidentally in real life I'm Robert Lawson Brown, I'm a physicist, I'm retired, been from some 47 years in industrial semiconductor processing, various roles, and I got into programming because all physicists do, starting way back in 1969 with Fortran. The Linden scripting language, LSL, is a domain specific scripting language, and by that I mean that it doesn't really have much use outside of the environment of second life and open sim, but its purpose is to provide interactivity with things like vehicles and controls and non-playing characters and providing conveniences such as teleports. Here's an example, this is a little vehicle and I put a copy of it over next to the presentation screen, and like most vehicles it has a little sit pad, that's the one that has been highlighted in the editing mode, and you can bring up the editing panel that you see here. All objects are made up of prims and meshes, and each one of a prim or a mesh has properties and general properties, physical properties such as position and size, various features that are set, the textures that give it its visual look, and some contents, and the contents can include things like animations, sounds, pictures, textures, and so forth, and scripts that can be used to provide interactivity for them. And here's a little script that's inside the vehicle, and this is actually the sitting script, so you can see it has the sitting position, and you can see here that the scroll foot responses to various controls that will move the vehicle around. Here's another example, this is a door, and the door has a script that opens and closes it upon being touched, also plays a little sound and provides a smooth motion. That particular door by the way is an airlock door, so it's paired with another door, an inner and outer door, and there's a safety interlock so that you can't open both doors simultaneously as you would not want to do with an actual airlock. So how does LSAO relate to general computer programming, because it is quite different, and as I will go over here it is implementing an event-driven execution and imperative structure. So just for a couple slides, let's look at computer programs in general. So very briefly, any program is a set of instructions that is known as code, and computers execute code to perform specific tasks. The language for this is very specialized and somewhat arcane. I've used quite a few over the decades, currently the biggies are Python, Java, and JavaScript. Also programs are just not code by themselves. You also have to have data that the code will be working upon. So you need the step-by-step instructions, you also need the data that is going to be used. We have two general categories, the procedural and event-driven. Now a procedural program goes step-by-step to manipulate data, generate a product or media, whereas the event-driven is waiting for something to happen, to which the program will respond, and the programs that run your iPhone or smartphone and tablets and so forth are very much event-driven, and they're simply idling there, just waiting for you to press a key, something like 95% of the time. For procedural programming, when people learn a new language, the very first thing they do is to write a program that says hello world. So I just threw a couple of snippets in here to show you how that is being done. And one is written in the Swift language, which is printing the hello world five times. Then I show a similar snippet in LSL, which instead of a print has owner say, which basically puts hello world into the chat line for the owner to hear. In an event-driven program, or event-oriented if you prefer, you register an event, such as I first tell it that I want the script to listen, and I want it to listen in the example I show here, specifically for the owner. Then I have an event handler. An event handler, when triggered, will execute a small amount of code. In this case, it says that when you hear something, which they refer to as a listen event, it will say back to the owner hello world. Now the important thing is that this only does so when the event happens. It does not take place in any given sequence, and that is going to be the most important part of working with LSL. Just real briefly, there are other coding styles that people will talk about. I won't go through this really, just wanted to throw it up so that you'll know that there are different ways you can write your statements in code. In particular, we're interested in the imperative form, because in the Linden scripting language, while we're event-oriented, the events didn't trigger imperatives. We also used functions where possible to help encapsulate and reuse code. I should also mention that I can put multiple scripts into one object, and that kind of lets me pretend that I'm doing object-oriented code, but that can be somewhat argued. So let's look at the code organization now. All of the LSL is organized into blocks. The block is inside curly brackets, and each block has a header that indicates the type of block. And the example I show here is a state block. So I say, here's a state, it has some name, and then there are the curly brackets that will contain its code. The types of blocks, we have function blocks, which are executed by invoking a function. State blocks, which are executed by switching to different states. Event blocks, which are triggered when an event occurs. And then control blocks, if the type statements, logical statements that control the flow of the program. Let's go through these. For functions, these are reusable blocks of code. We generally do that at the top level of the program, and I will get to explaining types shortly, but you do have to say what type of a function you're going to declare. For example, here I have a function that's going to return a vector value. So I say vector, I give it a name, my function, I give it a list of parameters that are going to be passed into that function. And then in my curly brackets, I have the code for that function. In this case, it's returning a vector from the function. There are 481 predefined functions in LSL. And I can only put a tiny fraction of those on one slide here. But there is a handy reference online in the LSL Ricky. We're looking all of these up, and they all handle things like changing prim properties and scanning the environment or working with other events. So you can take a look at those states. Okay, now a state is something you won't find often in other languages. There's a few that use states. But states organize the code into blocks that have distinct behaviors. Occasionally, or not occasionally, actually all the time, you're going to want to handle events in different ways, depending upon what your script is currently doing. So we declare state blocks after the functions. And a state block will have event detectors and the event handlers. Under the control of the program, it will shift from one state to another. By the way, there is one state called default state, which is mandatory. That's a state you want to start in. Events are things that happen externally and are detected by the script. And you have each event handler declared inside of a state block. Now, each event has to be registered. And the reason is that you don't want the server, the simulation server, to be throwing every event that happens around you at your script. You only want to queue those up for the script to handle that are important. So you can turn awareness of certain events on and off. And again, the important part of LSL is it's event awareness for objects being touched, objects being sent nearby, messages being heard, information received from an external server, and several other types of events. And I will show a little clearer what a state is when we get looking at some of the code. There are 38 different events. And I've listed them here. And you can see what they are. We have ones that are at target that means I was moving and I got to my destination. I have changed and it indicates something that's changed on the object. We have collisions between objects and so forth, many types. Now, this whole event thing means that the scripts can be a little tricky to build. And that's because events can invoke different parts of the program in any order. You don't have any control over that. If you set it up to have touches and listens and that sort of thing, you don't know when someone is going to chat something that the program hears or touches the object or a message comes in from an outside server or other such things. And like I say, LSA is unique in that it has this event where it is built in. So I refer to registering an event. That's where you use a built-in function to filter events, to not have them overwhelm the script. When an event happens, the simulation server checks to see if a script has registered that event and the event matches the filter. If it matches, the event details are placed in a queue for the script. The script pulls the queue and pulls events on a first-in and first-out basis. So for example, let's say that you are looking at events where both you're looking at touching and scanning, scanning from your biobjects. Well, if someone touches that will be handled first and then a scanner picks something up that I'd be handled next. The queue can only hold 64 events. If more events happen than that before the script can handle them, it simply discards the more recent events. So it is possible to overload a script and have it start ignoring people. There are some special events that are not queued. For example, when I change the state of a script, I have state entry and state exit. Those are handled immediately. There's also an event for resing an object. And if you res an object, that event is handled immediately. We also have touch collision and money transactions that are not filtered. The script is always aware of those. They are queued though. So if people try to give you money too fast, you'll miss the more recent ones. Most of the event registrations such as sensor and listen are cleared when you change the state of the script. There is an exception to that, the timer event. Each script has only one timer running. And that, by the way, is a good reason for putting different parts of your behavior into different scripts. Because then you can have different timers for each one. Then we have control flow in blocks. Well, these are things that take some sort of a logical test before they execute the code. So you have, for example, if something equals something else, then you do this. And we also have controls like while this is true, do that. There are seven types of control flow. I mentioned state. And state is a block that says you leave the current block of code. You start up a whole different block of code under this new state name. When you use state to shift control, just being a little pedantic here, it's an imperative statement. But don't worry about that too much. It doesn't have any curly brackets with it. It's an event, but it doesn't have any curly brackets. Do while, that means you do the associated block of code, then you check a condition. So it's going to execute that code at least once. And then it will see if it wants to continue executing it over and over again. While is the opposite way. It first checks the condition. And if that condition is okay, then it does the associated block. And it will do it over and over again, so long as the condition is true. Far is a loop. So I might, for example, set up an index, say index equals five. And I decrement it each time I do the block. So I'll execute it five times. You may remember that when I showed the hello world part, I had a far loop that said to the owner hello world five times. Inside a block, I can put a return statement. And when the return statement is encountered, it will exit that block and go to the next highest block above it. And that'll become clearer when I show some code snippets. And then finally, I have jump. Now you can label lines of code and jump says go immediately to that line of code. I don't recommend using jump. Jump is something that has persisted in programming languages ever since the days of the basic programming language some 50 years ago. And it causes spaghetti code. So we tend to say please don't use jumps. And as my child has mentioned, it is all called the go to in some languages. Control blocks can be inside event handler blocks. It can be inside function blocks with one exception. And that's that state flow control. That cannot be outside of an event handler. It always has to be inside an event handler. Can't be in a function. And I find that unfortunate. I've seen many occasions where it'd be nice to have state control inside a function, but it's just forbidden. You cannot do that. So state blocks have event handlers. Event handler blocks have imperatives. And that is the most important structure you will see in a LSA program. The code shifts over one or more states. Each state has its own event handlers. And each event handler has its own code to execute that task. Here's an example. Have a state that is named, the dogs are out. That declares a state. Then in its curly brackets, I have an event. I have the state entry event. And that declares an event handler to operate when you enter the state. And then in that curly brackets, I have an imperative. I say, I say, who let the dogs out? That's executable code. And then immediately after that, I say, state who, who, who. Now it's telling the program to leave this state and go to the next state. Okay, variables. When you need variables to hold information, and variables have a label and a type. Variables may be declared in any block or at the top level. When you declare a variable, you give it that type that allocates a certain amount of memory for the variable. The types are integers, floating point numbers, strings of characters, strings of text, keys, which I will explain. Vectors, which is basically a set of three floating point numbers. Rotation, which is basically a set of four floating point numbers. And the list. So here's my example. I declare an integer, something recount that's going to be its name. And I initialize it to be 256. The type is strict. You cannot change it once you declare it. And the script editor will yell at you if you try to do that. But you can do a type conversion. You change the value and use another variable. For example, here I'm showing I have an integer count the daisies, which I initialize. Then I have a string. How many daisies, which is going to be text? Well, to turn the integer value, count the daisies into a string. I use the type and parentheses just in front of the variable name. And that will fetch the value, turn it into a string, and then put that value into my other variable. Okay, I mentioned variables go inside blocks. You do have global and local variables. If I put a variable at the top of the program, it is visible to all blocks. But if I put a variable inside a block, say an event block or a function block, that variable is only usable inside that block or any blocks contained within that block. Cannot be used in a block that's at a higher level. This is called scope, by the way. You'll hear that in a lot of programming languages. Comes from the Greek and it simply means look at. Another thing to be aware of is that variable can be used only after being declared. So looking at my program from top to bottom. If I try to use a variable name that I haven't declared yet, it will fail. So you have to be aware of that. Other languages have what we call a look ahead function. For example, JavaScript looks ahead. So I can declare variables anywhere and it will catch it. But that is not the case in LSL. So declare most variables at the top of the block and you'll be fine. Also, I recommend using global variables sparingly. You want to use your global variables when you really need to pass values from one state to another. List, well, some people wouldn't call list a variable I did here. But basically it's a collection of values. So it's an ordered set of any values. And note, this is the values, not the variables. You use variables to put a value into a list. But once it's in the list, it's no longer a variable. It's an actual solid concrete value. And that's the only data collection that's available in LSL. And boy, does that ever cause misery. Everybody wants better data collections in LSL. We don't have them. Your list looks like this. It's a square bracket and then each item separated by a comma. The items can be integers and floats and vectors, strings and rotations, but not another list. You cannot nest a list into list. Now I'm not going to discuss the syntax in detail because that would take hours and hours and hours. But I'm going to show a practical program and point out some features. For deep learning, you want to go to the LSL wiki and I show the link here. You can also find it by simply searching for it to search for LSL portal then it will come right up. And you'll find, you know, a very large amount, overwhelming amount of information there on programming at LSL. So let's look at the practical program. First, let's look at the skeleton of the program. You can see at the top, I start by doing a global variable. So I give it a type, I give it a name, and I optionally give it some initial value. Then I declare my function blocks. So again, I have an optional type. I give the function a name and a list of parameters. Then it's code in the curly brackets. It's code block. Then I have a state block. You have to state a name, curly brackets, and then I have my code inside the curly brackets for the state. And typically there will be an event handler and it will have curly brackets showing its code. Okay. So here's a little example. I'll start at the top of the program with an integer. It says, are the dogs out equal to true? Now, I didn't mention Booleans. In LSL, you can have Boolean values of true and false, but they're actually just integers. True is anything that is not zero. False is zero. So I created my little Boolean here, are the dogs out, which can be either true or false. But it's actually an integer. It can be some integer at all. Then next I had a floating point value that is a velocity. And I set that to initial value. Then after that, I declare a function. Well, this function is going to return a floating value. I give it a name, how far. And it takes one parameter, a floating point variable that is the time away. Then inside that function, I have my flute is a distance. And I say, if the dogs are out, if that's true, then I calculate the distance as the time of way times the velocity. And then I return that distance. My distance is a floating value, so that's the correct value I should return from this function. Going on, I repeated what I just showed you here, a little smaller type. I come down, I have my first state, the default state. I have an event handler for the state entry. And then I have a registry event for listening. When the listen event occurs, that's the event handler that's shown just beneath that. If the message is bark bark, then I change the, are the dogs out value to be true? Because I heard bark bark, they were obviously out. Then I jump to the state, bring them home. Okay. So I have left the default state. I come to this state, bring them home. It's another state. I have a state entry for it. And I tell the owner how far he's going to have to go to find the dogs. And then I set up a sensor that looks for the dogs. Okay. And if the sensor detects a dog, it will tell the owner how many detected. And then it jumps right back to the default state. Now, I mentioned that it was wise to divide operation up into several short scripts whenever feasible. I noticed something's going on in the chat here. Let me just check here. Okay. Yeah. I see they're giving you some resources for scripting. So I'm going to show you now a multiple script case. And let me just click here. There we go. This is my NPC controller and communicator. It's several scripts that work in concert. And you may recall when I was showing you the contents of objects that there could be more than one script in there, more than one content. So I have an object that is my NPC controller. If you notice to the right of the presentation screen, that's the little box that's sitting there. And that's used to control NPCs. And I had two NPCs currently resed here in the presentation area. I have the Avenger off to the right, and then I have Dancing Pamela sort of behind me on the left. Okay. And here you can see what's inside them. The Avenger, you can see that I have three different scripts and a number of animations that she can use. Over in Charlie, I have a similar setup for him. And then in the controller, you can see I have two scripts, a communication script and a dialogue script. Now, this is going to be complicated, but I'm going to skim through it, just get the feel of it. And, you know, I am available if anyone wants to have a one-to-one hands-on tutorial on scripting. So let's look at the control station script first. This script talks to the NPCs and to a user interface. Okay. So like before, you can see I declare some global variables at the top. Then I have a function called setup control. Notice this function does not return a value, so I did not have to give it a type. But it sets up an event handler for me. I'm going to have my default state. It always appears first, so I don't have to declare it like other states. And upon entry, it will do a setup, do the setup com function. On res, it resets the script, so it will reenter the default state whenever it is res. By the way, states are kind of like properties of a prim or a mesh. They will persist over server restarts if you take it in inventory and then re-res it and that kind of thing. So if you really want to make sure that you always start from the same spot, you need to use the on res event to reset the script back to default or do other kinds of initializations if you think you need it. Okay. When the listen event occurs, well, here's the handler for it. And you can see that it checks the message, make sure it's the kind of message it wants to handle. That is the right kind of message. It executes a function called message linked. And I mentioned that I was doing this for multiple scripts. Well, here's the key to that. Message linked sends that message onto other scripts inside the same object. Doesn't send it out over into the outside environment keeps it nicely encapsulated inside the object. And if I receive a message that looks like it was intended for a NPC, then I broadcast that one out to the NPCs, which of course are other objects. They're not contained in the control station. And here's the NPC communication script. This is the one that we'd be waiting for those messages coming from the control station. And I will not, you know, take a lot of time explaining this one. Just let you see the way it is set up. As you can see, I just recently modified this one back in February. That's, by the way, is a good practice. At the top, I always have a series of comments where I give the name of the script, what it's supposed to do, who did it, who wrote it, and the version. Here's a function that says handle instructions. So it gets an instruction and it just basically goes through and sees what the instruction is and then does something. Okay. And again, I won't go through all of this. I'm happy to share it with anyone who wants to go through it and have a look at it. I get linked messages from the different behavior scripts. And it will look at those messages and see how those have to be handled. And some of them might be passed along to the control station. So the controls, if an NPC cannot communicate with its control station, it will attempt to find a new one. If it can't find a new one, then it derezzes. I do that so that I don't have any rogue NPCs running around. You know, they happen to stumble out of the parcel or region where I wanted them to be. They will de-rezz and not cause any trouble. All right. Now, when writing and debugging, the one thing you have to get used to is that, well, first of all, you're going to go plan the algorithm as best you can and make the code. But it's going to fail the first time, the second time, maybe more times after that. Got to fix it and try again. And a hardware example of that was the test of the heavy launch in the Starship vehicle on Thursday, which had a failure. And they're going to fix it and try again in a couple of months. So pause here for a moment and see if there's any questions. Anybody want to jump in and chat? Okay. There are some differences with OpenSIM. First off, OpenSIM has a few more functions than Second Life does. Some fairly useful ones at that. Also, there is what we call experience permissions. For example, if I set up a kind of touch-terror port, your avatar has to have given permission to be teleported. And that is saved in each parcel. They don't do that in OpenSIM. They use a different mechanism altogether, which they call the damage control setup. So, you know, there are some differences. However, most of what you do is transferable between the two different kinds of systems, OpenSIM and Second Life. There might be more diverges in the future. You know, you may have heard that the Second Life viewer and the OpenSIM viewer have completely branched. You can't use one with the other. So, there's two versions of Firestorm. There's a Firestorm for Second Life and a Firestorm OpenSIM. I suppose there were some reasons they needed to do that, but, you know, it is kind of unfortunate. Any other questions here? Okay, I'm going to move on to some additional material then, unless someone wants to leap in. Oh, wait, did Roy ask something? Yeah, that's true. Note cards cannot be used as data storage in Second Life. The only way to have a real data storage in Second Life is to use an external server. For example, I have a server on Google Virtual Machines that's actually called sciagea.org that I can use as an external database. Inside of Second Life in a script, I have the ability to send an HTTP request to it and get an HTTP response back. So, that provides a method of having a much larger data storage than is possible in Second Life. Okay, let's see what else we have here. A little bit about the LSL syntax. And it's maybe redundant, but... Oh, download LSL? Well, it's built into Second Life, so you don't really download it. And also, you don't really have any decent external editors for it. You have to edit it in Second Life, in a script, in an object to really be able to edit and test and do a programming cycle. That is very unfortunate. However, there is that wiki we mentioned that provides all the information on the syntax and a huge number of examples. By the way, that's something else. You know, I've found that generally in Second Life, you're thinking about a script to do something. There's a very good chance that someone has already done it. And you simply have to search for it, you'll find it, and you can probably use it with a little bit of modification. See here. Okay, I mentioned that variables have specific types. An integer is a whole number between the limits shown here on the screen. And for those of you who are into this, it is a signed 32-bit value. And besides using the integer notation, you can also use hexadecimal notation for integer values. Floats are numbers with a fractional part. It supports exponential notation and it could be either positive or negative. So it can go down to values smaller than or physically real and to values that are larger than you could ever expect to require. Of course, the real point or floating point is to get extra precision in the value. Okay, strings are text. And the text is the sequence of 16-bit characters using Unicode. So any 16-bit Unicode character, including the little graphic ones, can be used. The total length is limited only by the available memory. And in LSL, they are always inside double quotes. Let's say someone's talking about rezzing prims and so forth. Yeah, because you see when you do some things, the script will go to sleep for a few fractions of a second. And that basically prevents it from doing too much at once. Keys, now, I mentioned keys before. Every object in Second Life in your inventory or rezzed out in the open or whatever, has a unique key. And it's a sequence of hexadecimal characters grouped in a manner that follows the standard, the UUID-4 standard. And I show an example of what a key looks like there. They're all unique. If you duplicate something, the duplicate will have its unique key. And they automatically assign when you create an object. There is a huge number of possible keys. So again, there's no great fear of them ever running out of keys. Vectors, well, as I said, a vector is an ordered set of three values in the float format. A vector can be a position. It can be a orlian rotation. It can be a color, like, you know, red, green, and blue values. It can be a velocity. You know, it's simply three floating point numbers, lots of different uses. And in the code, we put the values between the little angular brackets. So if I was using variable names, it would be angle bracket x comma y comma z. And a tall story. No, not a tall story. It's a real story. Or you can put in the actual values directly into a vector and so forth. And, you know, the names don't have to be x, y, z. They can be Bob, Ted, Alice, and so on. Typically, you would have a variable name and you set it equal to a vector. Once I've done that, there's a special notation for getting the values out. I can go with the dot syntax you see here. So I created Wombat as a sequence of three values. And I go Wombat dot x, I get the x value, Wombat dot y, I get the y value, and so on. Okay, let's see here. A rotation is a ordered set of four values in float format. And it is called a quantarian. Now, I tell you, you know, in all my years of going to presentations on physics, no one has ever successfully drawn the quantarian rotations on a blackboard. And I do mean blackboard because that ages me. That came from the blackboard error, not the whiteboard error. But, you know, whenever you try, you just end up getting confused, you know. However, the Orlean rotations are a bit easier to understand, although not quite as useful in terms of combining rotations of the quantarian. So LSL provides a way to translate between the quantarian and the Orlean, and back again. So generally, if you want to multiply rotations, combine them, you do it in the quantarian format. If you want to know what it is and be able to visualize it, you turn it into the Orlean format, okay? The list I mentioned was an ordered set of any values, and I mentioned my irritation at the fact that you're so limited, but it is as it is. Variables are basically labeled memory, and that's the reason the typing is so strict, because once it sets out the amount of memory required for a given variable, you're stuck with it. So that's why we have to declare them. And here I just show some more examples. You know, integer, pie slices, float, pie diameter, a list of pies. You'll notice that I'm able to combine different types of variables into the list. And the static typing, you know, that you can change the value, but the type has to stay the same. That means that list handling is not very efficient in LSL. It does slow down execution, consumes more memory than, you know, the individual variables would. And it will cause runtime crashes if you overload, make them too long. Scope I mentioned before. Scope is where a variable can be used. If I make the variable declare a condition outside of any state or function, it's global, can be used anywhere. But if I put it inside a state, it can only be used by the code inside that state, inside a function, only inside the function, so forth. Mention events, registering them. And, you know, you need to know that the program will wait, wait, wait until the event occurs, and then it will execute whatever was in the handler. Okay, so that's what I prepared to talk about. So again, let's see if there's some questions. We have about eight minutes to go before the end of the hour. So anybody want to talk about some items or ask about the NPCs I've set up or whatever? Yes, the slides will be available. And in fact, I did record this session, so I'm going to try to make a little video of it that I will put onto the second life, sorry, the Science Circle, Science Circle YouTube channel. So I'll be there. Oh, yes, the NPCs are animash and the scripts are triggering animation. I know you can't see what I see, but I'm going to click on the control station and I'm going to control the Avenger and change your animation. So if you watch, the animation should now change. Okay, she's now doing a faster walk and now she's doing her sneaky walk and her slow walk and so forth. I also have pathfinding enabled on these NPCs, but as I mentioned earlier, pathfinding doesn't seem to be functioning in this particular parcel. And yeah, I'll share my NPC codes and control station and so forth with anybody. Oh, pathfinding. Well, pathfinding is a second life only innovation. Basically, it lets an object choose a designation and then choose the best path around all the objects that might be in its way and then it moves smoothly to that destination. Unfortunately, it's a somewhat buggy. It doesn't seem to quite work right all the time. For example, I can tell a pathfinding object to stay within a parcel, but occasionally it will try to escape the parcel and get into trouble. And I hope that one day there's some improvements to pathfinding, but I have a feeling that Linden Labs resources are being put in other directions at the moment. So, you know, it is as it is. There are other ways to make movement. There is a terrific set of functions called keyframing. And at keyframing, you set up a list of where you would like an object to be or rotate or whatever and the timing part and it will move from one to the next quite smoothly. Now, it differs from pathfinding in that pathfinding will keep the object from making any collisions and has some other nice little attributes. In keyframing, no such luck if you set up a path in keyframing that collides with something else that's going to collide. Pathfinding has lots of nifty things that can be set up to avoid. So, you can set up a character. If you get too close to it, the character moves away from you and you can set them to follow. So, it will pursue you, you know, a short distance away. And then you have patrols where it will go from point to point. Someone puts an object in the way, it will figure out how to get around it. And then, of course, just wandering a random walk. So, I really wish that pathfinding was more universal and worked somewhat better and we can but hope for the future. Okay, more questions. Okay, we have about a minute to go. And I'm kind of talked out. So, oh, the recordings. I usually get them up on the weekend. You know, we'll see how quickly I can go this weekend because this is Earth Day weekend here in my area. And there are some activities I'm going out to, you know, myself, so I'll be out there. Yes, and, you know, I think these NPCs are going to be very useful as information givers and, you know, other things. The animations are all done with what is called the bio vision hierarchy. It's a long in use text-based animation system so you can get your NPCs to do anything, you know, walk to the well, put a bucket down the well, pull the water up, walk away, that kind of thing. You know, it just takes a bit of work to handle that. Okay, so that's our hour. And I guess we will close up shop now. And I do hope you did enjoy the presentation.