Gotta ride it like you find it

(That’s actually about the Rock Island Line, and this is about a different line, but it seems apposite.)

Train crossing a bridge

That picture is from Karima Hoisan’s new Kitely sim “The Hudson Line”, which she has just announced over in her (much more frequently updated) weblog.

It’s a wild dreamlike re-interpretation of the RL commuter rail line of the same name; in one of those great SL synergies it started in casual conversations about the differences between various people’s living and working environments, and grew into this lovely impressionistic world. I helped with my usual small scripting contributions, and this time even some of the building!

It was wonderful fun to watch it emerging out of nothing (what an age we live in), and while you don’t get to have that fun, you can visit the announcement, and the sim itself (Kitely account or something required I guess?), and ride the train, and watch the movies, and find out what’s down there in the tunnel at the end (and it’s not Grand Central Station, muhahaha).

(Scripting note: llSetKeyframedMotion works very nicely in Kitely these days, and therefore I assume probably also in OpenSim; I do advise not entirely trusting that the moved object will arrive exactly where you expect it to, especially the first time after a sim reset, and setting at least a backup timer as well as the usual at_target() event. But it’s running the trains and cars and trucks and tugboats in the Hudson Line sim just fine!)

Advertisements

Moving Trains

That title is a pun, since these particular trains that move scriptily down the track are in a video that is itself quite moving in the emotional sort of way.

As the official announcement from Karima Hoisan says, Karima and Natascha Randt’s “Window on a Train” is now up for general viewing an’ admiration.

Here it is on the “You Tube”:

and note that per Director Randt “Watching our film without being in full-screen and HD (720p) is Prohibited!” :)

I don’t want to say anything specific about the movie itself, because I think it has the most impact when viewed with the least preconceptions. Do prepare to be touched…

Machinima in general is so cool. I am not a visual person myself, so generally I can only sort of gawk (or make pictures with math), but I am pleased to say that in this case I was able to contribute, in the form of some scripts to provide smooth and mostly consistent motion in three objects (an engine and two cars) that were far too primmy to make physical, and also far too primmy to link together.

If I can gather up the energy and time :) I will post about it (the one-word giveaway is LLSetKeyframedMotion, which having been added to LSL later than 2007, is for me a Strange New Thing in the world, which proved Just Right for this particular use-case).

But anyway! Watch the movie! :) It is good!

Automatic walking (and the invisible stranger)

So very poetic friend Karima flatteringly chose my avatar following script as some context for a new poem of hers lauding scripters (she even used some of the code in the poem, and although this sounds like it couldn’t possibly work it certainly does, and you should go read it).

This reminded me of a related script that I have been too lazy to work on forever and ever, and I now have a rough draft. It may stay a rough draft for some time :) since LSL makes it hard to do in a really polished way, but in any meantime here it is. When you’re using it, once you start walking you keep walking, more or less forward unless you use the side-arrow keys to turn, until you tap backward or etc to stop. (Works best on or over flat ground; see the page for details!)

If I ever like it enough I’ll put it into the Library.

Here is a picture of me walking with the automatic walker. Note that this is a hard sort of picture to get in general, because you have to walk and cam around to looking at yourself from the front at the same time, but the walker makes it relatively simple!

(Well, okay, and without any special scripts you can also cheat by just playing a walking anim and not actually walking. :) But this would let you make a video of yourself walking! A bit, anyway…)

In other news, I noticed that for a long time (like, weeks) there had been a green dot over in an adjacent parcel on the Rise, and whenever I went over to look it was the same AV name in the title floating there, but with no visible person, and they never answered chat or IMs.

Feeling whimsical the other day, I’d nudged the invisible person (who turned out to be nudgeable) over into my land, and built a little protective envelope of colorful devices around them. I figured I’d keep them around as a decoration until my conscience got the better of me, and then send them home (since they were on my land, and I could now do Teleport Agent Home).

But then not long after that I logged into the Rise, and there they were again, standing invisible and unresponsive at the same place in the empty field next door. (And yeah I’ve checked high and low and Highlight Transparent; I suppose they are probably wearing a full-transparent body alpha, or something subtler.)

Most puzzling! What process moved them back there again, to stand again invisible and unresponsive?

But that’s why we love the mainland. :)

Update: the llSetForce() version of this seems to work just awesomely! I am now playing with a version where you stop by pressing Back, and each time you press Forward it speeds you up a bit. Enormous fun so far!

Dale meets Dale

An age and an age ago, I made a picture with both Dales in it (or, strictly speaking, both Dale-bottoms), for some pictures (of bottoms) event that has since slipped my mind.

(Oh, right, looking at the text, it was one of them there weblog memes, notable for involving bottoms.)

It was comparatively easy, since I had the two of me almost entirely not touching, so there was just one tiny piece of foot to fiddle with carefully in Photoshop, and otherwise the only hard part was holding the camera absolutely still while I took one picture, stood up, changed shape and skin and hair, sat down again on the other sit-place, and took another picture.

For some reason the other day I wanted to take some more-overlapping Dale and Dale pictures, and eventually I did:

Narcissus 1

Nice but her face doesn’t show at all. (And there’s that little oops just under his near hand, oops.)

Narcissus 2

Quite nice, really. :)

This time I was working in gimp rather than Photoshop (for financial and sundry reasons), and it turns out that you can do this sort of thing pretty easily. I’m not going to do a detailed tutorial unless someone wants that, but basically you take two pictures from exactly the same place, layer one over the other, add a layer mask to the top one, and then draw on that layer mask in black or white, depending on which layer you want to show at any given point. It’s a nice forgiving process, doesn’t require an exceptionally steady hand, and so on.

This time I didn’t want to have to hold the camera absolutely still to take the pictures, so I wrote a script. Here it is!

integer SAVE_FACE = 0;
integer RESTORE_FACE = 1;
vector saved_camera_pos = ZERO_VECTOR;
rotation saved_camera_rot = ZERO_ROTATION;
integer have_saved = FALSE;

default {
    state_entry() {
        have_saved = FALSE;
    }
    attach(key id) {
        if (id!=NULL_KEY)
            llRequestPermissions(id,PERMISSION_CONTROL_CAMERA | 
                                    PERMISSION_TRACK_CAMERA);
    }
    run_time_permissions(integer p) {
        llOwnerSay("Touch that one face to save camera position,"+
                   " that other face to put it back.");
    }
    touch_start(integer total_number) {
        if (llDetectedTouchFace(0)==SAVE_FACE) {
            have_saved = TRUE;
            saved_camera_pos = llGetCameraPos();
            saved_camera_rot = llGetCameraRot();
            llOwnerSay("Camera position saved.");
        } else if (llDetectedTouchFace(0)==RESTORE_FACE) {
            if (have_saved) {
                llSetCameraParams([
                    CAMERA_ACTIVE,TRUE,
                    CAMERA_POSITION,saved_camera_pos,
                    CAMERA_POSITION_LOCKED,TRUE,
                    CAMERA_FOCUS,saved_camera_pos+*saved_camera_rot,
                    CAMERA_FOCUS_LOCKED,TRUE
                    ]);            
            } else {
                llOwnerSay("No position is saved yet.");
            }
        }
    }
}

So basically you put that into some prim, you color face 0 some color, color face 1 some other color, attach it to your HUD somewhere, fiddle it around so that you can see both of those faces, put your camera where you want it, touch face 0, take a picture, do whatever you want to, touch face 1, possibly press ESC to make sure you’re on the default camera, and now your camera is back where it was before for taking your second picture.

I could have added a third face case with like llReleaseCamera() or whatever it is on it, but it’s easiest to just take the HUD off when you’re done to free up your default camera. :)

Isn’t nature wonderful! I may get up the energy to put up a real page about it like my famous ExplodingObjects and AvatarFollower pages, but for now I am impressed with myself just for having slapped it up here.

Now I will go back to admiring my pictures; aren’t I a cute couple? :)

Combat System Scripting V: our first combat meter

I have suddenly remembered this here series of combat scripting posts that I was doing the other Geological Epoch!

So I will do another one. A pretty simple, but important one; our first Combat Meter.

Now as I didn’t explain in the first post but probably should have, when it comes to people (AVs) taking “damage” in Second Life, there are two distinctly different ways that can work. There is a notion of “health” and “damage” that are built directly into the world (but as far as I can tell very seldom used, because they’re inflexible and kind of silly), and then there’s “health” and “damage” as enforced by scripts in things that you wear.

(In a way this is like the difference between vendors that let you buy using “buy”, and those that let you buy using “pay”, as explained in my extremely thorough post on the subject. “Buy” is built into the world whereas “Pay” enables scripting, just like Linden Damage is build into the world whereas Scripted Damage is scripted. Not that there is anything all that significant about the analogy. :) )

Many, even most, Residents probably don’t even know that every AV is always at some state of Health, and that if that Health goes to zero, we “die”, in the sense that we are suddenly teleported Home, just as though we’d hit control-shift-h or whatever.

The reason that people don’t know this is that the vast majority of SL land has the “damage enabled” flag turned off, so you’re always at 100% health, and the viewer doesn’t normally even bother displaying the health meter in that case.

But if you stop by my Park in the center of Hughes Rise, or various other places, and then look carefully, you’ll be able to find a little heart on the screen, probably with a “100%” next to it. It you fly or teleport a few dozen meters into the sky and let yourself fall, or fly at high speed into a tree, or other amusing things, the number may go down from 100% (and then rather quickly go back up again). If you lose enough health fast enough, you may even hear your AV make a little supposedly gender-appropriate pained-grunt sort of noise.

And if you get the little number next to the heart down to 0%, you will suddenly be teleported home.

Now that sounds like a useful basis for a combat system, but it really isn’t, because it’s relatively easy to script up an object that instantly kills anyone it’s pointed at, and being teleported home isn’t a very flexible way to implement defeat (for instance it’s tough to script a scoreboard of “kills” or anything, just for one example), and healing is always exactly the same over time (no way to make “medikits” or “health potions” or anything), and it doesn’t apply to non-AVs (targets, monsters, robots, etc) and so on and so on.

So basically no one uses the built-in health and damage system for actual combat. What they use instead is Scripted Damage, which works by having the combatants wear some sort of attachment or other, that somehow finds out when they are “damaged” by “weapons”, or “healed” by whatever, and does whatever the system designer thought appropriate when they “die”.

The things that you wear that keep track of your damage generally have some sort of indicator of how healthy you are right now, in Scripted Damage terms, and are therefore generically called Damage Meters.

In today’s posting here, we will make a Damage Meter. Or, actually, we will discover that we’ve Already Got One!

(“Oh, yes, it’s very nice.“)

Already got one with a few modifications, that is; because it turns out that our self-healing target, from the self-healing target post, makes a very nice basis for an AV damage meter.

This is because of this Very Important Fact: when something hits your AV, all of your attachments see the collision event.

(I understand that this may not be true in OpenSim, or in some OpenSims, in which case damage meters there will have to work in some entirely other way of which I amn’t aware; I may explore that at some point too.)

So for instance if you were to take our self-healing target into inventory, and attach it to the center of your HUD say, and stand in front of the auto-popgun, you would see the health value above it go gradually down; and then if you step aside out of the stream of pellets, it will go slowly up again.

The only problem with it is that when it gets down to zero nothing useful happens. The script does an “llDie()” at that point, which you might think would cause the attachment to Cease To Be, or to detach or something, but in fact (another Interesting Fact) llDie() does nothing at all in attachments, so nothing actually happens.

This is easy to change, though. For now let’s just have the meter say something amusing when you “die”, to demonstrate that it is working. We will just change the process_collision routine so that it does something slightly different when health is zero, as in:

process_collision(integer index) {
    if (llVecMag(llDetectedVel(index))>15) {
        health = health - 1;
        show_health(health,MAX_HEALTH);
        if (health<=0) {
            llSay(0,"Arg! "+llKey2Name(llGetOwner())+
                    " has been defeated!");
        }
        llSetTimerEvent(HEAL_SECONDS);
    }
}

(We also took out the “pow” and “clunk”, because we’re confident enough that things are generally working that we don’t need them anymore.)

So modify the target script as above, put it into a prim, wear that prim somewhere appropriate on your HUD, and stand in front of the auto-popgun or have a friend shoot at you, and you will find yourself being “damaged” and eventually being “defeated”.

Combat! Shazam!

Some other stuff one might want to do:

  • Support “medikits” or “health potions” or whatever, that heal you when, say, you walk (run) across them (I have code for that!),
  • Cause you to, say, fall down and stop running around when you “die” (I have code for that too!),
  • Remove your ability to use your own weapons while you are “dead” (I don’t have code for that yet, but I might talk about various approaches).

(The other thing I’m currently playing with, which I haven’t quite scripted up yet, is to have the targets, the “monsters”, be “lootable”, so that once you’ve defeated one you can touch it or something and get I dunno some kind of reward. If anyone knows of a sim or a system of scripts that does this sort of WoW-like lootable-monsters thing, let me know; I’d like to take a look!)

So that’s all for this time. Pew pew! :)

Wind and Rotations

So I must apologize for (A) having gotten distracted from the combat-system scripting by thinking about the details of sailboat physics, and (B) posting some stuff here that is really just for me, but I thought it might someday be useful to someone else to, and besides this was a handy place to jot it down.

There are an annoying number of ways to write down rotations and directions and things in LSL, and different parts of the word want different ones. (And this is just, for the moment, talking about rotations in two dimensions!)

For instance, llWind() returns a vector representing the wind direction. Wind is (I believe!) always flat (parallel to the ground) in SL, so we can ignore Z. But what exactly are X and Y?

Well, Rule 1 (which I always forget and have to try out again) is that X is East and Y is North. So if for instance I am standing at 117, 119, 117 in Hughes Rise, on the walkway in the Park, and I walk one meter due North, I’ll be at 117, 120, 117. See how Y got bigger? And if I walk one meter due West from there, toward the stairs, I’ll be at 116, 120, 117; X got smaller.

So if llWind returns < 5, 0, 0 > for instance, that means the wind is blowing at five meters per second toward the East (and therefore is called a West wind, ’cause winds are called by where they are coming from, just to be confusing).

Now say you have a sail, and you want to know the angle between the sail and the wind. Well, the sail’s trim is represented as a rotation, which can be represented in LSL either as an actual rotation object (which has four components, corresponding to the four components of the corresponding quaternion, where a quaternion is of course an element of a four-dimensional associative normed division algebra over the real numbers), which is mathematically wonderful but not too useful for actually thinking about, or as a three-element vector, each element representing a rotation about the corresponding axis.

So for instance if your sail is flat in the X dimension, it will be facing head-on to winds coming from the east or west. If you then rotate it ninety degrees (which is a rotation of pi/2, or PI_BY_TWO, in radians) about the Z axis (the up-and-down one), it will be facing head-on to winds coming from the north or south.

To represent a rotation of pi/2 radians as a vector in LSL, we write < 0, 0, PI_BY_TWO >. Now the fun part! If I know the X and Y parts of the wind speed from llWind, and I have the rotation of my sail as a rotation about the Z axis, how do I figure out the angle at which the wind is hitting my sail?

Now I pause to think and experiment a little, so as to have an answer to write down in the next paragraphs. :)

Well, first of all notice that by using a symmetrical sail and a rotation of pi/2, I’ve sort of cheated; in particular, I didn’t have to figure out whether rotations are clockwise or counterclockwise (anticlockwise). This is probably obvious to smart people of various kinds, but I can never remember it (some Right Hand Rule or something is probably involved) so I have done the experiment and will write down Rule 2: Rotations are counterclockwise, or, to be precise and therefore meaningful, if you’re looking down at something that is not rotated, and you rotate it positively about the Z axis, it moves around anticlockwise.

This surprised me. :)

It makes sense, though, if we think back to like Trig 101 or whatever that was; X is East (i.e. to the right on the map), and Y is North (upward on the map), which is just how the graphs were all done on the board, and if you start out with a line running from the origin to < 1,0 >, say, and “rotate it” by some small angle theta, it goes up a bit into th’ ol’ First Quadrant there, which means it’s moved anticlockwise.

(Whoever designed clocks must have skipped that class or something.)

So we can represent wind direction as a rotation-about-Z by drawing it on the board there using the X and Y that we got from llWind(), and straightforwardly figure out what angle we would have to rotate that origin-to-1,0 line through to get it there, and it turns out to be (unless I’m wrong) llAtan2(Y,X); that is, the angle whose tangent is y over x.

So we have two rotation amounts, the amount that our sail’s been rotated and the amount that the wind’s been “rotated”, and we can just subtract them from each other to get the angle between them. If they’ve been rotated the same, the difference is zero, and our sail is getting (basically) all of the benefit of the wind. If they are ninety degrees apart, the sail is edge-on to the wind, and getting no benefit at all. Because we suspect there is probably something trigonometric going on here, we will note that llCos(0) is 1.0, and llCos(PI/2) is 0.0, and decide that we can do, say:


vector wind = llWind(ZERO_VECTOR); // The wind right here
float windrotz = llAtan2(wind.y,wind.x);
vector sailrot = llRot2Euler(llGetLocalRot());
float sailrotz = sailrot.z;
float rot_difference = windrotz - sailrotz;
float wind_effect_on_sail = llCos(rot_difference);

and so wind_effect_on_sail will be 1 if the wind’s blowing right onto the sale, will be 0 if the wind is blowing edge-on onto the sail (luffing!), and will be inbetween in between. (It will also be negative, I think, if the wind is blowing on the wrong side of the sail, but I haven’t thought about that yet.)

And some similar calculation can be used to get the angle between the sail and the hull (or, really, the keel), to figure out how much of the wind’s effect on the sail then contributes to the forward motion of the boat. But that’s a little later. :)

Combat System Scripting, interlude: OpenSim

So on our last post in this thread someone asked if this stuff would work in OpenSim.

I did a little playing around, and the answer seems to be “it depends”.

Unlike Second Life, which is both (arguably) a bunch of software, and a particular virtual world that uses that software, OpenSim is just a bunch of software (and an Open Source one at that, which means that there are all various forks and versions and levels and patches around out there). So what you get when you log into one virtual world that’s running (some level of) OpenSim may be different from what you get in another.

What I found when I tried out the Scripts So Far in the first OpenSim-based grid that came to hand (running perhaps “OpenSim 0.7.2 ReactionGrid”, although it wasn’t ReactionGrid) was:

There’s one simple thing in the self-healing target script that this OpenSim didn’t like; this line gets a syntax error:

integer health = MAX_HEALTH;

because apparently this OpenSim is even more silly than Second Life is, about global initializers not having any hard stuff like math or variables in them. So I changed that to just:

integer health;

and added a

    health = MAX_HEALTH;

down in the state_entry() handler, and that got rid of the compilation error.

The autopopgun script worked fine except that (as one of the commentors anticipated) I had to create my own bullet for it because there was no popgun in the library to cheat with. :) I won’t post the bullet script and other bullet details here right now ’cause it would be a distraction, but it worked.

Then I pointed the autoshooter at the target, and the target said “Clunk”.

Repeatedly.

As you may recall, this means that it thinks it’s being hit by something that’s not moving fast enough to cause damage.

A little poking around revealed that in this particular OpenSim, llDetectedVel() was always returning 0.000, making it not very useful for our purposes!

As a temporary test, I changed the

    if (llVecMag(llDetectedVel(index))>15) {

in the target script to the obvious

    if (llVecMag(llDetectedVel(index))>=0) {

so that instead of “is the thing that hit us moving at least fifteen meters per second?”, it was instead asking “is the thing that hit us moving at all, or for that matter not moving at all?”. To which the answer must always be Yes! :)

With that change the target worked fine; its health count went down while the shooter was shooting at it, went back up again when I moved the shooter away, and then went down and turned yellow and then red and then the target Ceased to Be when I left the shooter pointed at it long enough.

There may be OpenSim-based grids where llDetectedVel() works (this list of LSL functions that work in OpenSim seems to claim that it is implemented), but it didn’t seem to in mine. But other than that, and the one tiny syntax change mentioned above, everything we’ve done so far seems to work in OpenSim!

In case anyone else was wondering. :)