XMREngine.sorpra

From Dreamnation
Jump to: navigation, search

EXPLICITLY MOVING SCRIPT OBJECT ACROSS SIM BOUNDARY

The existing OpenSim functions for moving objects around do not allow anyone to be seated on the object when it crosses a sim boundary. This keeps one from creating vehicles that cross sim boundaries.

XMREngine provides a function that will move the object that the script is part of across a sim boundary along with any avatars seated on the object.

   integer xmrSetObjRegPosRotAsync (vector pos, rotation rot, integer options, integer eventnum, list eventprms)
   pos = position relative to the sim the object is currently in (same coords as used by llGetPos())
   rot = rotation to apply to the object (same coords as used by llGetRot())
   options = option flag bitmask
             0: seated avatars walk across border
             XMRSORPRA_FLYACROSS: seated avatars fly across border
                                  (useful for airplane-like vehicles so avatars don't fall when crossing)
   eventnum = event to post when the move is complete
   eventprms = parameters to pass to event handler
   returns 0: move is within current sim and was completed synchronously
              no event will be posted
           1: move crosses sim boundary and will complete in background
              event will be posted to script when move is complete

As mentioned in the 'returns' section above, if the pos argument indicates a position within the sim (0 <= x < 256 and 0 <= y < 256), the function completes before it returns to the script and the return value is 0.

OTOH, if the pos argument indicates movement into a neighboring sim (x < 0 or x >= 256 or y < 0 or y >= 256), the processing happens in the background and an event of your choosing is posted to the script when the move completes. The xmrSetObjRegPosRotAsync() function returns a value of 1 before the processing has completed. While the move is in progress, other events such as change() will be posted to the script indicating that avatars have been unseated and reseated and the object has changed region.

eventnum should be some event that your script does not use but can be any valid event. Use XMREVENTCODE_<eventname> symbols for this parameter. eventprms is a list of values to pass to the event handler, if any, and the values must be of the correct type(s) for the event handler.

Crossing a sim boundary is not completely transparent, so the script must account for these things:

   1) requested permissions are dropped
   2) llGetTime() base is different
   3) seating animations are reset to the default sit animation

It can be convenient to use the xmrEventDequeue() function following the xmrSetObjRegPosRotAsync() call when it returns a 1. xmrEventDequeue() will block and absorb the event posted by xmrSetObjRegPosRotAsync(). Don't forget that xmrEventDequeue() requires you to have a dummy event handler in your script for any events it processes or those events will be lost. See XMREngine.inline events for details on xmrEventDequeue().

Here is an example use of xmrSetObjRegPosRotAsync(). It is part of an animated horse that can seat up to two avatars. There is one global variable seatedTorsoAv that is set whenever someone is on the front position (the root prim), and another global var seatedRumpAv that is set whenever someone is seated on the rear position (a linked prim).

   /**
    * @brief Set position and/or rotation of the horse and seated avatars as a whole.
    *        If pos is outside the region (eg, something < 0.0 or >= 256.0) the horse and
    *        any seated avatars will be transparently TPd to the neighboring region.
    * @returns FALSE: stayed within same region
    *           TRUE: now in different region
    */
   integer SetNewPosRot (vector pos, rotation rot)
   {
       integer async = xmrSetObjRegPosRotAsync (pos, rot, 0, XMREVENTCODE_at_target, []);
       if (async) {
           
           /*
            * TP involved, wait for it to complete.
            * Ignore any changed events during TP so we dont think any avatars are getting up or
            * sitting down as the xmrSetObjRegPosRotAsync() will unseat then reseat any avatars.
            * Also ignore control events during TP so we dont process a bunch of arrow buttons when
            * TP completes so we don't do a big jump when we resume processing.
            * All other events will be queued for normal processing via their event handlers.
            */
           integer started = llGetUnixTime ();
           integer evcode;
           do {
               integer timeout = started + 30 - llGetUnixTime ();  // should be valid even when crossing sims
               list ev = xmrEventDequeue (timeout, XMREVENTMASK1_at_target | XMREVENTMASK1_control,
                                                   XMREVENTMASK2_changed, 0, 0);
               if (llGetListLength (ev) == 0) {
                   llSay (PUBLIC_CHANNEL, "timed out trying to cross sim border");
                   llResetScript ();
               }
               evcode = (integer)ev[0];
           } while (evcode != XMREVENTCODE_at_target);
           
           /*
            * If anyone is seated in front, re-request control key take-over and animation override.
            */
           if (seatedTorsoAv != NULL_KEY) {
               integer reqperms = PERMISSION_TAKE_CONTROLS | PERMISSION_TRIGGER_ANIMATION;
               llRequestPermissions (seatedTorsoAv, reqperms);
               list ev = xmrEventDequeue (30.0, XMREVENTMASK1_run_time_permissions, 0, 0, 0);
               if (llGetListLength (ev) == 0) {
                   llSay (PUBLIC_CHANNEL, "timed out re-requesting permissions");
                   llResetScript ();
               }
               evcode = (integer)ev[0];
               if (evcode != XMREVENTCODE_run_time_permissions) {
                   throw "bad event code " + evcode;
               }
               integer gntperms = (integer)ev[1];
               if (gntperms & reqperms != reqperms) {
                   llSay (PUBLIC_CHANNEL, "permissions not re-granted");
                   llResetScript ();
               }
               llStopAnimation ("sit");
               llStartAnimation ("horsefrontseat");
               llTakeControls (CONTROL_FWD | CONTROL_BACK | CONTROL_LEFT | CONTROL_RIGHT |
                               CONTROL_ROT_LEFT | CONTROL_ROT_RIGHT | CONTROL_UP | CONTROL_DOWN,
                               TRUE, FALSE);
           }
           
           /*
            * If someone is seated on the rear, tell the rear seating prim to re-
            * request animation permission and start the rear seating animation.
            */
           if (seatedRumpAv != NULL_KEY) {
               llMessageLinked (LINK_ROOT, 0, "rearsiton", seatedRumpAv);
           }
       }
       return async;
   }

The above example assumes the script contains this event handler for any state that the SetNewPosRot() function is called in:

   at_target () { }

IMPLICITLY MOVING OBJECTS ACROSS SIM BOUNDARY (WITH PHYSICS)

The xmrSetObjRegPosRotAsync() described above function is basically a souped-up llSetPos() function and so can be used to move the object to positions under direct script control. But the llSetVehicle...() functions cause the movement to occur in the background and the script is not directly positioning the object. So to handle this case, use the following function:

   xmrTrapRegionCrossing (integer enable)
   enable = TRUE: the next time the object attempts to cross a region boundary,
                  stop it from doing so and post a region_cross event to the script.
                  if the object attempts repeated crossings, only post the first one
                  until this function is called again with TRUE.
           FALSE: do not attempt to block or trap region crossing

... and this event handler:

   region_cross (vector newpos, vector oldpos)
   newpos = position that the object was attempting to move to, outside the current region
   oldpos = a position within the current region near the border

Before posting the region_cross() event, physics is disabled so the object will not continue to attempt to position itself outside the region, and the object is positioned just inside the border in the original region. You can then use xmrSetObjRegPosRotAsync() to move the object to its new position across the border, then re-enable physics.

The basic idea is:

  1. enable physics via llSetStatus(STATUS_PHANTOM,FALSE) and llSetStatus(STATUS_PHYSICS,TRUE) so OpenSim will process llSetVehicle...() calls
  2. load vehicle attributes with llSetVehicle...() calls but do not set it in motion yet
  3. call xmrTrapRegionCrossing(TRUE) so the script gets a region_cross() event if vechicle attempts to move out of the region
  4. at some point call llSetVehicle...() to put the object in motion

Then when the region_cross() event gets posted to your script, the server has disabled physics and re-positioned the object just inside the original region's border. So the script should do something like:

  1. call xmrTrapRegionCrossing(FALSE) so you can xmrSetObjRegPosRotAsync() to explicitly move object to new region
  2. call xmrSetObjRegPosRotAysnc(...) to start moving object and seated avatars to new region
  3. wait for xmrSetObjRegPosRotAsync() to complete. DO NOT try to reposition the object while waiting.
  4. re-enable physics via llSetStatus(STATUS_PHYSICS,TRUE) so OpenSim will process llSetVehicle...() calls
  5. reload vehicle attributes with llSetVehicle...() calls but do not set it in motion yet
  6. call xmrTrapRegionCrossing(TRUE) so the script gets a region_cross() event if vechicle attempts to move out of the region
  7. call llSetVehicle...() to put the object back in motion

An example script can be found at http://www.dreamnation.net/kunta/scripts/regioncrossevent.lsl. Just put this script in a default wooden cube object to try it. The Initialize() function sets up all the vehicle parameters and is called both at script reset time and just after crossing the sim border to tell the new sim server all about the vehicle parameters.

When the listen() event handler starts the object moving, it calls xmrTrapRegionCrossing (TRUE) just before actually starting the object moving. At this point the object is rolling along and will eventually hit a region boundary. At this point, the server will shut physics off (just like llSetStatus(STATUS_PHYSICS,FALSE)) and position the object just inside the border (just like llSetPos(...)). Then it will post a region_cross() event to the script. If the object were to make further attempts to cross the sim boundary, they will be blocked but no more region_cross() events will be posted to the script.

At this point in the example script (entry to the region_cross() event handler), it calls xmrTrapRegionCrossing (FALSE) so it can move the object across the region boundary with xmrSetObjRegPosRotAsync() without it being blocked. Then the script calls SetRotFast(ZERO_ROTATION) to turn the object upright to cross the border, so the object itself won't block the avatar from crossing the sim boundary.

Then it calls xmrSetObjRegPosRotAsync() to start moving the object and anyone seated across the border to the new sim. It should always return TRUE because we always give it co-ordinates outside the current region. While sorpraInProg > 0, all timer() and listen() events are blocked from attempting to change any vehicle settings.

Eventually the at_target() event handler will be called when the object and seated avatars have been moved to the new region. It clears sorpraInProg so timer() and listen() events will process vehicle setting changes. Then it reinitializes the vehicle settings so the new sim server will know what they are. Then it calls xmrTrapRegionCrossing (TRUE) so the region_cross() event will post if the object attempts to cross outside this new region. Finally, it calls UpdateLinearMotor() to set the object back in motion.