Jump to content

Thoughts on a tractor


SmacemanSpiff Grau
 Share

You are about to reply to a thread that has been inactive for 1166 days.

Please take a moment to consider if this thread is worth bumping.

Recommended Posts

So, let’s just imagine I’m a farmer.  I'm not, but let's imagine.  Let's also imagine that as a farmer I have a big field.  On this field there is a tractor.  


This tractor will:
-    NOT be a vehicle, but act as a “tour bus” when someone sits on it
-    Sit idle in the field until someone clicks on it to ride it.
-    The tractor will then either sit idle for a picture opportunity or drive itself and the rider in a preset loop around the field.  The rider will also be able to pause the tractor route via the menu if they see a bird or a cow (because cows are fun to stop and look at).
-    When the rider stands up, the tractor will stay in position waiting for the next person to sit on it with the ability to resume the preset route from where it left off.
-    It will NOT run the preset route if nobody is riding it, because that would be a rogue tractor. SAFTEN UP, PEOPLE!

I am currently working with llSetKeyframedMotion, which comes SO CLOSE to doing what I want.  Everything is working, including the sit position, except for one component.  I have a preset loop programmed, which works.  I use KFM_CMD_PAUSE to pause the tractor in route and KFM_CMD_PLAY to resume, which also works.  The caveat is that when it is in PAUSE mode and someone right clicks the tractor to sit or stand and then de-selects it, it automatically resumes the KeyframedMotion.  This is stated in the wiki, and proven by me (obviously the final word… ).  I can program the tractor to SIT ON TOUCH, but we all know that people will still right click to get the pie menu to sit on it or perv out the details (at least, I would…).  

Is there any way around the “resume KeyframedMotion after selection” caveat?

I’ve tried thinking of a way to stop the keyframe command and then restart it from where it left off (basically pausing, but actually stopping the command), but my brain hasn’t gotten there.  The biggest issue is if it is stopped in the middle of a turn there is no easy way of picking back up from there.

I tested using single instances of llSetKeyframedMotion for each leg of the route, starting a new one once it reaches a target, but this got messy really fast and has potential for a TON of script, which has more potential for errors and offsets.

I considered using llMoveToTarget, but I didn’t really want to make it a physical object, and llSetKeyframedMotion is so much smoother and easier to work with.

I have not played with any “roller coaster” style scripts yet, but I’m afraid any prim track I use would eat up all my available prim.  It’s a homestead, but still… 

At this point, my last resort is just putting out a rezzer that will only allow one tractor at a time on property.  Once the rider gets off, it will die and rez a new one at the rez point.  This will eliminate the “pause and resume” option after the rider stands up and kind of defeats the whole purpose I set out with.  I’m not thrilled about this idea, but it is a backup plan.

The cows and I thank you for any suggestions.
 

Link to comment
Share on other sites

I've never really used keyframe motion before, but the only solution that hits me is to KFM_CMD_STOP the tractor instead of pausing it. this would reset the tractor to the beginning of its route, but perhaps, after stopping the tractor, you could place it at its last keyframe position and recalculate its route to start from there.

Link to comment
Share on other sites

8 hours ago, SmacemanSpiff Grau said:

The caveat is that when it is in PAUSE mode and someone right clicks the tractor to sit or stand and then de-selects it, it automatically resumes the KeyframedMotion.  This is stated in the wiki, and proven by me (obviously the final word… ).  I can program the tractor to SIT ON TOUCH, but we all know that people will still right click to get the pie menu to sit on it or perv out the details (at least, I would…).  

Ignoring KFM_CMD_PAUSE for the moment, I find
       llSetStatus(STATUS_BLOCK_GRAB, TRUE);
seems to help in my cursory testing with non-privileged alts. I, as owner, can still grab the thing with a right click and thereby screw up the motion, as can a Friend to whom I've given permission to Edit, Delete & Etc my stuff, so testing requires a particularly lowly confederate. I tried giving the thing a sit target and did not find being a "passenger" to confer the special "screw up the motion" privilege (the wiki's claim notwithstanding).

But none of this was using KFM_CMD_PAUSE, and I'm not sure now how that command even works; every time I try to resume paused motion with a KFM_CMD_PLAY it does something stupid, regardless of how I've invoked those commands within the script. So at the moment I can't even get far enough to replicate the actual problem you're having.

  • Like 2
Link to comment
Share on other sites

Qie -  Thank you so much!  That fixed it!  At least in my first tests.  Looks like people can still right-click to get the pie menu, but it prevents them from grabbing it.  There are only two of us with edit rights, so it should be great.

I played with the PAUSE and PLAY commands some more, and the more you start and stop, the object does start getting a little wonky and off track.  I will probably have to build in a reset function at a target location to keep it clean.  

Here is a "basic" version of the working script.

////// VARIABLES

key ActiveID;
integer listenHandle_A;
vector sit_here = <-0.3, 0.0, 0.55>;
string animation = "TankSitterV6";

integer KeyframeActive = FALSE;
integer KeyframePause = FALSE;


////// FUNCTIONS

rotation NormRot(rotation Q)
{
    float MagQ = llSqrt(Q.x*Q.x + Q.y*Q.y +Q.z*Q.z + Q.s*Q.s);
    return
        <Q.x/MagQ, Q.y/MagQ, Q.z/MagQ, Q.s/MagQ>;
}


StartFlightPath()
{
    llSetRegionPos(<144.88700, 144.00000, 501.87710>);
    llSetRot(<-0.00000, 0.00000, -1.00000, 0.00000>);

    llSleep(2.0); // making sure position and rotation are set before taking off
 
    llSetKeyframedMotion(
            [
                <-33.38704, 0.00000, 0.00000>, NormRot(<0.00000, 0.00000, 0.00000, 1.00000>), 6.677408,
                <-7.57170, -6.14423, 0.00000>, NormRot(<0.00000, 0.00000, 0.70711, 0.70711>), 1.950202,
                <9.88572, -7.05081, 0.00000>, NormRot(<0.00000, 0.00000, 0.70711, 0.70711>), 2.428509,
                <31.07302, 0.00000, 0.00000>, NormRot(<0.00000, 0.00000, 0.00000, 1.00000>), 6.214604,
                <4.32674, 6.56656, 0.00000>, NormRot(<0.00000, 0.00000, 0.70711, 0.70711>), 1.572773,
                <-4.32674, 6.62848, 0.00000>, NormRot(<0.00000, 0.00000, -0.70711, -0.70711>), 1.583128
            ],    //
            [KFM_MODE, KFM_LOOP]);    
}


PauseFlightPath()
{
    llSetKeyframedMotion( [], [ KFM_COMMAND, KFM_CMD_PAUSE]);    
}


ResumeFlightPath()
{
    llSetKeyframedMotion( [], [ KFM_COMMAND, KFM_CMD_PLAY]);    
}


StopFlightPath()
{
    llSetKeyframedMotion( [], [ KFM_COMMAND, KFM_CMD_STOP]);    
}



////// MENUS

Menu_A(key id)
{
 
    // MAIN MENU
    
    string text = "\nMain Menu\n\n";
    list buttons;
                        
    // if KeyframActive is not active, no reason to play or pause
    if(!KeyframeActive && !KeyframePause) buttons = 
                        [ "Reset", "EXIT"];
                        
    // if KeyframActive is active, allow pause
    else if(KeyframeActive && !KeyframePause) buttons = 
                        ["Stop", "Pause", "EXIT",
                        "Reset"];
                     
    // if KeyframActive is active and has been paused, allow play
    else if(KeyframeActive && KeyframePause) buttons = 
                        [ "Stop", "Resume", "EXIT",
                        "Reset"];
                      
    llDialog(id, text, buttons, Channel_A());
}



////// CHANNELS

integer Channel_A()
{
    integer channel;    
    listenHandle_A = llListen (channel =  -1 - (integer)("0x" + llGetSubString( (string)llGetKey(), -8, -2) ), "", ActiveID, "");
    llSetTimerEvent (60.0);
    return channel;
}

Close_Channel_A()
{
    llListenRemove (listenHandle_A);
    llSetTimerEvent (0.0);
}



////// SCRIPT BODY

default
{
    state_entry()
    {
        llSitTarget(sit_here, ZERO_ROTATION);
        llSetStatus( STATUS_BLOCK_GRAB_OBJECT, TRUE);	// THANKS QIE!
        llSetLinkPrimitiveParamsFast(LINK_THIS,
                [PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX]);
    }

    touch_start(integer total_number)
    {
        ActiveID = llDetectedKey(0);
        Menu_A(ActiveID); 
    }
    
    
    changed(integer change)
    {
        if (change & CHANGED_LINK)
        {
            key agent = llAvatarOnSitTarget();
            if (agent)
            {
                llRequestPermissions(agent, PERMISSION_TRIGGER_ANIMATION);
            }
            else
            {
                llStopAnimation(animation);
            }
        }
    }
    
    run_time_permissions(integer perm)
    {
        if (perm)
        {
            llStartAnimation(animation);
        }
    }
    
    listen(integer channel, string name, key id, string message)
    {
        if (channel == Channel_A())
        {
            Close_Channel_A();
                
            if(message == "Reset")
            {
                StopFlightPath();
                StartFlightPath();
                KeyframeActive = TRUE;
                KeyframePause = FALSE;
                Menu_A(id);
            }
            
            else if(message == "Pause")
            {
                PauseFlightPath();
                KeyframeActive = TRUE;
                KeyframePause = TRUE;
                Menu_A(id);
            }
            
            else if(message == "Resume")
            {
                ResumeFlightPath();
                KeyframeActive = TRUE;
                KeyframePause = FALSE;
                Menu_A(id);
            }
            
            else if(message == "Stop")
            {
                StopFlightPath();
                KeyframeActive = FALSE;
                KeyframePause = FALSE;
                Menu_A(id);
            }
        }
    }


    timer()
    {
        Close_Channel_A();
    }
}

 

  • Like 2
Link to comment
Share on other sites

18 hours ago, SmacemanSpiff Grau said:

I use KFM_CMD_PAUSE to pause the tractor in route and KFM_CMD_PLAY to resume, which also works.  

You could try splitting the route up into subsections. Each subsection has a start and end point and travels from one to the other by basic KFM. At each end point after KFM is stopped it can then wait for a pre-determined or random time, or until somebody hops aboard again. This removes the need to use pausing.

This is working on the principle that when people do want to stop and look around them its going to be at a point where there is something worth looking at, there's no point bothering with them wanting to stop and look at one particular blade of grass.

Link to comment
Share on other sites

Keyframe motion starts from where you are and is all relative, so you need to get your position and compute keyframes to get where you're going. Store the points you want to go through, and when you want to start moving, pick a nearby waypoint point and make your first keyframe take you there.

Note that there's some error in your final position. You have to recalculate the next move based on where you end up.

My NPCs do this, as do Yava Pods, Delaunay Industries freight lifters, and the trains at Janet's Viking sim, Folkvang. It's non-trivial to code but works quite well if done properly.

Link to comment
Share on other sites

Also, be aware that moving_start and moving_end can be a little uncertain (*), although this might be due to the scripts run problems and hopefully will improve after the next roll.

 

(*) I have a funicular at Zephyr, where scripts run is 39% and has been for weeks. The funicular cars start their journeys about every 2 minutes, using keyframed motion. Two or thee times a day a car either fails to begin moving, or fails to move far enough to the end of the journey, and I get IM reports of this, when the moving_end even hasn't occurred after twice the expected journey time. The car is usually 2 or 3 metres short of the destination, but quite often the car hasn't even begun to make the move. Since the majority of journeys are completed this is pointing to some hold-up in the server, but I can't even begin to investigate that. For a while I thought the errors were because the region had no avatars in it and was therefore being idled to give resources to more populated regions, but some of the incidents occur when I am in the region, often within a few metres of the funicular base station.

  • Like 1
Link to comment
Share on other sites

8 hours ago, animats said:

 

Note that there's some error in your final position. You have to recalculate the next move based on where you end up.

 

I am finding this with the KFM_CMD_PAUSE and KFM_CMD_PLAY.  It never stops or starts exactly where it "should", so the more I use it, the more off kilter the object gets.  

I did play around with way points, but I think my approach was messy.  I was using targets and different llSetKeyframedMotion commands at each target.  Because it is executing just shy of the actual marker, again, the route gets thrown off.  Similar to Profaitchikenz, it eventually never makes it to the next target and stalls out.  Your wording changed my approach to it, though.  I think I have an idea, or the start of one, to have a list way points, figure out which one is closest, move to that position / rotation,  clear the llSetKeyframedMotion list and generate a new one based on the waypoint.  This will eliminate the use of the PAUSE and PLAY commands and just use the STOP command.    

Another idea, and probably easier than doing lengthy math to determine the closest way point, is to utilize targets. 

Thanks for your thoughts!

Link to comment
Share on other sites

46 minutes ago, SmacemanSpiff Grau said:

did play around with way points, but I think my approach was messy.

It may be messy but given the problems I outlined above, it's a good pragmatic approach to this problem. The reason I suggested splitting the route into sub-routes does solve one of your problems of never being quite in the right place. At the end of the sub-route movement, simply do llSetRegionPos and then you'll be right where you want to be for the next start.

Link to comment
Share on other sites

It helps to approach scripted motion in SL like robotics. You issue a command to the actuators, something roughly similar to what you asked for happens, and then you issue corrections. You don't control the world, but you can influence it.

  • Like 1
Link to comment
Share on other sites

You are about to reply to a thread that has been inactive for 1166 days.

Please take a moment to consider if this thread is worth bumping.

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
 Share

×
×
  • Create New...