Making a Valentines Day Tour

Join us in Outworldz at www.outworldz.com:9000 or follow us:

Build your own Valentines Day tour bird

Here is a complete set of scripts and sculpts to make a Valentines Day Tour bird for two lovers! It can be made with as few as 8 prims. It is easy to teach routes to, and you can expand it to chat to your visitors, and even play sounds at different waypoints. Its ideal for use with my chat to speech software.

Valentines Day tour

To start, you will need to upload each of these sculpts and the head texture:

Head Sculpt Neck Sculpt Wing Sculpt Bottom Sculpt
Head sculpt Neck Sculpt Wing sculpt Bottom sculpt
You also need this head texture:
JHead texture

Go ahead and upload each item to Second Life. Be sure you check the box for 'Use Lossless Compression".

upload dialog

Create a new box, and change the type to Sculpted.

sculted  type

Once you have select type Sculpted, left click and drag the texture and drop it onto the Sculpt Texture area.

drag and drop

Repeat for all 4 sculpts. You will end up with 4 prims of different shapes. Once you have them all loaded onto a prim, click the "Inside-out button" for those that are well, inside out.. and then resize them to make the shape seen in the image below. Then drag the texture for the head onto the head.

The image to the left is the original bottom. After a resize it appears as shown on the right side.

sculpts

You will need one more prim. Make a cube and stretch it across to make a seat.

Now is a good time to add two spheres for pose balls. I know people don't like pose balls, and prefer they are hidden. But the user needs a clue to sit, so add two spheres.

popose balls

Now shift-copy the wing, and select the "Mirror" button. Rotate the wing into place.

Shift-copy the wing

Now you can color the bird white by selecting 'blank' texture, and color the two pose balls to blue and pink:

colored model

Now sweep over all the prims until they are all selected. Click the seat on and off and on again so that it is the last prim selected, and then press Ctrl-L to link them all together

If you do this properly, the seat have a yellow outline in edit mode:

Outlined seat in yellow

Now we are ready to add the scripts. There is a script for the pair of pose balls, and a script for the seat. You can add more seats and pose balls if you like. There is also a set of scripts to rez a bird, and to train it for a route.

All the scripts are available at the Free LSL Script Library # 1579.. I recommend that you download them there, as HTML has issues with copy and paste. Click the link and then click download on each script.

PoseBall Script:

Put this inside each pose ball. It will control the avatars camera to give them a nice view, play the sit animation, and it tells the main script someone is sitting.

Tip: edit the pose balls individually, and set them to the mode so that when clicked, you sit on them. Your guests can then just left-click the pose ball and they will sit. No groping and fumbling until after the ride begins!

If you don't like the default sit pose, change the line #41 where it says

llStartAnimation("sit")

and add your own animation to the pose ball. You will probably have to adjust the TARGET and ROT vectors to sit properly. I usually just edit the pose ball and turn it in the correct direction, and raise or lower it slightly. Once you have the pose ball script running, sit on each one and rotate and position them so you sit correctly.

Scripting

All the scripts are available in the script library.

The latest version is V1.1 as of 09-06-2012

Pose ball script (updated - one line was wrapped incorrectly so the llSitTarget was commented out)

// Poseball script for both pose balls
// Author: Ferd Frederix


vector TARGET = <-.5,0,-0.1>;		// 1/2 meter up, back a tenth.
vector ROT = <0, -90, 0>;	//rotated 90 degrees

default
{
	state_entry()
	{
		llSetAlpha(1.0,ALL_SIDES);
		llSetCameraEyeOffset(<-3, 0, -5> );	// move camera back
		llSetCameraAtOffset(<-2, 0, 1>);
		rotation rot     = llEuler2Rot(ROT * DEG_TO_RAD);     // convert the degrees to radians 
		llSitTarget(TARGET, rot); // where they sit
	}

	changed(integer change)
	{
		if (change & CHANGED_LINK)
		{
			key av = llAvatarOnSitTarget();
			if (av) //evaluated as true if not NULL_KEY or invalid
			{
				llRequestPermissions(av, PERMISSION_TRIGGER_ANIMATION);
			}
			else
			{
				llSetAlpha(1.0,ALL_SIDES);	// make visible when they stand
			}
		}
	}

	run_time_permissions(integer perm)
	{
		if(PERMISSION_TRIGGER_ANIMATION & perm)
		{
			llStopAnimation("sit");
			llStartAnimation("sit");	// you can choose a different animation for sitting
			llSetAlpha(0.0,ALL_SIDES);
			llMessageLinked(LINK_SET,0,"sit","");	// tell the main script we are ready.
		}
	}
	on_rez(integer p)
	{
		llResetScript();
	}
}

Seat Script:

Add this script to the seat, or root prim. Be sure you compile it in MONO mode. If you don't check the mono switch, you can easily run out of RAM on a long route. Compiling in MONO goves you an extra 48K bytes of RAM to hold the route information.

The seats X axis must face forward. Don'tt worry about that now. After your get it booted up, you will be able to rotate the seat to fix this.



integer _debug = 0;

integer REZ = 3454;

vector TARGET = <-1.0,0,0.02>;
vector ROT = <0, -90, 0>;
list Line = [];
list lCoordinate = [];
list lDescriptions = [];
list lSounds = [];
list lCommands = [];
list lLinks = [];

// notecard reading
integer iIndexLines; 
string NOTECARD = "Route";        // the notecard for configuring

key kNoteCardLines;        // the key of the notecard
key kGetIndexLines;        // the key of the current line
integer i = 0;
integer count = 0;
integer timeout = 0;


float DAMPING = .2;   // .3

vector TargetLocation;


float INTERVAL = .050; // seconds to move

string SpeakThis;  // what is to be said
string PlayThis;  // what is to be Played

string strip( string str)
{        
    string out;
    integer i;
    
    //llOwnerSay("str = " + str);
    
    for ( ; i < llStringLength(str); i++)
    {
        out += llGetSubString(str,i,i);

        out = llStringTrim(out, STRING_TRIM);
        //llOwnerSay("out = " + out + " at " + (string) i);
    }
    return out;

}

string Getline(list Input, integer line)
{
    return strip(llList2String(Input, line));
}


default
{
    on_rez(integer param)
    {
        llResetScript();
    }
    
    changed( integer change )
    { 
        if (change & CHANGED_INVENTORY) 
        {    
       //     llResetScript(); // uncomment if you want to load the notecard when you drop a new notecard in
        }
    }
    
    state_entry()
    {
        llSetAlpha(1.0,ALL_SIDES);
        llSetCameraEyeOffset(<0, 0, -5> );
        llSetCameraAtOffset(<0, 0, 1>);

        
        rotation rot     = llEuler2Rot(ROT * DEG_TO_RAD);     // convert the degrees to radians, then convert that vector into a rotation, rot30x 
        llSitTarget(TARGET, rot); // where they sit
        llSetPrimitiveParams([PRIM_PHYSICS, FALSE]);
        llSetBuoyancy(1);
        //llSetStatus(STATUS_ROTATE_X | STATUS_ROTATE_Y | STATUS_ROTATE_Z, FALSE);
        //llSetColor(<1,0,0>, ALL_SIDES);
        kNoteCardLines = llGetNumberOfNotecardLines(NOTECARD);
        kGetIndexLines = llGetNotecardLine(NOTECARD,0);

         
         llSay (0, "Tour guide initialising. Please wait.");
    }
    
     // read notecard on bootup
    dataserver(key queryid, string data)
    {

        vector TempLocation;
        if (queryid == kNoteCardLines)
        {
            iIndexLines = (integer) data;
        }

        if (queryid == kGetIndexLines)
        {
            if (data != EOF)
            {
                queryid = llGetNotecardLine(NOTECARD, i);
                list lLine = (llParseString2List(data, ["|"], []));
                //if (_debug )    llOwnerSay("Line = " + llDumpList2String(lLine,":"));

                float X = (float) Getline(lLine,1);
                if (X > 0)
                {
                    float Y = (float)Getline(lLine,2);
                    float Z = (float)Getline(lLine,3);
                    string Msg = llList2String(lLine,4);
                    string sUUID = Getline(lLine,5);
                    string sCommand = Getline(lLine,6);
                    string sLink = Getline(lLine,7);
    
                    TempLocation.x = X;
                    TempLocation.y = Y;
                    TempLocation.z = Z;
        
                    lCoordinate = lCoordinate + [TempLocation];
                    lDescriptions = lDescriptions + [Msg];
                    lSounds +=  [sUUID];
                    lCommands += [sCommand];
                    lLinks += [sLink];
    
                    integer locationLength = (llGetListLength(lCoordinate));
                    integer     InitPerCent = (integer) llRound(((float) locationLength / (float) iIndexLines) * 100);
                    llSetText("Initialising... \n" + (string) InitPerCent + "%" , <1,1,1>, 1.0);
                    if (InitPerCent == 100)
                    {
                        state Paused;
                    }
                   
                }
                else
                {
                    state Paused;
                }
                i++;
            }
            kGetIndexLines = llGetNotecardLine(NOTECARD,i);
            // (_debug ) llOwnerSay("Got " + (string) i);
        }
    }
    
    
    
                
    
    touch_start(integer total_number)
    {
        integer check = llGetListLength(lCoordinate);
        
        if (_debug) 
            llOwnerSay("List is " + (string) check + " destinations long");
        
        if (check >= iIndexLines)
        {
            state Paused;
        }
        
        if (check < iIndexLines)
        {
            llSay(0, "Hang on a sec, still initialising...");
        }        
    }
    
    state_exit()
    {
        llSetText("", <1,1,1>, 1.0);
        TargetLocation = (vector) llList2String(lCoordinate, 0);  // Look at 0th
        
        if (_debug) 
            llOwnerSay("Looking at Target Location = " + (string) TargetLocation);
        
    }    
}


state Paused
{
    state_entry()
    {
        llSay(0,"Ready");
    }

    link_message(integer sender,integer num,string msg, key id)    
    {
        if (msg =="sit")
        {
            llWhisper(0,"Please stay seated. Waiting 10 seconds for a passenger");
               
            llSleep(10.0);
                
            llShout(REZ,"rezswan");
            state moving;
        }
    }

    changed(integer change)
    {        
        if (change & CHANGED_INVENTORY)
        {
            llResetScript();
        }
    }   


    
    
}

state moving
{

    state_entry()
    {
        llSetPrimitiveParams([PRIM_PHYSICS, TRUE]);
        
        if (_debug) llOwnerSay("State Moving entered, is pointing to target " + (string) TargetLocation );
        
        llLookAt(TargetLocation, 2, 1); 
        
        SpeakThis =  llList2String(lDescriptions, count);
        if (_debug) llOwnerSay("Speaking:" + SpeakThis);
        
        if (llStringLength(SpeakThis) > 1)
            llSay(0,SpeakThis);

        PlayThis =  llList2String(lSounds, count);
        if (_debug) llOwnerSay("Playing:" + PlayThis);
        if (llStringLength(PlayThis) > 1)
            llPlaySound(PlayThis,1.0);
            
        llSetTimerEvent(INTERVAL);
    }
    
    changed(integer change)
    {
         if (change & CHANGED_LINK) 
         { 
            key av = llAvatarOnSitTarget();
            //llWhisper(0,"Sit by " + (string) av);
            if (av) //evaluated as true if not NULL_KEY or invalid
            {
                state moving;
            }
            else
            {
                state end;
            }
        }
    
    }    
    
    
    on_rez(integer param)
    {
        llResetScript();
    }
        
        
    timer() 
    {
        timeout ++;

        if (timeout%20 == 0)
            llMessageLinked(LINK_SET,0,"FLAP","");	// for optional wing flap script


        if (_debug)  llSetText("Timeout... \n" + (string) timeout, <1,1,1>, 1.0);
        
        if (llVecMag(llGetPos() - TargetLocation) > .49999)
        {
            if (timeout > 30/INTERVAL) // Time Out to contingency, we hit something solid.
            {
                if (_debug)    llOwnerSay("Timeout!");
                state setposition;
            }
            llMoveToTarget((llVecNorm(TargetLocation - llGetPos()) * 1) + llGetPos(), DAMPING);
        }
        
        if (llVecMag(llGetPos() - TargetLocation) < .49999)
        {
            if (_debug) llOwnerSay("At location: " + (string) llGetPos());
            state speaking;
        }

            
    }
    
    state_exit()
    {
        llSetTimerEvent(0);
        timeout = 0;
        i = 0;
    }
        
}

state setposition //contingency
{
    state_entry()
    {
            llSetPrimitiveParams([PRIM_PHYSICS, FALSE]);

            llSetTimerEvent(INTERVAL);        
    }
    
    changed(integer change)
    {
         if (change & CHANGED_LINK) 
         { 
            key av = llAvatarOnSitTarget();
            if (av) //evaluated as true if not NULL_KEY or invalid
            {
                llWhisper(0,"Please stay seated");
            }
            else
            {
                state end;
            }
        }
        if (change & CHANGED_INVENTORY)
        {
         //  llResetScript();
        }
    
    }    
    



    timer() 
    {
        if (llVecMag(llGetPos() - TargetLocation) > .49999)
        {
            if (_debug )  llOwnerSay("setposition llLookAt: " + (string) TargetLocation);
                
            llSetPos((llVecNorm(TargetLocation - llGetPos()) * 0.5) + llGetPos());
            llLookAt(TargetLocation, 10, 10);
        }
        
        if (llVecMag(llGetPos() - TargetLocation) < .49999)
        {
            if (_debug)
                llOwnerSay("At location: " + (string) llGetPos());
            state speaking;
        }

    }
    state_exit()
    {
        llSetTimerEvent(0);
    }
       
}

state speaking
{

    state_entry()
    {
        count ++;
        if (count > (iIndexLines - 1))
            state end;
        
        if (count <= (iIndexLines - 1))
        {
            TargetLocation = (vector) llList2String(lCoordinate, count);        
            if (_debug) llOwnerSay("New Target: " + (string) TargetLocation);
            state moving;
        }
    }  
   
    state_exit()
    {
        llSetTimerEvent(0);
    }
    
}

state end
{
    state_entry()
    {
        llMessageLinked(LINK_SET,0,"SIT","");
        llSetAlpha(1.0,ALL_SIDES);
        llSetTimerEvent(0);
        llSetPrimitiveParams([PRIM_PHYSICS, FALSE]);
        key av = llAvatarOnSitTarget();
        if (av) {//evaluated as true if not NULL_KEY or invalid
            llWhisper(0, llKey2Name(av) +" thank you for taking the tour");
            llStopAnimation("sit knees up2");
            llUnSit(av);
        }
    

        llSay(0, "Tour stopped.  It will disappear in one minute");
        llSetTimerEvent(60);
        
    }
    
    changed(integer change)
    {
         if (change & CHANGED_LINK) 
         { 
            key av = llAvatarOnSitTarget();
            //llWhisper(0,"Sit by " + (string) av);
            if (av) //evaluated as true if not NULL_KEY or invalid
            {
                llWhisper(0,"Please stay seated");
                state moving;
            }
            
        }
        if (change & CHANGED_INVENTORY)
        {
         //   llResetScript();
        }
        
    }    
    on_rez(integer param)
    {
        llResetScript();
    }


    timer() 
    {
         llDie();
    }
    
}


Setting up the route

You will use the Waypoint prim #10 to establish a path of waypoints for the bird to follow. To start with, rez a prim ad name it '10', or any other number without the double quotes, at a spot at the beginning of the ride.

Mark the prim as 'Phantom'. The bird will pass through the #10 prim on its route.

Put the following script in it. Take a copy of that prim back into inventory.

Left-click and drag from inventory the '10' prim at a spot nearby, in the direction you want the bird to travel. Do not use shift-copy! The prim you shift copy will be in reverse order, as the original #10 moves, and leaves behind a copy. So just click and drag out a new one each time you need another waypoint.

Each prim will rename itself to the largest number found +10, about 5 seconds after it rezzes. Since you started with prim #10, the second prim will rename itself as #20, the next will be 30, and so on.


// WAYPOINT #10 PRIM SCRIPT
// When rezzed, does a llRegionSay on channel 300 of the word "number".  Other #10 prims hear this
// chats 10||Description on channel 300 when it hears 'number' on 300. 
// remembers the highest number it hears and sets the name to 1- that number


integer wanted = 0;
integer debugger = 1;

list prims;


debug(string message)
{
    if (debugger)
        llOwnerSay(message);
}

default
{
    state_entry()
    {
        llListen(300,"","","");
        wanted++;
        llRegionSay(300,"number");
        llSetTimerEvent(5.0);   // 5 seconds to hear from all prims
        llOwnerSay("Setting coordinates");
    }

    listen(integer channel,string name, key id, string message)
    {
        if (message == "die")
            llDie();

        else if (message =="where")
            llRegionSay(300,llGetObjectName() + "|" + (string) llGetPos() + "|" + llGetObjectDesc());

        else if (message =="number")
            llRegionSay(300,llGetObjectName());

        else if (wanted)
        {
            prims += (integer) message; // add to memory list
        }


    }

    timer()
    {
        wanted = 0;
        prims = llListSort(prims,1,0);  // sort descending
        integer num = (integer) llList2Integer(prims,0); // get highest number
        llSetObjectName((string) (num + 10));  // leave room for more prims to be added
        llOwnerSay("Name set to " + llGetObjectName() + ".  You can add text to be spoken when the tour reaches this location by adding some text to the description of this object.");

        llSetTimerEvent(0);
    }

    on_rez(integer p)
    {
        llResetScript();
    }
}





Special Commands

The bird has some special commands that it can use to chat with your riders. To get it to chat to your visitors, go to your '10' prims and change the description ( not the name) of #10 to "Beginning" and change prim #20 to "End". Or pick any other saying, such as "welcome" and "goodbye". You can also add text mentioning your store or sights to see. This text will be chatted at the prim BEFORE the the one you store the text in.

You can also have it play a sound.Sounds will be played when the bird spots the new destination. This is always one prim before you set the sound file. There are several ways to do this:

1) The simplest is to add a sound file to the seat. For my example, I am assuming the text to be chatted is "Look at the nifty thing I made", and your sound is named 'LookatMySkybox', without all the double quotes. Now change the description of the The #30 prim to Look at the nifty thing I made|LookatMySkybox. Separate the two with a pipe, | and add the sound name. This only works when the sound file is also inside the seat. The sound will play when it reaches prim #20, not #30.

2) If your sound file has full permissions/rights, you can just copy the sound UUID from inventory and paste it in after the pipe '|'.

3) In either case, there must be some of text there or the sound will not play. If you want a sound to play, and no text to be chatted, use a space, as in '(space)|SoundOrUUIDtoPlay

Recording the notecard

Once you have at least two prims in place, you can set up a test ride for the bird. My advice is not to rush off and place hundreds of prims. Try two, okay? Or maybe 4 in a square so you can see how fast the bird will turn and plan your turns accordingly. If you built it like mine, it will also be fat and wide to hold two big-assed avatars. You need room to turn, too.

Drop this script into another spare prim, and touch it. It will interrogate all the #10 prims and give you a list of coordinates that you can copy and paste from chat.

// Version 1.1 09-06-2012
// Changed lines 54 and 55 to this: 
//    integer aname = (integer) llList2String(msg,0); 
//    vector dest  = (vector) llList2String(msg,1);


integer wanted = 0;
integer debugger = FALSE ;
list prims;


debug(string message)
{
    if (debugger)
        llOwnerSay(message);
}



string left(string src, string divider) {
    integer index = llSubStringIndex( src, divider );
    if(~index)
        return llDeleteSubString( src, index , -1);
    return src;
}

string right(string src, string divider) {
    integer index = llSubStringIndex( src, divider );
    if(~index)
        return llDeleteSubString( src, 0, index + llStringLength(divider) - 1);
    return src;
}



default
{
    state_entry()
    {
        llSetText("Click after setting up all tour prims.", <1.0, 1.0, 1.0>, 2.0);
        llListen(300,"","","");
    }

    touch_start(integer n)
    {
        llOwnerSay("Please wait");
        prims = [];
        wanted ++;
        llRegionSay(300,"where");
    }

    listen(integer channel,string name, key id, string message)
    {
        debug(llGetObjectName() + " heard  " + message);
        if (wanted)
        {
            list msg = llParseString2List(message,["|"],[""]);
            integer aname = (integer) llList2String(msg,0);        // changed V 1.1
            vector dest  = (vector) llList2String(msg,1);            // changed V 1.1
            string text = llList2String(msg,2);
            integer  isthere = llListFindList(prims,[aname]);


            if (isthere > -1)
            {
                llOwnerSay("Error, there are two prims named " + aname + ".  Please make sure each prim is uniquely numbered from - to N in sequence from the start prim to the finish prim. Gaps in the sequence are allowed.");
            }

            if (wanted)
            {
                prims += (integer) aname;
                prims += dest;
                prims += text;

            }
            llSetTimerEvent(10.0);
        }
    }

    timer()
    {
        wanted = 0;
        llSetTimerEvent(0);
        integer i = 0;

        //prims = llListSort(prims,3,1);


        for (i = 0; i < llGetListLength(prims); i+=3)
        {
            integer primnum = llList2Integer(prims,i);
            vector loc = llList2Vector(prims,i+1);    
            string text = llList2String(prims,i+2);
            llOwnerSay("|" + (string) primnum + "|" + (string) loc.x + "|" + (string) loc.y + "|" + (string) loc.z + "|" + text);

        }
        llOwnerSay("Copy the above lines to the notecard in the tour guide and reset it");


    }

    on_rez(integer p)
    {
        llResetScript();
    }
}

Click the "Tour Recorder" prim once. It should say "Please wait". After 10 seconds, it will print out a list of coordinates into chat:
[19:28]  Tour  recorder: 10|24.088040|48.962760|54.082320|
[19:28]  Tour  recorder: 20|18.274350|53.096180|56.138620|
[19:28]  Tour  recorder: 30|19.203230|77.231740|38.415370|
[19:28]  Tour  recorder: 40|18.877440|77.295090|39.746290|
[19:28]  Tour  recorder: 50|37.810330|78.505890|40.247760|
[19:28]  Tour  recorder: 60|49.180880|156.787400|20.250000|There be dragons! (free dragons)
[19:28]  Tour  recorder: Copy the above lines to the notecard in the tour guide and reset it

Highlight these chatted lines with your mouse ad press Ctrl-C to copy them. In my example above, I had prims numbers 10 through 60.

Now edit the Notecard "Route" in the seat of the bird.

Paste the chat into the notecards and save it. You can change anything up to the first pipe "|" symbol. A comment for things like Start and end is a good thing to do. I like to work on small route sections. Marking the start and end points in the note card lets me add routes and subtract them later as the sim changes.

You should see hovertext above the seat counting upwards to 100%. Once it is finished, please take can take a copy of the bird into inventory. This is the one you will rez for your guests. And if you goofed, ( as I did many times), and the bird runs off the Never Never land and you lose it, you will have a copy. Trust me, Just try it at 1,000 meters in the air, and stop all scripts in a physical, phantom object, and watch it fall thru the floor, then watch it fall down down down as you desperately try to camera back, and sweep grab it before you lose an hour or a days work. Its happens. A lot.

Now all you have to do it to right click and sit on either pose ball, and in 10 seconds, the bird will travel to prim #10, then prim #20 and so on.

Once the bird reaches the end of the tour, it will disappear in one minute. You guests can hop off at any time, and it will wait a minute for them to hop back on before it poofs.

Adjusting the route

If you need to add a waypoint between two already issued boxes, do it manually. Drag out a new copy of prim box #10, edit the box after it rezzes, and change the name to be a number that is between the other two box numbers. For example, if you need to add a box between number 100 and 110, make it any number from 101 to 109. Click the recorder box and paste the new chat into a notecard. Make sure it works by taking another wild ride. You can change the #10 prims all you want.

The bird is designed to skim along and go over ground very closely. A #10 prims that is 0.5 meters square is perfect if they fit flat on the ground. But the bird will not penetrate the ground or solid objects. If you get stuck because a prim is too low, move the prim up slightly, click the recorder, and remkae the notecard. The bird will not penetrate solid objects, either. It will attempt to go through them for 30 seconds. After 30 seconds it switches to non-physical, jerky motion, and moves through the object. It will also do that if it cannot reach the next #10 prim in 30 seconds. The cure for the latter is simple, add another prim halfway between the others.

This pic shows a few things that can help:

What to do and not to do

Finishing and Cleaning Up

Once you are certain that this route, is done, you can clean up all the #10 prims by typing '/300 die". It helps to shout it. It hekops to fly around everywhere and shout it, or you will find them days later, like I did. 100 meter shout distances is not that far.

So how do they get a bird ride?

You need a 'Tour bird rezzer'

Add this script to a prim to make a 'Tour bird rezzer'. Put this script into any prim, and also add a copy of your tour bird. It keeps track of the number of tours given, and rezzes a new bird after one leaves the area.

Click it to get it started. If you click it, it will chat to you the number of tours given, and it will run a scan to see if a bird is nearby. If no bird is found, it rezzes a new one in ten seconds.


string aname ;
integer counter;

default
{
    state_entry()
    {
		llListen(3454,"","","");
		aname = llGetInventoryName(INVENTORY_OBJECT,0);
    }


    listen(integer channel, string name,key id, string msg)
    {
        llSleep(10); //the some time for the tour  to get out of range
        llSensor(aname,"",PASSIVE|SCRIPTED,5,PI);
    }
    
    touch_start(integer who)
    {
        if (llDetectedKey(0) == llGetOwner())
            llOwnerSay((string) counter + " tours given");
            
        llSensor(aname,"",PASSIVE|SCRIPTED,5,PI);
    }
    
    sensor(integer num)
    {  
        llSensorRemove();
    }
    
    no_sensor()
    {
        llOwnerSay("Rezzing " + aname);
		counter++;
        rotation rezrot = llEuler2Rot(<0.0,0.0,0.0>) * llGetRot();
        llRezObject(aname, llGetPos() + <0.0,0.0,0.5>,  <0.0,0.0,0.0>, rezrot, 0);
    }
}

Troubleshooting and Tips

Flies at an angle: You will need to rotate the seat so that the X axis of just the seat points forward. If the bird drags you off at a weird angle, ( which is great fun for you and whoever is watching, but somewhat impractical), then stop the scripts, edit it, turn off PHYSICAL so it does not fall thru the ground/floor or whatever, ( you've been warned lol) and rotate the seat to point in the right direction. Then start the scripts again and set it back to a physical object. It will zoom off to the beginning, so ride it back home. It its still off ( it will be), stop the scripts, watch yourself fall to the bottom of the lake, have a good laugh, promise to turn off Physical next time, and rotate the seat again. Repeat until you have seen all the lake bottoms in your area, and be sure to fall on some visitors while you are at it, until your get the bird facing forward, upright, and off the bottom of the nearest lake.

I ran out of memory with a stack-heap error! Okay, don;t panic. You did flip on the mono switch when you saved it?

It takes off and heads for <0,0,0>, the SW corner of the sim. It has something wrong in the notecard. A blank line will will do that. So will typing in an extra comma or an extra period when you hand-edit it. Its never ever happened to me....no more than once or twice each edit, anyway.

It rotates like craze at the end. If it acts crazy, but only at the end of the tour, edit the notecard and remove the blank space at the end of it. There should not be a blank line at the end. This little piece of advice cost me a week to figure out. But you got a better script for that, as I rewrote most of it trying to figure that part out.

It says there are two boxes named .... The recorder will complain if there are two boxes with the same name. Track the errant box number down, and rename it to fit between two other boxes. This can happen if you rez them too quickly.

I feel sick after riding it Up and down is fun! Make your bird climb a wall and go over a roof. Place #10 prims at the intersection of the wall and floor, wall and ceiling, and bottom gulleys in of roofs, and it will happily go straight up and down. You can go around a spiral staircase if you place prims in short runs.

I put out 150 prims and its missing some of my route. Don't put out too many prims at one time. Recorder prims with more than 64 waypoints can miss messages ( the SL listen queue length is only 64) and you can end up with duplicate prim numbers. Make smaller routes and copy and paste them into one notecard.

Happy sailing on Valentines Day!

Back to the Best Free Tools in Second Life and OpenSim.