Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
wiki:scripting_portal:yengine.sample [2018/10/29 20:44]
Royale Mobian
wiki:scripting_portal:yengine.sample [2018/10/31 23:49] (current)
Royale Mobian
Line 5: Line 5:
 | [[wiki:​scripting_portal:​yengine.sample:​flight_2|Flight 2]] | uses llSetVehicle...() and region_cross() event for vehicle crossing sim boundaries | | [[wiki:​scripting_portal:​yengine.sample:​flight_2|Flight 2]] | uses llSetVehicle...() and region_cross() event for vehicle crossing sim boundaries |
 | [[wiki:​scripting_portal:​yengine.sample:​horse_control|Horse Control]] | horse control, region crossing handling more than one seated avatar includes example use of Timer class (from timer.lsl) | | [[wiki:​scripting_portal:​yengine.sample:​horse_control|Horse Control]] | horse control, region crossing handling more than one seated avatar includes example use of Timer class (from timer.lsl) |
-===== Pirate Ship Controller ​===== +| [[wiki:​scripting_portal:​yengine.sample:​pirate_ship_controller|Pirate Ship Controller]] | similar to blimp controller ​
-similar to blimp controller +| [[wiki:​scripting_portal:​yengine.sample:​region_cross_event|Region Cross Event]] | simpler (than flight 2) use of llSetVehicle...() crossing sim boundary | 
-<code ossl> +| [[wiki:​scripting_portal:​yengine.sample:​rummy_board|Rummy Board]] | rummy card game (lots of OOP usage and other language extensions) | 
-//​pirateshipctl+| [[wiki:​scripting_portal:​yengine.sample:​rummy_card_panel|Card Panel]] | part of rummy card game (runs in all the card display prims and button prims) | 
 +| [[wiki:​scripting_portal:​yengine.sample:​sailboat_control|Sailboat Control]] | sailboat control script | 
 +| [[wiki:​scripting_portal:​yengine.sample:​timer|Timer]] | general timer class library, provides several independent timers in the script |
  
-//  Copyright (C) 2013, Kunta Kinte of www.dreamnation.net 
-// 
-//  This program is free software; you can redistribute it and/or modify 
-//  it under the terms of the GNU General Public License as published by 
-//  the Free Software Foundation; version 2 of the License. 
-// 
-//  This program is distributed in the hope that it will be useful, 
-//  but WITHOUT ANY WARRANTY; without even the implied warranty of 
-//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ​ See the 
-//  GNU General Public License for more details. 
-// 
-//  You should have received a copy of the GNU General Public License 
-//  along with this program; if not, write to the Free Software 
-//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 ​ USA 
- 
-/** 
- * @brief Pirate Ship Controller 
- * 
- ​* ​  When nobody seated in CaptChair, ship is on autopilot and follows loop described 
- ​* ​  by points given in a notecard named "​autopilot"​. ​ Each line in the notecard is in 
- ​* ​  the form: 
- ​* ​       regionname <​positioninregion>​ speedtothispoint 
- * 
- ​* ​  Other lines in the notecard can be: 
- ​* ​       groundproximitywarningheight = <​metres> ​    ​default is 2.0 
- ​* ​       turnrate = <​turn_rate_degrees_per_second> ​  ​default is 3.0 
- ​* ​       timestep = <​position_updates_in_seconds> ​   default is 0.2 
- ​* ​       verbose ​ = <​debug_print_level> ​             default is 0 
- * 
- ​* ​  The prim named WaterLevel is used for the Z-axis position of the ship.  The script 
- ​* ​  keeps the WaterLevel prim's Z position at the sim's water level. 
- * 
- ​* ​  All prims with name starting with GroundSensor will be kept above ground. ​ If a 
- ​* ​  ​ground sensor prim would go below ground, the ship is stopped. 
- */ 
- 
-yoption advflowctl; 
-yoption arrays; 
-yoption chars; 
-yoption norighttoleft;​ 
-yoption objects; 
-yoption trycatch; 
- 
-constant KTSTOMPS = 0.514444444444444; ​ // knots -> metres/​second 
-constant MAXMANSP = 25.0;               // knots 
-constant MYST_STOPPED = 0; 
-constant MYST_RUNNING = 1; 
-constant MYST_MANUAL ​ = 2; 
- 
-array   ​regionpositions; ​               // regionname => globalcoord 
-float   ​groundproximitywarningheight = 2.0; 
-float   ​timestep = 0.2;                 // update position at this interval 
-float   ​turnrate = 3.0;                 // degrees per second 
-float   ​waterLevel; ​                    // current region'​s water level 
-integer aplistlen; ​                     // llGetListLength (autopilot) 
-integer canUseOsGetNotecardLine = TRUE; 
-integer lastPosReport = 0;              // time of last position report 
-integer mystate = MYST_STOPPED;​ 
-integer numSeated; ​                     // total number of avatars seated 
-integer stepnumber; ​                    // which point along current path we are on 
-integer verbose; ​                       // debugging shout level 
-key     ​seatedav = NULL_KEY; ​           // who is seated in captains chair 
-list    autopilot; ​                     // all points given in notecard 
-list    groundSensorPrimPoss; ​          // GroundSensor primitive'​s local positions 
-string ​ currentregion; ​                 // which region we are in 
-vector ​ currentregpos; ​                 // our current position in region 
-vector ​ waterLinePrimPos; ​              // WaterLine primitive'​s local position 
- 
- 
-default 
-{ 
-    state_entry () 
-    { 
-        Initialize (); 
-        llReleaseControls (); 
-        llShout (PUBLIC_CHANNEL,​ "Touch to start"​);​ 
-    } 
- 
-    touch_start () 
-    { 
-        string who = llDetectedName (0); 
-        switch (mystate) { 
-            case MYST_STOPPED:​ { 
-                llShout (PUBLIC_CHANNEL,​ "​["​ + who + "] Starting... touch to stop"​);​ 
-                if (seatedav == NULL_KEY) { 
-                    DoRunningState (); 
-                } else { 
-                    DoManualState (); 
-                } 
-                break; 
-            } 
-            case MYST_RUNNING:​ 
-            case MYST_MANUAL:​ { 
-                llShout (PUBLIC_CHANNEL,​ "​["​ + who + "] Stopped... touch to start"​);​ 
-                mystate = MYST_STOPPED;​ 
-                break; 
-            } 
-        } 
-    } 
- 
-    changed (integer change) 
-    { 
-        // reinitialize if notecard changed 
-        if (change & CHANGED_INVENTORY) { 
-            llResetScript (); 
-        } 
- 
-        // see if someone got up or sat down 
-        if (change & CHANGED_LINK) { 
-            CountNumSeated (); 
-            key av = llAvatarOnSitTarget (); 
-            if (seatedav != av) { 
-                llReleaseControls (); 
-                seatedav ​ = av; 
-                if (seatedav != NULL_KEY) { 
-                    llRequestPermissions (seatedav, PERMISSION_TAKE_CONTROLS);​ 
-                } else if (mystate == MYST_MANUAL) { 
-                    llShout (PUBLIC_CHANNEL,​ "​Stopped... touch to start"​);​ 
-                    mystate = MYST_STOPPED;​ 
-                } 
-            } 
-        } 
-    } 
- 
-    run_time_permissions (integer perms) 
-    { 
-        if (perms & PERMISSION_TAKE_CONTROLS) { 
-            llTakeControls (CONTROL_FWD | CONTROL_BACK | CONTROL_LEFT | CONTROL_RIGHT | 
-                            CONTROL_ROT_LEFT | CONTROL_ROT_RIGHT | CONTROL_UP | CONTROL_DOWN,​ 
-                            TRUE, FALSE); 
-            if (mystate != MYST_MANUAL) { 
-                llRegionSayTo (seatedav, PUBLIC_CHANNEL,​ "Use arrow keys to move me around"​);​ 
-                llRegionSayTo (seatedav, PUBLIC_CHANNEL,​ "Use Page Up/Down to change altitude"​);​ 
-                llRegionSayTo (seatedav, PUBLIC_CHANNEL,​ "You may have to click on blank area of screen first"​);​ 
-                DoManualState (); 
-            } 
-        } 
-    } 
- 
-    // for xmrEventDequeue() 
-    control ​   () { } 
-    dataserver () { } 
-    at_target ​ () { } 
-} 
- 
- 
-/** 
- * @brief Read notecard and validate it. 
- */ 
-integer Initialize () 
-{ 
-    llShout (PUBLIC_CHANNEL,​ "​initializing please wait"​);​ 
- 
-    /* 
-     * Parse autopilot notecard. 
-     * Each line is: 
-     ​* ​ regionname <​positionwithinregion>​ speedinknots 
-     */ 
-    if (llGetInventoryType ("​autopilot"​) != INVENTORY_NOTECARD) { 
-        llShout (PUBLIC_CHANNEL,​ "​missing autopilot notecard"​);​ 
-        aplistlen = 0; 
-    } else { 
-        integer notecardok = TRUE; 
-        for (integer nclino = 0; nclino < 1000; nclino ++) { 
-            string ncline = ReadNotecardLine ("​autopilot",​ nclino); 
-            if (ncline == EOF) break; 
-            try { 
- 
-                // trim off comment 
-                integer i = xmrStringIndexOf (ncline, "#"​);​ 
-                if (i >= 0) ncline = xmrSubstring (ncline, 0, i); 
- 
-                // trim off leading and trailing spaces and ignore blank lines 
-                ncline = llStringTrim (ncline, STRING_TRIM);​ 
-                if (ncline == ""​) continue; 
- 
-                // process value setting lines 
-                i = xmrStringIndexOf (ncline, "​="​);​ 
-                if (i >= 0) { 
-                    string name = llStringTrim (xmrSubstring (ncline, 0, i), STRING_TRIM);​ 
-                    string valu = llStringTrim (xmrSubstring (ncline, ++ i), STRING_TRIM);​ 
-                    switch (name) { 
-                        case "​groundproximitywarningheight":​ { 
-                            groundproximitywarningheight = xmrString2Float (valu); 
-                            break; 
-                        } 
-                        case "​timestep":​ { 
-                            timestep = xmrString2Float (valu); 
-                            break; 
-                        } 
-                        case "​turnrate":​ { 
-                            turnrate = xmrString2Float (valu); 
-                            break; 
-                        } 
-                        case "​verbose":​ { 
-                            verbose = xmrString2Integer (valu); 
-                            break; 
-                        } 
-                        default: { 
-                            throw "​unknown parameter " + name; 
-                        } 
-                    } 
-                    continue; 
-                } 
- 
-                // process standard line <​regionname>​ <​positionwithinregion>​ <​speedinknots>​ 
-                autopilot += ParseRegionCoordSpeed (ncline); 
-            } catch (exception e) { 
-                llShout (PUBLIC_CHANNEL,​ "​autopilot:"​ + nclino + ": " + xmrExceptionMessage (e)); 
-                notecardok = FALSE; 
-            } 
-        } 
-        aplistlen = llGetListLength (autopilot);​ 
-        if (!notecardok) aplistlen = 0; 
-    } 
- 
-    /* 
-     * Validate path.  It can't require turning sharper than turnrate allows. 
-     */ 
-    if (aplistlen > 0) { 
-        integer pathok = TRUE; 
- 
-        // start validating at last point 
-        curgcoord = GetRegionLocation ((string)autopilot[aplistlen-3]) + (vector)autopilot[aplistlen-2];​ 
-        for (pathindex = 0; pathindex <= aplistlen; pathindex += 3) { 
- 
-            // see if we can start at current point, head toward next point then turn toward follow-on point 
-            if (ComputePath ()) { 
-                // end of the arc is where we start for next segment 
-                curgcoord = endgcoord; 
-            } else { 
-                pathok = FALSE; 
-                curgcoord = nxtgcoord; 
-            } 
-        } 
-        if (!pathok) { 
-            llShout (PUBLIC_CHANNEL,​ "​either increase turn rate, decrease speed or lengthen segment"​);​ 
-            aplistlen = 0; 
-        } 
-    } 
- 
-    /* 
-     * Find certain linked prims. 
-     */ 
-    integer numberOfPrims = llGetObjectPrimCount (llGetKey ()); 
-    for (integer linknum = 0; ++ linknum <= numberOfPrims;​) { 
-        list pps = llGetLinkPrimitiveParams (linknum, [ PRIM_NAME, PRIM_POS_LOCAL,​ PRIM_ROT_LOCAL,​ PRIM_DESC ]); 
-        string ​  ​name ​ = (string) ​ pps[0]; 
-        vector ​  ccpos = (vector) ​ pps[1]; 
-        rotation ccrot = (rotation)pps[2];​ 
-        string ​  ​desc ​ = (string) ​ pps[3]; 
- 
-        // person who sits on this prim manually drives the ship 
-        if (name == "​CaptChair"​) { 
-            list     ​pr ​    = ParseChairDesc (desc); 
-            vector ​  ​sitpos = (vector) ​ pr[0]; 
-            rotation sitrot = (rotation)pr[1];​ 
- 
-            llShout (PUBLIC_CHANNEL,​ name + " at " + xmrVector2String (ccpos, "​0.0"​));​ 
-            llShout (PUBLIC_CHANNEL,​ " - sit: " + xmrVector2String (sitpos, ​ "​0.0"​) + " " + xmrRotation2String (sitrot, ​ "​0.000"​));​ 
- 
-            sitpos = sitpos * ccrot + ccpos; 
-            sitrot = ccrot  * sitrot; 
- 
-            llSetLinkPrimitiveParamsFast (linknum, [ PRIM_POS_LOCAL,​ ccpos, PRIM_ROT_LOCAL,​ ccrot ]); 
-            llSitTarget (sitpos, sitrot); 
- 
-            llUnSit (llAvatarOnSitTarget ()); 
-        } 
- 
-        // keep all these prims above ground level 
-        if (xmrStringStartsWith (name, "​GroundSensor"​)) { 
-            llShout (PUBLIC_CHANNEL,​ name + " at " + xmrVector2String (ccpos, "​0.0"​));​ 
-            groundSensorPrimPoss += ccpos; 
-        } 
- 
-        // we set the ship's Z value so that this prim is centered on the water line 
-        if (name == "​WaterLine"​) { 
-            llShout (PUBLIC_CHANNEL,​ name + " at " + xmrVector2String (ccpos, "​0.0"​));​ 
-            waterLinePrimPos = ccpos; 
-        } 
-    } 
- 
-    llShout (PUBLIC_CHANNEL,​ "​initialization complete"​);​ 
-    return TRUE; 
-} 
- 
- 
-list ParseRegionCoordSpeed (string ncline) 
-{ 
-    integer i = xmrStringIndexOf (ncline, "<"​);​ 
-    if (i < 0) throw "​missing < at beginning of coordinate";​ 
-    integer j = xmrStringIndexOf (ncline, ">",​ i); 
-    if (j < 0) throw "​missing > at end of coordinate";​ 
-    string region = llStringTrim (xmrSubstring (ncline, 0, i), STRING_TRIM);​ 
-    vector coord  = xmrString2Vector (xmrSubstring (ncline, i, ++ j - i)); 
-    float  speed  = xmrString2Float (xmrSubstring (ncline, j)) * KTSTOMPS; 
-    if (speed <= 0.0) throw "speed must be gt zero"; 
- 
-    return [ region, coord, speed ]; 
-} 
- 
- 
-/** 
- * @brief Parse the chair description string to get the offset position & rotation. 
- * 
- ​* ​  [sit] <​sitposition>​ [<​sitrotation>​] 
- */ 
-list ParseChairDesc (string desc) 
-{ 
-    integer i; 
-    integer j; 
-    rotation sitrot; 
-    vector sitpos = <​0.0,​0.0,​0.001>;​ 
- 
-    while ((i = xmrStringIndexOf (desc, " ")) >= 0) { 
-        desc = xmrSubstring (desc, 0, i) + xmrSubstring (desc, i + 1); 
-    } 
-    if (desc != ""​) { 
-        if (desc[0] == '<'​) { 
-            desc = "​sit"​ + desc; 
-        } 
-        list strings = llParseString2List (desc, [ ";"​ ], [ ]); 
-        for (integer k = llGetListLength (strings); -- k >= 0;) { 
-            desc = (string)strings[k];​ 
-            try { 
-                switch (xmrSubstring (desc, 0, 3)) { 
-                    case "​sit":​ { 
-                        i = xmrStringIndexOf (desc, "><"​);​ 
-                        if (i < 0) { 
-                            sitpos = xmrString2Vector (xmrSubstring ​ (desc, 3)); 
-                        } else { 
-                            sitpos = xmrString2Vector (xmrJSubstring (desc, 3, ++ i)); 
-                            sitrot = ParseRotation ​   (xmrSubstring ​ (desc, i)); 
-                        } 
-                        break; 
-                    } 
-                    default: throw "​unknown value " + xmrSubstring (desc, 0, 3); 
-                } 
-            } catch (exception e) { 
-                llShout (PUBLIC_CHANNEL,​ "error parsing chair description " + desc + ": " + xmrExceptionMessage (e)); 
-            } 
-        } 
-    } 
- 
-    return [ sitpos, sitrot ]; 
-} 
- 
-rotation ParseRotation (string s) 
-{ 
-    try { 
-        return llEuler2Rot (xmrString2Vector (s) * DEG_TO_RAD);​ 
-    } catch (exception e) { 
-        return xmrString2Rotation (s); 
-    } 
-} 
- 
- 
-/** 
- * @brief Ship is running on its own, following the path in the notecard. 
- ​* ​       No one is sitting in the captains chair. 
- */ 
-DoRunningState () 
-{ 
-    if (aplistlen > 0) { 
-        currentregion = llGetRegionName (); 
-        currentregpos = llGetPos (); 
-        waterLevel ​   = llWater (ZERO_VECTOR);​ 
-        FindClosestPoint (); 
-        mystate = MYST_RUNNING;​ 
-        while (TRUE) { 
-            xmrEventDequeue (timestep, 0, 0, -1, -1); 
-            if (mystate != MYST_RUNNING) break; 
-            PerformTimeStep (); 
-        } 
-    } else { 
-        mystate = MYST_STOPPED;​ 
-    } 
-} 
- 
- 
-/** 
- * @brief Ship is following the inputs given by the person in the captains chair. 
- */ 
-DoManualState () 
-{ 
-    integer ​ turning = 0; 
-    currentregion ​   = llGetRegionName (); 
-    currentregpos ​   = llGetPos (); 
-    waterLevel ​      = llWater (ZERO_VECTOR);​ 
-    rotation currot ​ = llGetRot (); 
-    float    heading = llRot2Euler (currot).z * RAD_TO_DEG; 
-    float    speed   = 0.0; 
-    mystate ​         = MYST_MANUAL;​ 
-    do { 
-        float endstep = llGetTime () + timestep; 
-        float waitime; 
-        while ((waitime = endstep - llGetTime ()) > 0.0) { 
-            list ev = xmrEventDequeue (waitime, XMREVENTMASK1_control,​ 0, -1, -1); 
- 
-            /* 
-             * control key received, update direction and/or speed. 
-             */ 
-            if ((llGetListLength (ev) != 0) &&&​ ((integer)ev[0] == XMREVENTCODE_control)) { 
-                key     ​id ​    = (key)ev[1]; 
-                integer held   = (integer)ev[2];​ 
-                integer change = (integer)ev[3];​ 
- 
-                if (id == seatedav) { 
-                    if (held & CONTROL_FWD) { 
-                        speed += 0.1; 
-                        if (speed > MAXMANSP) speed = MAXMANSP; 
-                    } 
-                    if (held & CONTROL_BACK) { 
-                        speed -= 0.1; 
-                        if (speed < 0.0) speed = 0.0; 
-                    } 
-                    if (held & (CONTROL_FWD | CONTROL_BACK)) { 
-                        llShout (PUBLIC_CHANNEL,​ "speed " + xmrFloat2String (speed, "​0.0"​) + " kts"); 
-                    } 
- 
-                    if (held & (CONTROL_LEFT | CONTROL_ROT_LEFT)) { 
-                        turning =  1; 
-                    } else if (held & (CONTROL_RIGHT | CONTROL_ROT_RIGHT)) { 
-                        turning = -1; 
-                    } else if (change & (CONTROL_LEFT | CONTROL_ROT_LEFT | CONTROL_RIGHT | CONTROL_ROT_RIGHT)) { 
-                        turning =  0; 
-                    } 
-                } 
-            } 
-        } 
- 
-        /* 
-         * timestep elapsed, step ship to new position. 
-         */ 
-        if (turning != 0) { 
-            heading ​ += turning * turnrate * timestep; 
-            float hdg = 90.0 - heading; 
-            while (hdg <=  0.0) hdg += 360.0; 
-            while (hdg > 360.0) hdg -= 360.0; 
-            llShout (PUBLIC_CHANNEL,​ "​heading " + xmrFloat2String (hdg, "​0.0"​));​ 
-        } 
- 
-        rotation currot = llEuler2Rot (<0.0, 0.0, heading * DEG_TO_RAD>​);​ 
-        currentregpos ​ += <speed * timestep * KTSTOMPS, 0, 0> * currot; 
-        if ((turning || speed) &&&​ SetNewPosRot (currentregpos,​ currot)) { 
-            llRequestPermissions (seatedav, PERMISSION_TAKE_CONTROLS);​ 
-        } 
-    } while (mystate == MYST_MANUAL);​ 
-} 
- 
- 
-/** 
- * @brief Find closest point along entire path to where we are right now. 
- * @returns pathindex = which path segment closest point is on 
- ​* ​         stepnumber = which step number along that path closest point is 
- ​* ​         ship moved to that closest point 
- */ 
-FindClosestPoint () 
-{ 
-    vector ​ nowgcoord ​   = GetRegionLocation (currentregion) + currentregpos;​ 
-    float   ​closestDist ​ = 999999999.0;​ 
-    integer closestIndex = -1; 
-    integer closestStep ​ = -1; 
-    vector ​ closestPoint;​ 
-    vector ​ closestStart;​ 
- 
-    curgcoord = GetRegionLocation ((string)autopilot[aplistlen-3]) + (vector)autopilot[aplistlen-2];​ 
-    for (pathindex = 0; pathindex <= aplistlen; pathindex += 3) { 
- 
-        // compute path from curgcoord -> autopilot[pathindex] 
-        ComputePath (); 
- 
-        // step through all points on that path 
-        for (integer step = 0; ++ step <= ntotsteps;) { 
-            vector point = ComputeStepPoint (step); 
-            float dist = llVecDist (point, nowgcoord); 
-            if (closestDist > dist) { 
-                closestDist ​ = dist; 
-                closestIndex = pathindex; 
-                closestStep ​ = step; 
-                closestPoint = point; 
-                closestStart = curgcoord; 
-            } 
-        } 
- 
-        // end of the arc is where we start for next segment 
-        curgcoord = endgcoord; 
-    } 
- 
-    /* 
-     * Set up vars such that it assumes we are at that point now. 
-     */ 
-    MoveShipTo (closestPoint);​ 
-    pathindex ​ = closestIndex;​ 
-    stepnumber = closestStep;​ 
- 
-    /* 
-     * Compute parameters for remainder of path. 
-     */ 
-    curgcoord ​ = closestStart;​ 
-    ComputePath (); 
-} 
- 
- 
-/** 
- * @brief Step the ship to next point along path. 
- ​* ​       If end of current path, start next one in loop. 
- */ 
-PerformTimeStep () 
-{ 
-    while (++ stepnumber > ntotsteps) { 
- 
-        /* 
-         * End of current path, increment on to next path. 
-         */ 
-        pathindex = (pathindex + 3) % aplistlen; 
- 
-        /* 
-         * See where we are right now. 
-         */ 
-        curgcoord = GetRegionLocation (currentregion) + currentregpos;​ 
- 
-        /* 
-         * Compute the path from current to next assuming we then go on to follow-up point. 
-         */ 
-        ComputePath (); 
- 
-        /* 
-         * Start processing the path. 
-         */ 
-        stepnumber = 0; 
-    } 
- 
-    /* 
-     * Compute next point along current path. 
-     */ 
-    vector thispos = ComputeStepPoint (stepnumber);​ 
-    MoveShipTo (thispos); 
-} 
- 
- 
-/** 
- * @brief Compute path from current point to next point. 
- ​* ​     Path consists of a linear segment and an arced segment. 
- * @returns TRUE: path was computed 
- ​* ​        ​FALSE:​ turn is too sharp 
- */ 
-integer pathindex; ​ // in:  where next point is in autopilot[] list 
-vector ​ curgcoord; ​ // in:  where ship is now 
- 
-float   ​nxtspeed; ​  // out: its speed to next point 
-float   ​startangle;​ // out: angle from arc center to begin point 
-float   ​turnradius;​ // out: arc radius 
-float   ​wholarcang;​ // out: angle of arc wedge 
-integer narcsteps; ​ // out: number of steps along arced portion 
-integer nlinsteps; ​ // out: number of steps along linear portion 
-integer ntotsteps; ​ // out: narcsteps+nlinsteps 
-vector ​ beggcoord; ​ // out: where arc begins 
-vector ​ ctrgcoord; ​ // out: where center of arc is 
-vector ​ endgcoord; ​ // out: where arc ends 
-vector ​ folgcoord; ​ // out: where ship will head after nxtgcoord 
-vector ​ nxtgcoord; ​ // out: where ship is headed to next 
- 
-integer ComputePath () 
-{ 
-    // get next point along path 
-    string nxtregion = (string)autopilot[(pathindex+0)%aplistlen];​ 
-    vector nxtrcoord = (vector)autopilot[(pathindex+1)%aplistlen];​ 
-           ​nxtspeed ​ = (float) autopilot[(pathindex+2)%aplistlen];​ 
-           ​nxtgcoord = GetRegionLocation (nxtregion) + nxtrcoord; 
- 
-    // get point following the next point along path 
-    string folregion = (string)autopilot[(pathindex+3)%aplistlen];​ 
-    vector folrcoord = (vector)autopilot[(pathindex+4)%aplistlen];​ 
-           ​folgcoord = GetRegionLocation (folregion) + folrcoord; 
- 
-    /* 
-     * Compute turn radius at standard rate for our speed. 
-     */ 
-    turnradius = nxtspeed / turnrate / DEG_TO_RAD; 
- 
-    /* 
-     * Use turn radius to round the corner at next position when heading on way to follow-on position. 
-     * 
-     ​* ​            fol 
-     ​* ​             / 
-     ​* ​            / 
-     ​* ​           / 
-     ​* ​          / 
-     ​* ​         / 
-     ​* ​        / 
-     ​* ​      end 
-     ​* ​      /. 
-     ​* ​     / . 
-     ​* ​    / ​  ​. ​  ctr 
-     ​* ​   /     . 
-     ​* ​  / th    . 
-     ​* ​ nxt--------beg----------cur 
-     */ 
-    float hdgnxt2cur = llAtan2 (curgcoord.y - nxtgcoord.y,​ curgcoord.x - nxtgcoord.x);​ 
-    float hdgnxt2fol = llAtan2 (folgcoord.y - nxtgcoord.y,​ folgcoord.x - nxtgcoord.x);​ 
-    float theta   = hdgnxt2fol - hdgnxt2cur; 
-    while (theta >= PI) theta -= TWO_PI; 
-    while (theta < -PI) theta += TWO_PI; 
- 
-    /* 
-     * Compute point between cur and nxt where turning arc begins. 
-     */ 
-    float nxt2begdist = llFabs (turnradius / llTan (theta / 2.0)); 
-    beggcoord ​        = nxtgcoord; 
-    beggcoord.x ​   += llCos (hdgnxt2cur) * nxt2begdist;​ 
-    beggcoord.y ​   += llSin (hdgnxt2cur) * nxt2begdist;​ 
- 
-    /* 
-     * Compute how many steps are along the linear portion. 
-     * If negative, turn is too sharp. 
-     */ 
-    float  nxt2curdist = XYDist (curgcoord - nxtgcoord); 
-    float  cur2begdist = nxt2curdist - nxt2begdist;​ 
-    if (cur2begdist < 0.0) { 
-        llShout (PUBLIC_CHANNEL,​ "turn at " + nxtregion + " " + xmrVector2String (nxtrcoord, ""​) + " is too sharp"​);​ 
-        if (verbose >= 2) { 
-            llShout (PUBLIC_CHANNEL,​ "​curgcoord="​ + curgcoord); 
-            llShout (PUBLIC_CHANNEL,​ "​nxtgcoord="​ + nxtgcoord); 
-            llShout (PUBLIC_CHANNEL,​ "​folgcoord="​ + folgcoord); 
-            llShout (PUBLIC_CHANNEL,​ "​beggcoord="​ + beggcoord); 
-            llShout (PUBLIC_CHANNEL,​ "​nxt2curdist="​ + nxt2curdist);​ 
-            llShout (PUBLIC_CHANNEL,​ "​cur2begdist="​ + cur2begdist);​ 
-        } 
-        return FALSE; 
-    } 
-    nlinsteps ​        = llCeil (cur2begdist / nxtspeed / timestep); 
- 
-    /* 
-     * Compute point between nxt and fol where turning arc ends. 
-     */ 
-    float nxt2foldist = XYDist (folgcoord - nxtgcoord); 
-    endgcoord ​        = (folgcoord - nxtgcoord) * nxt2begdist / nxt2foldist + nxtgcoord; 
- 
-    /* 
-     * Compute centerpoint of turning arc. 
-     */ 
-    float  beg2curx ​ = curgcoord.x - beggcoord.x;​ 
-    float  beg2cury ​ = curgcoord.y - beggcoord.y;​ 
-    float  beg2ctrx ​ = -turnradius * beg2cury / llSqrt (beg2curx * beg2curx + beg2cury * beg2cury); 
-    float  beg2ctry ​ =  turnradius * beg2curx / llSqrt (beg2curx * beg2curx + beg2cury * beg2cury); 
-    ctrgcoord ​       = beggcoord; 
-    if (theta < 0.0) { 
-        // turning left 
-        ctrgcoord.x -= beg2ctrx; 
-        ctrgcoord.y -= beg2ctry; 
-    } else { 
-        // turning right 
-        ctrgcoord.x += beg2ctrx; 
-        ctrgcoord.y += beg2ctry; 
-    } 
- 
-    /* 
-     * Compute number of steps through the arc. 
-     */ 
-    wholarcang = PI - llFabs (theta); 
-    narcsteps ​ = llCeil (wholarcang / turnrate / DEG_TO_RAD / timestep); 
-    ntotsteps ​ = nlinsteps + narcsteps; 
-    if (theta >= 0.0) wholarcang = -wholarcang;​ 
- 
-    /* 
-     * Compute angle from centerpoint to begin point. 
-     */ 
-    startangle = llAtan2 (beggcoord.y - ctrgcoord.y,​ beggcoord.x - ctrgcoord.x);​ 
- 
-    if (verbose >= 2) { 
-        llShout (PUBLIC_CHANNEL,​ " pathindex="​ + pathindex); 
-        llShout (PUBLIC_CHANNEL,​ " curgcoord="​ + curgcoord); 
-        llShout (PUBLIC_CHANNEL,​ " ​ nxtspeed="​ + nxtspeed); 
-        llShout (PUBLIC_CHANNEL,​ "​startangle="​ + (startangle * RAD_TO_DEG));​ 
-        llShout (PUBLIC_CHANNEL,​ "​turnradius="​ + turnradius);​ 
-        llShout (PUBLIC_CHANNEL,​ "​wholarcang="​ + (wholarcang * RAD_TO_DEG));​ 
-        llShout (PUBLIC_CHANNEL,​ " narcsteps="​ + narcsteps); 
-        llShout (PUBLIC_CHANNEL,​ " nlinsteps="​ + nlinsteps); 
-        llShout (PUBLIC_CHANNEL,​ " ntotsteps="​ + ntotsteps); 
-        llShout (PUBLIC_CHANNEL,​ " beggcoord="​ + beggcoord); 
-        llShout (PUBLIC_CHANNEL,​ " ctrgcoord="​ + ctrgcoord); 
-        llShout (PUBLIC_CHANNEL,​ " endgcoord="​ + endgcoord); 
-        llShout (PUBLIC_CHANNEL,​ " folgcoord="​ + folgcoord); 
-        llShout (PUBLIC_CHANNEL,​ " nxtgcoord="​ + nxtgcoord); 
-    } 
- 
-    return TRUE; 
-} 
- 
- 
-/** 
- * @brief Compute a point along the current path segment 
- ​* ​       as set up by last call to ComputePath(). 
- * @param step = which point along the path to compute (0..ntotsteps) 
- * @returns point along path 
- */ 
-vector ComputeStepPoint (integer step) 
-{ 
-    if (step <= nlinsteps) { 
-        return <​curgcoord.x + (beggcoord.x - curgcoord.x) * ((float)step / (float)nlinsteps),​ 
-                curgcoord.y + (beggcoord.y - curgcoord.y) * ((float)step / (float)nlinsteps),​ 
-                curgcoord.z + (endgcoord.z - curgcoord.z) * ((float)step / (float)ntotsteps)>;​ 
-    } 
- 
-    float thisang = startangle + wholarcang * (float)(step - nlinsteps) / (float)narcsteps;​ 
-    return <​ctrgcoord.x + llCos (thisang) * turnradius, 
-            ctrgcoord.y + llSin (thisang) * turnradius, 
-            curgcoord.z + (endgcoord.z - curgcoord.z) * ((float)step / (float)ntotsteps)>;​ 
-} 
- 
- 
-/** 
- * @brief Move ship to the given global position from where it is now in one jump. 
- ​* ​     Turn the ship's heading from current position to point to new position. 
- ​* ​     Note that the ship may cross regions doing so. 
- * @param newgcoord = new position global coordinate 
- */ 
-MoveShipTo (vector newgcoord) 
-{ 
-    string curregion = currentregion;​ 
-    vector currcoord = currentregpos;​ 
-    vector curgcoord = GetRegionLocation (curregion) + currcoord; 
- 
-    vector delta = newgcoord - curgcoord; 
-    if (llVecMag (delta) > 0.001) { 
-        float heading = llAtan2 (delta.y, delta.x); 
-        rotation rot  = llEuler2Rot (<0.0, 0.0, heading>​);​ 
-        SetNewPosRot (currcoord + delta, rot); 
-    } 
-} 
- 
- 
-/** 
- * @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) 
-{ 
-    /* 
-     * Force Z value to put WaterLevel prim at sim's water line. 
-     */ 
-    pos.z = waterLevel - waterLinePrimPos.z;​ 
- 
-    /* 
-     * Maybe print out new co-ordinate. 
-     */ 
-    if (verbose >= 1) { 
-        integer thisPosReport = llGetUnixTime (); 
-        if (lastPosReport < thisPosReport) { 
-            float hdg = llRot2Euler (rot).z * RAD_TO_DEG; 
-            while (hdg <=  0.0) hdg += 360.0; 
-            while (hdg > 360.0) hdg -= 360.0; 
-            llShout (PUBLIC_CHANNEL,​ "​moving to " + currentregion + " " + xmrVector2String (pos, "​0.0"​) + 
-                    " via hdg " + xmrFloat2String (hdg, "​000.0"​));​ 
-            lastPosReport = thisPosReport;​ 
-        } 
-    } 
- 
-    /* 
-     * See if any of the GroundSensor prims would be below ground. 
-     * Also warn if they would be near ground. 
-     */ 
-    integer alarm = FALSE; 
-    for (integer i = llGetListLength (groundSensorPrimPoss);​ -- i >= 0;) { 
-        vector primlocalpos = (vector)groundSensorPrimPoss[i];​ 
-        float primz = pos.z + primlocalpos.z;​ 
-        float groundheight = llGround (primlocalpos);​ 
-        if (primz <= groundheight) { 
-            llShout (PUBLIC_CHANNEL,​ "SHIP RUN AGROUND!!"​);​ 
-            llShout (PUBLIC_CHANNEL,​ "​Stopped... touch to start"​);​ 
-            mystate = MYST_STOPPED;​ 
-            return FALSE; 
-        } 
-        alarm |= (integer)(primz <= groundheight + groundproximitywarningheight);​ 
-    } 
-    if (alarm) { 
-        llShout (PUBLIC_CHANNEL,​ "​Ground proximity warning"​);​ 
-    } 
- 
-    /* 
-     * Put ship at new position. 
-     */ 
-    currentregpos = pos; 
-    integer async = xmrSetObjRegPosRotAsync (pos, rot, XMRSORPRA_FLYACROSS,​ XMREVENTCODE_at_target,​ []); 
-    if (async) { 
-        if (numSeated > 0) llShout (PUBLIC_CHANNEL,​ "HANG ON EVERYONE!!!"​);​ 
- 
-        /* 
-         * Sim crossing involved, wait for it to complete. 
-         */ 
-        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,​ XMREVENTMASK2_changed,​ 0, 0); 
-            if (llGetListLength (ev) == 0) { 
-                llShout (PUBLIC_CHANNEL,​ "timed out trying to cross sim border"​);​ 
-                llResetScript (); 
-            } 
-            evcode = (integer)ev[0];​ 
-        } while (evcode != XMREVENTCODE_at_target);​ 
- 
-        /* 
-         * See who is still seated. 
-         */ 
-        CountNumSeated (); 
- 
-        /* 
-         * Get new region name and position within the new region. 
-         */ 
-        currentregion = llGetRegionName (); 
-        currentregpos = llGetPos (); 
-        waterLevel ​   = llWater (ZERO_VECTOR);​ 
-        llShout (PUBLIC_CHANNEL,​ "​crossed over into region " + currentregion + " at " + xmrVector2String (currentregpos,​ "​0.0"​));​ 
-    } 
-    return async; 
-} 
- 
- 
-/** 
- * @brief Count total number of seated avatars. 
- */ 
-CountNumSeated () 
-{ 
-    integer numberOfPrims = llGetObjectPrimCount (llGetKey ()); 
-    numSeated = 0; 
-    for (integer linknum = 0; ++ linknum <= numberOfPrims;​) { 
-        numSeated += (llAvatarOnLinkSitTarget (linknum) != NULL_KEY); 
-    } 
-} 
- 
- 
-/** 
- * @brief Read a line from a notecard. 
- */ 
-string ReadNotecardLine (string notecard, integer linenum) 
-{ 
-    if (canUseOsGetNotecardLine) { 
-        try { 
-            return osGetNotecardLine (notecard, linenum); 
-        } catch (exception e) { 
-            llShout (PUBLIC_CHANNEL,​ "​Can'​t use osGetNotecardLine:​ " + xmrExceptionMessage (e)); 
-            llShout (PUBLIC_CHANNEL,​ "Using slower llGetNotecardLine instead"​);​ 
-            canUseOsGetNotecardLine = FALSE; 
-        } 
-    } 
-    key lineid = llGetNotecardLine (notecard, linenum); 
-    list event = xmrEventDequeue (10.0, XMREVENTMASK1_dataserver,​ 0, 0, 0); 
-    if ((integer)event[0] != XMREVENTCODE_dataserver) throw "bad event " + (integer)event[0];​ 
-    if ((key)event[1] != lineid) throw "bad ident " + (key)event[1];​ 
-    return (string)event[2];​ 
-} 
- 
- 
-/** 
- * @brief Get region'​s global coordinates. 
- */ 
-vector GetRegionLocation (string regionname) 
-{ 
-    if (regionname == ""​) regionname = llGetRegionName (); 
- 
-    if (regionpositions[regionname] != undef) { 
-        return (vector)regionpositions[regionname];​ 
-    } 
-    try { 
-        key reqid = llRequestSimulatorData (regionname,​ DATA_SIM_POS);​ 
-        list event = xmrEventDequeue (10.0, XMREVENTMASK1_dataserver,​ 0, 0, 0); 
-        if ((integer)event[0] != XMREVENTCODE_dataserver) throw "bad event " + (integer)event[0];​ 
-        if ((key)event[1] != reqid) throw "bad ident " + (key)event[1];​ 
-        vector gblpos = xmrString2Vector ((string)event[2]);​ 
-        llShout (PUBLIC_CHANNEL,​ "​region " + regionname + " at " + xmrVector2String (gblpos, ""​));​ 
-        regionpositions[regionname] = gblpos; 
-        return gblpos; 
-    } catch (exception e) { 
-        llShout (PUBLIC_CHANNEL,​ "error fetching " + regionname + " position: " + xmrExceptionMessage (e)); 
-        return ZERO_VECTOR;​ 
-    } 
-} 
- 
- 
-/** 
- * @brief Compute length using only X & Y components. 
- */ 
-float XYDist (vector delta) 
-{ 
-    return llSqrt (delta.x * delta.x + delta.y * delta.y); 
-} 
- 
-</​code>​ 
-===== Region Cross Event ===== 
-simpler (than flight 2) use of llSetVehicle...() crossing sim boundary 
-<code ossl> 
-//​regioncrossevent 
- 
-// Demo for the region_cross() event 
-// adapted by Kunta Kinte of www.dreamnation.net July 24, 2013 
-// ...from the Flight Script as described below 
- 
-//Flight Script - Andrew Linden ​ 
-// Simple airplane script example 
-  
-// THIS SCRIPT IS PUBLIC DOMAIN! Do not delete the credits at the top of this script! 
-  
-// Nov 25, 2003 - created by Andrew Linden and posted in the Second Life scripting forum 
-// Jan 05, 2004 - Cubey Terra - minor changes: customized controls, added enable/​disable physics events 
-// Feel free to copy, modify, and use this script. 
-  
-// Always give credit to Andrew Linden and all people who modify it in a readme or in the object description. 
-  
-yoption advflowctl; 
-yoption norighttoleft;​ 
-yoption trycatch; 
- 
-integer sorpraInProg = 0; 
-rotation sorpraRotation;​ 
-vector motionvec = <​0,​0,​0>;​ 
- 
-default 
-{ 
-    state_entry() 
-    { 
-        DebPrint ("​initializing"​);​ 
-        ​ 
-        llSetSitText("​Fly"​);​ 
-        llCollisionSound("",​ 0.0); 
-  
-        // the sit and camera placement is very shape dependent 
-        // so modify these to suit your vehicle 
-        llSitTarget(<​0.3,​ 0.0, 0.5>, ZERO_ROTATION);​ 
-        llSetCameraEyeOffset(<​-10.0,​ 0.0, 2.0> ); 
-        llSetCameraAtOffset(<​3.0,​ 0.0, 1.0> ); 
- 
-        Initialize (); 
-  
-        llSetStatus (STATUS_PHYSICS,​ FALSE); 
-        SetRotFast (ZERO_ROTATION);​ 
- 
-        DebPrint ("​initialized,​ say N S E W to move, H to halt, R to reset"​);​ 
-        llListen (0, "",​ llGetOwner (), ""​);​ 
-    } 
- 
-    listen (integer channel, string name, key id, string message) 
-    { 
-        if (message == "​R"​) llResetScript (); 
- 
-        if (sorpraInProg) return; 
- 
-        integer wasrunning = (motionvec != ZERO_VECTOR);​ 
-        if (message == "​H"​) motionvec = ZERO_VECTOR;​ 
-        if (message == "​N"​) motionvec = <0, 1,0>; 
-        if (message == "​S"​) motionvec = <​0,​-1,​0>;​ 
-        if (message == "​E"​) motionvec = < 1,0,0>; 
-        if (message == "​W"​) motionvec = <​-1,​0,​0>;​ 
-        integer nowrunning = (motionvec != ZERO_VECTOR);​ 
- 
-        if (nowrunning != wasrunning) { 
-            if (nowrunning) { 
- 
-                llSetStatus(STATUS_PHYSICS,​ TRUE); 
- 
-                llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION,​ ZERO_VECTOR);​ 
- 
-                llSetVehicleFloatParam(VEHICLE_LINEAR_FRICTION_TIMESCALE,​ 1000.0); 
-                llSetVehicleFloatParam(VEHICLE_ANGULAR_FRICTION_TIMESCALE,​ 1000.0); 
- 
-                // before setting the object in motion, 
-                // block any attempted region crossing 
-                // and post a region_cross() event 
-                xmrTrapRegionCrossing (TRUE); 
- 
-                // now it's safe to put the object in motion 
-                // because we are armed to catch any attempted 
-                // region crossing 
-                UpdateLinearMotor (); 
- 
-                // if we tumble abount, make sure our motion is aligned 
-                // with the basic compass direction described by motionvec 
-                llSetTimerEvent (1.0); 
-            } else { 
- 
-                llSetTimerEvent (0.0); 
-                xmrTrapRegionCrossing (FALSE); 
- 
-                // stop the motors 
-                motionvec = ZERO_VECTOR;​ 
-                UpdateLinearMotor (); 
-                llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION,​ ZERO_VECTOR);​ 
-  
-                // use friction to stop the vehicle rather than pinning it in place 
-                llSetVehicleFloatParam(VEHICLE_LINEAR_FRICTION_TIMESCALE,​ 1.0); 
-                llSetVehicleFloatParam(VEHICLE_ANGULAR_FRICTION_TIMESCALE,​ 1.0); 
-  
-                llSetStatus (STATUS_PHYSICS,​ FALSE); 
-            } 
-        } 
-    } 
- 
-    // Called once per second whenever someone is seated on object. 
-    // Normally, it realigns the linear motor vector to point in motionvec'​s direction. 
-    // But if we are busy moving object across region boundary, it just checks for timeout. 
-    timer () 
-    { 
-        // if we re not in the middle of crossing a region boundary, 
-        //     make sure our direction is aligned with compass direction 
-        //     given by motionvec 
-        // else if we have waited too long to cross region boundary, 
-        //     eg, maybe our seated avatar unseated or logged out or is stuck or region is down, 
-        //     shout out an error message and reset the script 
-        if (sorpraInProg == 0) { 
-            UpdateLinearMotor (); 
-        } else if (-- sorpraInProg == 0) { 
-            DebPrint ("​xmrSetObjRegPosRotAsync timed out!!"​);​ 
-            llResetScript (); 
-        } 
-    } 
- 
-    // Called when the object is about to cross a region boundary 
-    //   ​newpos = the position outside the current region it is trying to move to 
-    //   ​oldpos = the position inside the current region near the border 
-    // Physics have already been turned off by the server at this point 
-    region_cross (vector newpos, vector oldpos) 
-    { 
-        // oldpos = inside current region (ie, 0 <= x < 256 and 0 <= y < 256) 
-        // newpos = outside current region (ie, x < 0 || x >= 256 || y < 0 || y >= 256) 
-        DebPrint ("​region_cross:​ " + oldpos + " => " + newpos); 
- 
-        // should always print 0 
-        DebPrint ("​region_cross:​ physics="​ + llGetStatus (STATUS_PHYSICS));​ 
- 
-        // we want to allow xmrSetObjRegPosRotAsync() to move the object across the boundary 
-        // without it being blocked or posting a region_cross() event 
-        xmrTrapRegionCrossing (FALSE); 
- 
-        // get current object rotation 
-        sorpraRotation = llGetRot (); 
- 
-        // set the object rotation to identity so the avatar and object can be pushed across 
-        // region boundary without blocking each other 
-        SetRotFast (ZERO_ROTATION);​ 
- 
-        // start moving the avatar and object across the region boundary 
-        sorpraInProg = xmrSetObjRegPosRotAsync (newpos, ZERO_ROTATION,​ XMRSORPRA_FLYACROSS,​ XMREVENTCODE_at_target,​ [ ]); 
-        if (sorpraInProg == 0) throw "​xmrSetObjRegPosRotAsync returned zero"; 
- 
-        // we will wait up to 30 seconds for server to move everything and everyone to new region 
-        sorpraInProg = 30; 
-    } 
- 
-    // Called when object and all seated avatars are in the new region 
-    // at the position passed to xmrSetObjRegPosRotAsync() 
-    at_target () 
-    { 
-        // the xmrSetObjRegPosRotAsync() call has completed 
-        sorpraInProg = 0; 
- 
-        // rotate the object back into its position 
-        SetRotFast (sorpraRotation);​ 
- 
-        // load up all the llSetVehicle...() parameters 
-        Initialize (); 
- 
-        // trap any future attempt to cross out of the new region 
-        xmrTrapRegionCrossing (TRUE); 
- 
-        // resume vehicle motion in the new region 
-        UpdateLinearMotor (); 
-    } 
- 
-    on_rez () 
-    { 
-        llResetScript (); 
-    } 
-} 
- 
-Initialize () 
-{ 
-    // object must be physical and not phantom for the llSetVehicle...() functions to do anything 
-    llSetStatus (STATUS_PHANTOM,​ FALSE); 
-    llSetStatus (STATUS_PHYSICS,​ TRUE); 
-  
-    llSetVehicleType(VEHICLE_TYPE_AIRPLANE);​ 
-  
-    // weak angular deflection 
-    llSetVehicleFloatParam(VEHICLE_ANGULAR_DEFLECTION_EFFICIENCY,​ 0.1); 
-    llSetVehicleFloatParam(VEHICLE_ANGULAR_DEFLECTION_TIMESCALE,​ 1.0); 
-  
-    // strong linear deflection 
-    llSetVehicleFloatParam(VEHICLE_LINEAR_DEFLECTION_EFFICIENCY,​ 1.0); 
-    llSetVehicleFloatParam(VEHICLE_LINEAR_DEFLECTION_TIMESCALE,​ 0.2); 
-  
-    // somewhat responsive linear motor 
-    llSetVehicleFloatParam(VEHICLE_LINEAR_MOTOR_TIMESCALE,​ 0.5); 
-    llSetVehicleFloatParam(VEHICLE_LINEAR_MOTOR_DECAY_TIMESCALE,​ 20); 
-  
-    // somewhat responsive angular motor, but with 3 second decay timescale 
-    llSetVehicleFloatParam(VEHICLE_ANGULAR_MOTOR_TIMESCALE,​ 0.5); 
-    llSetVehicleFloatParam(VEHICLE_ANGULAR_MOTOR_DECAY_TIMESCALE,​ 3); 
-  
-    // very weak friction 
-    //​llSetVehicleVectorParam(VEHICLE_LINEAR_FRICTION_TIMESCALE,​ <1000.0, 1000.0, 1000.0> ); // CUBEY - original line 
-    llSetVehicleVectorParam( VEHICLE_LINEAR_FRICTION_TIMESCALE,​ <200, 20, 20> ); // CUBEY - increased friction 
-    llSetVehicleVectorParam(VEHICLE_ANGULAR_FRICTION_TIMESCALE,​ <1000.0, 1000.0, 1000.0> ); 
-  
-    llSetVehicleFloatParam(VEHICLE_VERTICAL_ATTRACTION_EFFICIENCY,​ 0.65); ​ // almost wobbly - CUBEY - increased from .25 to improve stability 
-    llSetVehicleFloatParam(VEHICLE_VERTICAL_ATTRACTION_TIMESCALE,​ 1.5);    // mediocre response 
-  
-    llSetVehicleFloatParam(VEHICLE_BANKING_EFFICIENCY,​ 0.4);    // medium strength 
-    llSetVehicleFloatParam(VEHICLE_BANKING_TIMESCALE,​ 0.1);     // very responsive 
-    llSetVehicleFloatParam(VEHICLE_BANKING_MIX,​ 0.95); ​         // more banking when moving 
-  
-    // hover can be better than sliding along the ground during takeoff and landing 
-    // but it only works over the terrain (not objects) 
-    //​llSetVehicleFloatParam(VEHICLE_HOVER_HEIGHT,​ 3.0); 
-    //​llSetVehicleFloatParam(VEHICLE_HOVER_EFFICIENCY,​ 0.5); 
-    //​llSetVehicleFloatParam(VEHICLE_HOVER_TIMESCALE,​ 2.0); 
-    //​llSetVehicleFlags(VEHICLE_FLAG_HOVER_UP_ONLY);​ 
-  
-    // non-zero buoyancy helps the airplane stay up 
-    // set to zero if you don't want this crutch 
-    llSetVehicleFloatParam(VEHICLE_BUOYANCY,​ 0.2); 
-} 
- 
-// We tend to tumble when on the ground 
-// So set motion direction to where we want to go 
-// assuming motionvec is a general compass direciton 
-UpdateLinearMotor () 
-{ 
-    rotation rot = llGetRot (); 
-    vector motor = motionvec / rot; 
-    llSetVehicleVectorParam (VEHICLE_LINEAR_MOTOR_DIRECTION,​ motor); 
-} 
- 
-// Just like llSetRot() but no 0.2 second delay 
-SetRotFast (rotation rot) 
-{ 
-    llSetLinkPrimitiveParamsFast (LINK_ROOT, [ PRIM_ROT_LOCAL,​ rot ]); 
-} 
- 
-DebPrint (string msg) 
-{ 
-    llOwnerSay (msg); 
-    //print (msg); 
-} 
- 
-</​code>​ 
-===== Rummy Board ===== 
-rummy card game (lots of OOP usage and other language extensions) 
-<code ossl> 
-// rummyboard 
-// - main script for the RUMMY card game 
-// - resides in the RESET button 
- 
-//    Copyright (C) 2013, Kunta Kinte of www.dreamnation.net 
-// 
-//    This program is free software; you can redistribute it and/or modify 
-//    it under the terms of the GNU General Public License as published by 
-//    the Free Software Foundation; version 2 of the License. 
-// 
-//    This program is distributed in the hope that it will be useful, 
-//    but WITHOUT ANY WARRANTY; without even the implied warranty of 
-//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ​ See the 
-//    GNU General Public License for more details. 
-// 
-//    You should have received a copy of the GNU General Public License 
-//    along with this program; if not, write to the Free Software 
-//    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 ​ USA 
- 
-/* 
- * The RummyBoard consists of these components: 
- ​* ​  ​PlayerStatus<​0..5>​ 
- ​* ​  ​Discard 
- ​* ​  ​ResetButton 
- ​* ​  Stock 
- ​* ​  ​StartButton 
- ​* ​  ​HelpButton 
- ​* ​  ​MeldPanel<​0..15>​ 
- ​* ​  ​MeldCardPanel<​0..15><​0..3>​ 
- * The ResetButton contains this script. ​ Each of 
- * the others has an instance of cardpanel.lsl 
- * which simply checks in with us on startup. 
- * The cardpanel.lsl script will also send us a 
- * message whenever an avatar touches it.  And 
- * we can also set its texture and color by 
- * sending it messages. 
- * 
- * The RummyHUD consists of 20 instances of 
- ​* ​  ​PlayerCardPanel<​0..19>​ 
- * which are also cardpanel.lsl scripts. ​ This 
- * script controls everything seen on the panel 
- * and processes all the clicks. 
- */ 
- 
-yoption arrays; 
-yoption advflowctl; 
-yoption chars; 
-yoption norighttoleft;​ 
-yoption objects; 
-yoption trycatch; 
- 
- 
-constant RUMMY_CHANNEL = -646830961; 
- 
-constant canDiscardDraw = 1;  // 0: discard cannot be same card drawn from stock or discard pile 
-                              // 1: discard can be same card drawn from stock or discard pile 
- 
-constant mustDiscardOut = 0;  // 0: melding can leave zero cards in player'​s hand 
-                              // 1: melding must leave at least one card in player'​s hand 
- 
-integer fontsize = 14; 
- 
-array allCardPanels; ​   // key     ​uuid ​  -> CardPanel 
-array melds; ​           // integer index  -> Meld 
-array playersByAvUUID; ​ // key     ​uuid ​  -> Player 
-array playersByIndex; ​  // integer index  -> PlayerStatus 
-Player currentPlayer;​ 
-integer phaseDrawing;​ 
-integer pingPlayerIndex;​ 
-list sortedCards = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,​10,​11,​12,​13,​14,​15,​16,​17,​18,​19,​20,​21,​22,​23,​24,​25,​ 
-                    26,​27,​28,​29,​30,​31,​32,​33,​34,​35,​36,​37,​38,​39,​40,​41,​42,​43,​44,​45,​46,​47,​48,​49,​50,​51];​ 
- 
-//                                     A 2 3 4 5 6 7 8 9 10 J  Q  K 
-integer[] cardpoints = new integer[] { 1,​2,​3,​4,​5,​6,​7,​8,​9,​10,​10,​10,​10 }; 
- 
-  
-default { 
-    state_entry () 
-    { 
-        llSay (PUBLIC_CHANNEL,​ "RUMMY Game v1.0.2 -- Ready to play!"​);​ 
-        llSay (PUBLIC_CHANNEL,​ "Click Help button for instructions."​);​ 
- 
-        allCardPanels.clear (); 
-        melds.clear (); 
-        pingPlayerIndex = 0; 
-        playersByAvUUID.clear (); 
-        playersByIndex.clear (); 
-        currentPlayer = undef; 
-        phaseDrawing = 0; 
- 
-        /* 
-         * Start listening for incoming messages. 
-         */ 
-        llListen (RUMMY_CHANNEL,​ "",​ "",​ ""​);​ 
- 
-        /* 
-         * Tell anyone who cares that we have restarted. 
-         * Any objects must now re-register because we have forgotton about everything. 
-         */ 
-        llRegionSay (RUMMY_CHANNEL,​ "​rstr"​);​ 
- 
-        /* 
-         * Ping timer to make sure card panels are still here. 
-         */ 
-        llSetTimerEvent (5); 
- 
-        /* 
-         * Draw RESET string on this button. 
-         */ 
-        string textextras = "​width:​128,​height:​64";​ 
-        string data = "";​ 
-        data = osSetFontSize (data, fontsize); 
-        data = osDrawText (data, "​RESET"​);​ 
-        osSetDynamicTextureData ("",​ "​vector",​ data, textextras, 0); 
-    } 
- 
-    timer () 
-    { 
-        if (playersByAvUUID.count > 0) { 
-            if (pingPlayerIndex >= playersByAvUUID.count) { 
-                pingPlayerIndex = 0; 
-            } 
-            Player p = (Player)playersByAvUUID.value (pingPlayerIndex);​ 
-            if (p.cardPanels.count > 0) { 
-                PlayerCardPanel pcp = (PlayerCardPanel)p.cardPanels.value (0); 
-                if (++ pcp.unansweredPings > 2) { 
-                    pcp.Detached (); 
-                    return; 
-                } 
-                llRegionSayTo (pcp.id, RUMMY_CHANNEL,​ "​ping"​);​ 
-            } 
-            pingPlayerIndex ++; 
-        } 
-    } 
- 
-    listen (integer channel, string name, key id, string message) 
-    { 
-        switch (xmrSubstring (message, 0, 4)) { 
- 
-            /* 
-             * Card panel was touched. 
-             ​* ​  ​CPTC<​avataruuidthattouchedcardpanel>​ 
-             */ 
-            case "​CPTC":​ { 
-                key byavuuid = xmrSubstring (message, 4); 
-                CardPanel cp = (CardPanel)allCardPanels[id];​ 
-                if (cp == undef) { 
-                    llOwnerSay ("​unknown cardpanel " + id + " " + name); 
-                } else { 
-                    cp.unansweredPings = 0; 
-                    cp.Touched (byavuuid); 
-                } 
-                break; 
-            } 
- 
-            case "​DTCH":​ { 
-                CardPanel cp = (CardPanel)allCardPanels[id];​ 
-                if (cp != undef) { 
-                    cp.Detached (); 
-                } 
-                break; 
-            } 
- 
-            /* 
-             * Responding to a ping. 
-             */ 
-            case "​PONG":​ { 
-                CardPanel cp = (CardPanel)allCardPanels[id];​ 
-                if (cp != undef) { 
-                    cp.unansweredPings = 0; 
-                } 
-                break; 
-            } 
- 
-            /* 
-             * Response for RESET button confirmation dialog. 
-             */ 
-            case "​Rese":​ { 
-                if (message == "​Reset"​) { 
-                    llSay (PUBLIC_CHANNEL,​ name + " has reset the game..."​);​ 
-                    llResetScript (); 
-                } 
-                return; 
-            } 
- 
-            /* 
-             * Response to dialog indicating the user wants one of our HUDs. 
-             */ 
-            case "I ne": { 
-                if (message == "I need HUD") { 
-                    llGiveInventory (id, "​RummyHUD"​);​ 
-                } 
-                break; 
-            } 
- 
-            /* 
-             * Register a card panel. 
-             ​* ​  ​RGCP<​owneruuid>​ 
-             ​* ​    ​name=MeldCardPanel<​meldno><​cardno>​ 
-             ​* ​    ​name=PlayerCardPanel<​index>​ 
-             ​* ​    ​name=PlayerStatus<​playerno>​ 
-             */ 
-            case "​RGCP":​ { 
-                CardPanel cp = (CardPanel)allCardPanels[id];​ 
-                if (cp == undef) { 
-                    if (name == "​Discard"​) { 
-                        cp = DiscardCardPanel.Construct (id); 
-                    } else if (name == "​HelpButton"​) { 
-                        cp = HelpButton.Construct (id); 
-                    } else if (xmrSubstring (name, 0, 13) == "​MeldCardPanel"​) { 
-                        integer code   = (integer)xmrSubstring (name, 13); 
-                        integer meldno = code / 10;  // -> 0..15 
-                        integer cardno = code % 10;  // -> 0..3 
-                        Meld meld = Meld.Construct ("",​ meldno); 
-                        cp = MeldCardPanel.Construct (id, meld, cardno); 
-                    } else if (xmrSubstring (name, 0, 9) == "​MeldPanel"​) { 
-                        integer meldno = (integer)xmrSubstring (name, 9); 
-                        cp = Meld.Construct (id, meldno); 
-                    } else if (xmrSubstring (name, 0, 15) == "​PlayerCardPanel"​) { 
-                        key avuuid = xmrSubstring (message, 4); 
-                        string avname = llKey2Name (avuuid); 
-                        if (avname == ""​) { 
-                            SendAvErrMsg (avuuid, "​failed to capture avatar name, try detaching and reattaching HUD"); 
-                        } else { 
-                            Player player = Player.Construct (avuuid, avname); 
-                            integer index = (integer)xmrSubstring (name, 15); 
-                            cp = PlayerCardPanel.Construct (id, player, index); 
-                        } 
-                    } else if (xmrSubstring (name, 0, 12) == "​PlayerStatus"​) { 
-                        integer num = (integer)xmrSubstring (name, 12); 
-                        cp = PlayerStatus.Construct (id, num); 
-                    } else if (name == "​Stock"​) { 
-                        cp = StockCardPanel.Construct (id); 
-                    } else if (name == "​StartButton"​) { 
-                        cp = StartButton.Construct (id); 
-                    } else { 
-                        llOwnerSay ("​unknown card panel " + name); 
-                        cp = undef; 
-                    } 
-                    cp.unansweredPings = 0; 
-                    allCardPanels[id] = cp; 
-                } 
-                llRegionSayTo (id, RUMMY_CHANNEL,​ "​rgcp"​);​ 
-                break; 
-            } 
- 
-            /* 
-             * Response for START button confirmation dialog. 
-             */ 
-            case "​Shuf":​ { 
-                if (message == "​Shuffle"​) { 
-                    llSay (PUBLIC_CHANNEL,​ name + " has started the game..."​);​ 
-                    StartGame (); 
-                } 
-                break; 
-            } 
-        } 
-    } 
- 
-    touch_start (integer num) 
-    { 
-        key byavuuid = llDetectedOwner (0); 
-        llDialog (byavuuid, "Are you REALLY SURE you want to factory reset the game?  " +  
-                            "Any hands and scores will be lost and players will have to re-register.", ​ 
-                  [ "​Reset",​ "Never mind" ], RUMMY_CHANNEL);​ 
-    } 
-} 
-  
-/​******************\ 
- ​* ​ Game Control ​ * 
-\******************/​ 
- 
- 
-/** 
- * @brief Start a game. 
- */ 
-StartGame () 
-{ 
-    object v; 
- 
-    /* 
-     * Clear off any existing melds. 
-     */ 
-    foreach (,v in melds) { 
-        Meld m = (Meld)v; 
-        m.Clear (); 
-    } 
- 
-    /* 
-     * Reset discard pile to be empty. 
-     */ 
-    DiscardCardPanel.Clear (); 
- 
-    /* 
-     * Shuffle deck. 
-     */ 
-    StockCardPanel.Shuffle (); 
- 
-    /* 
-     * Figure out how many cards each player gets initially. 
-     */ 
-    integer nplayers = 0; 
-    foreach (,v in playersByIndex) { 
-        PlayerStatus ps = (PlayerStatus)v;​ 
-        if (ps.player != undef) nplayers ++; 
-    } 
-    if (nplayers == 0) { 
-        llSay (PUBLIC_CHANNEL,​ "no players registered; click on player status bar"); 
-        return; 
-    } 
-    integer ncards; 
-    if (nplayers <= 2) ncards = 10; 
-    else if (nplayers <= 4) ncards = 7; 
-    else ncards = 6; 
- 
-    /* 
-     * Deal that many cards to each player in order and 
-     * update all displays. 
-     */ 
-    integer cardno; 
-    for (integer i = 0; i < ncards; i ++) { 
-        for (integer j = 0; j < playersByIndex.count;​ j ++) { 
-            PlayerStatus ps = (PlayerStatus)playersByIndex[j];​ 
-            Player p = ps.player; 
-            if (p != undef) { 
-                if (i == 0) p.ClearHand (); 
-                cardno = StockCardPanel.PopFromStockPile (); 
-                p.AddCardToHand (cardno); 
-            } 
-        } 
-    } 
-    for (integer j = 0; j < playersByIndex.count;​ j ++) { 
-        PlayerStatus ps = (PlayerStatus)playersByIndex[j];​ 
-        if (ps.player != undef) { 
-            ps.UpdateDisplay (); 
-        } 
-    } 
- 
-    /* 
-     * Deal a card to the discard pile. 
-     */ 
-    cardno = StockCardPanel.PopFromStockPile (); 
-    DiscardCardPanel.PushToDiscardPile (cardno); 
- 
-    /* 
-     * Set current player to #0 and tell them it's their turn. 
-     */ 
-    SetCurrentPlayer (0); 
-} 
- 
- 
-/** 
- * @brief Tell the old player they are no longer current 
- ​* ​       and tell the new player they are now current 
- */ 
-SelectNextPlayer () 
-{ 
-    currentPlayer.hasMeldedBeforeThisTurn = currentPlayer.hasEverMeldedThisHand;​ 
-    SetCurrentPlayer (currentPlayer.index + 1); 
-} 
-SetCurrentPlayer (integer n) 
-{ 
-    if (currentPlayer != undef) { 
-        currentPlayer.UpdateDisplay (); 
-    } 
-    do { 
-        if (n == playersByIndex.count) n = 0; 
-        currentPlayer = ((PlayerStatus)playersByIndex[n++]).player;​ 
-    } while (currentPlayer == undef); 
-    llSay (PUBLIC_CHANNEL,​ "It is " + currentPlayer.avname + "'​s turn"​);​ 
-    SendAvErrMsg (currentPlayer.avuuid,​ "​it'​s your turn; draw from stock or from discard pile"​);​ 
-    phaseDrawing = 1; 
-} 
- 
- 
-/** 
- * @brief The current player is now out so the game is over. 
- */ 
-PlayerIsOut () 
-{ 
-    integer factor = 1; 
-    string goneRummy = "";​ 
-    if (!currentPlayer.hasMeldedBeforeThisTurn) { 
-        factor = 2; 
-        goneRummy = " has GONE RUMMY";​ 
-    } 
- 
-    llSay (PUBLIC_CHANNEL,​ "GAME OVER !!!  Hooray " + currentPlayer.avname + goneRummy + " !!!"); 
- 
-    integer score = 0; 
-    object v1; 
-    object v2; 
-    foreach (,v1 in playersByIndex) { 
-        PlayerStatus ps = (PlayerStatus)v1;​ 
-        Player player = ps.player; 
-        if (player != undef) { 
-            foreach (,v2 in player.cardPanels) { 
-                PlayerCardPanel pcp = (PlayerCardPanel)v2;​ 
-                if (pcp.cardno >= 0) { 
-                    score += cardpoints[pcp.cardno%13] * factor; 
-                    llSay (PUBLIC_CHANNEL,​ "... " + player.avname + " " + CardName (pcp.cardno) + " -> " + score); 
-                    pcp.RemoveFromHand (); 
-                } 
-            } 
-            ps.UpdateDisplay (); 
-        } 
-    } 
-    currentPlayer.score += score; 
-    currentPlayer.UpdateDisplay (); 
-    currentPlayer = undef; 
-} 
-  
-/​*************************\ 
- ​* ​ RummyBoard elements ​ * 
-\*************************/​ 
- 
- 
-/** 
- * @brief Manage the undealt cards. 
- */ 
-class StockCardPanel : CardPanel { 
- 
-    private static integer dealIndex; 
-    private static list shuffledCards;​ 
-    private static StockCardPanel singleton; 
- 
-    /** 
-     * @brief Called when the StockPanel button box checks in. 
-     */ 
-    public static StockCardPanel Construct (key id) 
-    { 
-        if (singleton == undef) { 
-            singleton = new StockCardPanel (); 
-        } 
-        singleton.id = id; 
- 
-        string url = "​http://​www.outerworldapps.com/​cards-classic/​bluback.png";​ 
-        llRegionSayTo (id, RUMMY_CHANNEL,​ "​sdtu"​ + url); 
- 
-        return singleton; 
-    } 
- 
-    private constructor () { } 
- 
-    public static Shuffle () 
-    { 
-        shuffledCards = llListRandomize (sortedCards,​ 1); 
-        dealIndex = 0; 
-    } 
- 
-    /** 
-     * @brief Called when someone clicks on the stock pile box, 
-     ​* ​       presumably to draw a card at the beginning of their turn. 
-     */ 
-    public override Touched (key byavuuid) 
-    { 
-        if ((currentPlayer != undef) &&&​ (currentPlayer.avuuid == byavuuid)) { 
-            if (phaseDrawing) { 
- 
-                /* 
-                 * Player is drawing a card from the stock pile. 
-                 */ 
-                integer cardno = PopFromStockPile (); 
-                currentPlayer.AddCardToHand (cardno); 
-                phaseDrawing = 0; 
-                SendAvErrMsg (currentPlayer.avuuid,​ "now do any melding you want then put one card in discard pile"​);​ 
-            } else { 
- 
-                /* 
-                 * Player must discard exactly one card. 
-                 */ 
-                SendAvErrMsg (byavuuid, "​cannot draw from stock pile; meld and/or discard"​);​ 
-            } 
-        } 
-    } 
- 
-    /** 
-     * @brief Draw (deal) a card from the stock pile. 
-     */ 
-    public static integer PopFromStockPile () 
-    { 
-        if (dealIndex >= llGetListLength (shuffledCards)) { 
-            llSay (PUBLIC_CHANNEL,​ "stock pile empty, reshuffling discards"​);​ 
-            list discards = DiscardCardPanel.PopAllFromDiscardPile (); 
-            shuffledCards = llListRandomize (discards, 1); 
-            dealIndex = 0; 
-        } 
-        return (integer)shuffledCards[dealIndex++];​ 
-    } 
-} 
- 
- 
-/** 
- * @brief Keep track of discards. 
- */ 
-class DiscardCardPanel : CardPanel { 
- 
-    private static array discardPile;​ 
-    private static DiscardCardPanel singleton; 
- 
-    public static DiscardCardPanel Construct (key id) 
-    { 
-        if (singleton == undef) { 
-            singleton = new DiscardCardPanel (); 
-        } 
-        singleton.id = id; 
-        return singleton; 
-    } 
- 
-    private constructor () { } 
- 
-    /** 
-     * @brief Someone clicked on the discard pile, 
-     ​* ​       presumably to either draw a card from the discard pile  
-     ​* ​       or to move a card from their hand to the discard pile. 
-     */ 
-    public override Touched (key byavuuid) 
-    { 
-        if ((currentPlayer != undef) &&&​ (currentPlayer.avuuid == byavuuid)) { 
-            if (phaseDrawing) { 
- 
-                /* 
-                 * Current player is drawing a card from the discard pile. 
-                 */ 
-                integer cardno = PopFromDiscardPile (); 
-                currentPlayer.AddCardToHand (cardno); 
-                phaseDrawing = 0; 
-                SendAvErrMsg (currentPlayer.avuuid,​ "now do any melding you want then put one card in discard pile"​);​ 
-            } else { 
-                array selectedcards = currentPlayer.GetSelectedCards (); 
-                if (selectedcards.count == 1) { 
- 
-                    /* 
-                     * Current player is discarding the one card they have selected in their hand. 
-                     */ 
-                    PlayerCardPanel pcp = (PlayerCardPanel)selectedcards.value (0); 
-                    if (!canDiscardDraw &&&​ (pcp.cardno == currentPlayer.lastCardAdded)) { 
-                        SendAvErrMsg (currentPlayer.avuuid,​ "​cannot discard drawn card, select another"​);​ 
-                    } else { 
-                        PushToDiscardPile (pcp.cardno);​ 
-                        pcp.RemoveFromHand (); 
- 
-                        /* 
-                         * End of game if player is out. 
-                         * Otherwise, on to next player. 
-                         */ 
-                        if (currentPlayer.NumberCardsInHand () == 0) { 
-                            PlayerIsOut (); 
-                        } else { 
-                            SelectNextPlayer (); 
-                        } 
-                    } 
-                } else { 
- 
-                    /* 
-                     * Player must discard exactly one card. 
-                     */ 
-                    SendAvErrMsg (byavuuid, "​select exactly one card to discard first"​);​ 
-                } 
-            } 
-        } 
-    } 
- 
-    public static Clear () 
-    { 
-        discardPile.clear (); 
-        singleton.DisplayCard (-1); 
-    } 
- 
-    /** 
-     * @brief Push a card onto the discard pile and update display. 
-     */ 
-    public static PushToDiscardPile (integer cardno) 
-    { 
-        discardPile[discardPile.count] = cardno; 
-        singleton.DisplayCard (cardno); 
-    } 
- 
-    /** 
-     * @brief Pop a card from the discard pile and update display. 
-     */ 
-    public static integer PopFromDiscardPile () 
-    { 
-        integer n = discardPile.count;​ 
-        integer cardno = (integer)discardPile[--n];​ 
-        discardPile[n] = undef; 
-        if (discardPile.count > 0) { 
-            singleton.DisplayCard ((integer)discardPile[discardPile.count-1]);​ 
-        } else { 
-            singleton.DisplayCard (-1); 
-        } 
-        return cardno; 
-    } 
- 
-    /** 
-     * @brief Pop all the cards from discard pile in a list. 
-     */ 
-    public static list PopAllFromDiscardPile () 
-    { 
-        integer i = discardPile.count;​ 
-        object[] objs = new object[] (i); 
-        i = 0; 
-        object v; 
-        foreach (,v in discardPile) { 
-            objs[i++] = v; 
-        } 
-        Clear (); 
-        return xmrArray2List (objs, 0, i); 
-    } 
-} 
- 
- 
-/** 
- * @brief Help button gives them the help notecard. 
- */ 
-class HelpButton : CardPanel { 
-    private static string textextras = "​width:​128,​height:​64";​ 
- 
-    private static HelpButton singleton; 
- 
-    public static HelpButton Construct (key id) 
-    { 
-        if (singleton == undef) { 
-            singleton = new HelpButton (); 
-        } 
-        singleton.id = id; 
- 
-        string data = "";​ 
-        data = osSetFontSize (data, fontsize); 
-        data = osDrawText (data, "​HELP"​);​ 
-        string json = "​{\"​data\":"​ + JSONString (data) + ",​\"​extra\":"​ + JSONString (textextras) + "​}";​ 
-        llRegionSayTo (id, RUMMY_CHANNEL,​ "​sdtd"​ + json); 
- 
-        return singleton; 
-    } 
- 
-    private constructor () { } 
- 
-    public override Touched (key byavuuid) 
-    { 
-        llGiveInventory (byavuuid, "​RummyHelp"​);​ 
-    } 
-} 
- 
- 
-/** 
- * @brief One of these per the six player status panels, 
- ​* ​       whether there is a player registered there or not. 
- */ 
-class PlayerStatus : CardPanel { 
-    private static string textextras = "​width:​512,​height:​64";​ 
- 
-    public integer index; ​ // index number 0..5 
-    public Player player; ​ // registered player (or undef if none) 
- 
-    /** 
-     * @brief Catalog this player status panel so we can display stuff on it. 
-     */ 
-    public static PlayerStatus Construct (key id, integer index) 
-    { 
-        PlayerStatus zhis = (PlayerStatus)playersByIndex[index];​ 
-        if (zhis == undef) { 
-            zhis = new PlayerStatus (); 
-            zhis.index = index; 
-            playersByIndex[index] = zhis; 
-        } 
-        zhis.id = id; 
-        zhis.UpdateDisplay (); 
-        return zhis; 
-    } 
- 
-    private constructor () { } 
- 
-    /** 
-     * @brief Someone just clicked on the player status bar. 
-     ​* ​       If no one is currently occupying that slot, put them there. 
-     ​* ​       Else, if they are the one occupying the slot, remove them. 
-     */ 
-    private override Touched (key byavuuid) 
-    { 
-        if (currentPlayer != undef) { 
-            SendAvErrMsg (byavuuid, "​cannot join or leave while a game is in progress"​);​ 
-            return; 
-        } 
-        if (player == undef) { 
-            player = (Player)playersByAvUUID[byavuuid];​ 
-            if (player == undef) { 
-                llDialog (byavuuid, "HUD not attached or try detaching and reattach HUD.  Do you need an HUD?", ​ 
-                          [ "I need HUD", "No thanks"​ ], RUMMY_CHANNEL);​ 
-                return; 
-            } 
-            if (player.index >= 0) { 
-                SendAvErrMsg (byavuuid, "you are already registered as player " + (player.index + 1)); 
-                return; 
-            } 
-            player.index = index; 
-            UpdateDisplay (); 
-        } else if (player.avuuid == byavuuid) { 
-            player.index = -1; 
-            player = undef; 
-            UpdateDisplay (); 
-        } else { 
-            SendAvErrMsg (byavuuid, player.avname + " already registered in that slot, try another"​);​ 
-        } 
-    } 
- 
-    /** 
-     * @brief Update the display to match what we think the state is. 
-     */ 
-    private string lastdisplayed = "";​ 
-    public UpdateDisplay () 
-    { 
-        if (player == undef) { 
-            if (lastdisplayed != ""​) { 
-                llRegionSayTo (id, RUMMY_CHANNEL,​ "​txur"​ + TEXTURE_BLANK);​ 
-                lastdisplayed = "";​ 
-            } 
-        } else { 
-            string data = "";​ 
-            data = osSetFontSize (data, fontsize); 
-            data = osDrawText (data, (string)player.NumberCardsInHand () + " : " + player.avname + " : " + (string)player.score);​ 
-            string json = "​{\"​data\":"​ + JSONString (data) + ",​\"​extra\":"​ + JSONString (textextras) + "​}";​ 
-            if (lastdisplayed != json) { 
-                llRegionSayTo (id, RUMMY_CHANNEL,​ "​sdtd"​ + json); 
-                lastdisplayed = json; 
-            } 
-        } 
-    } 
-} 
- 
- 
-/** 
- * @brief Start button shuffles the deck, deals initial cards and starts first player. 
- */ 
-class StartButton : CardPanel { 
-    private static string textextras = "​width:​128,​height:​64";​ 
- 
-    private static StartButton singleton; 
- 
-    public static StartButton Construct (key id) 
-    { 
-        if (singleton == undef) { 
-            singleton = new StartButton (); 
-        } 
-        singleton.id = id; 
- 
-        string data = "";​ 
-        data = osSetFontSize (data, fontsize); 
-        data = osDrawText (data, "​START"​);​ 
-        string json = "​{\"​data\":"​ + JSONString (data) + ",​\"​extra\":"​ + JSONString (textextras) + "​}";​ 
-        llRegionSayTo (id, RUMMY_CHANNEL,​ "​sdtd"​ + json); 
- 
-        return singleton; 
-    } 
- 
-    private constructor () { } 
- 
-    public override Touched (key byavuuid) 
-    { 
-        string msg = "Are you REALLY SURE you want to start the game?";​ 
-        if (currentPlayer != undef) { 
-            msg += " ​ All hands will be lost and deck will be reshuffled.";​ 
-        } 
-        llDialog (byavuuid, msg, [ "​Shuffle",​ "Never mind" ], RUMMY_CHANNEL);​ 
-    } 
-} 
- 
- 
-/** 
- * @brief One of these per meld.  It has four MeldCardPanels for displaying the  
- ​* ​       melded cards, but it does not display anything itself as such. 
- ​* ​       It is a CardPanel though so we can sense a touch meaning that the  
- ​* ​       current player wishes to meld his selected cards with this meld panel. 
- */ 
-class Meld : CardPanel { 
-    public array melded; ​        // cardno -> cardno (so it's sorted by cardno) 
-    public integer index; ​       // which meld panel we are (0..n) 
-    public integer setTypeMeld; ​ // 0=run; 1=set 
-    public array cardPanels; ​    // integer -> MeldCardPanel : each card panel on the meld panel 
- 
-    public static Meld Construct (key id, integer index) 
-    { 
-        Meld zhis = (Meld)melds[index];​ 
-        if (zhis == undef) { 
-            zhis = new Meld (); 
-            zhis.index = index; 
-            melds[index] = zhis; 
-        } 
-        if (id != ""​) zhis.id = id; 
-        return zhis; 
-    } 
- 
-    private constructor () { } 
- 
-    public Clear () 
-    { 
-        melded.clear (); 
-        UpdateDisplay (); 
-    } 
- 
-    /** 
-     * @brief Try to meld the currently selected cards into this meld. 
-     ​* ​       Update the status board and the player'​s hand. 
-     */ 
-    public override Touched (key byavuuid) 
-    { 
-        if (currentPlayer == undef) return; 
- 
-        /* 
-         * Get list of cards in their hand they are trying to meld. 
-         */ 
-        array melding = currentPlayer.GetSelectedCards (); 
-        integer meldingCount = melding.count;​ 
- 
-        /* 
-         * If not trying to meld anything, tell them what is in the pile if anything. 
-         */ 
-        if (meldingCount == 0) { 
-            if (melded.count > 0) { 
-                string meldedNames;​ 
-                integer firstCardNo = (integer)melded.index (0); 
-                if (setTypeMeld) { 
-                    meldedNames = RankName (firstCardNo) + " of"; 
-                    object v; 
-                    foreach (,v in melded) { 
-                        meldedNames += " " + SuitName ((integer)v);​ 
-                    } 
-                } else { 
-                    integer lastCardNo = (integer)melded.index (melded.count - 1); 
-                    meldedNames ​ = RankName (firstCardNo) + " .. " + RankName (lastCardNo);​ 
-                    meldedNames += " of " + SuitName (firstCardNo);​ 
-                } 
-                SendAvErrMsg (currentPlayer.avuuid,​ "... [ " + meldedNames + " ]"); 
-            } 
-            return; 
-        } 
- 
-        /* 
-         * Make sure it is their turn. 
-         */ 
-        if (currentPlayer.avuuid != byavuuid) return; 
- 
-        /* 
-         * Maybe they always need to leave one for discarding. 
-         */ 
-        if (mustDiscardOut && (meldingCount >= currentPlayer.NumberCardsInHand ())) { 
-            SendAvErrMsg (currentPlayer.avuuid,​ "​cannot meld all cards, must retain one card to discard"​);​ 
-            return; 
-        } 
- 
-        /* 
-         * Try to add them to this meld. 
-         * If successful, remove the melded cards from the player'​s hand. 
-         */ 
-        integer rc = AddToMeld (melding); 
-        if (rc) { 
-            string cardnames = "";​ 
-            object v; 
-            foreach (,v in melding) { 
-                PlayerCardPanel pcp = (PlayerCardPanel)v;​ 
-                if (cardnames != ""​) cardnames += ", "; 
-                cardnames += CardName (pcp.cardno);​ 
-                pcp.RemoveFromHand (); 
-            } 
-            llSay (0, currentPlayer.avname + " just melded " + cardnames); 
-            currentPlayer.hasEverMeldedThisHand = 1; 
-            if (currentPlayer.NumberCardsInHand () == 0) { 
-                PlayerIsOut (); 
-            } else { 
-                SendAvErrMsg (currentPlayer.avuuid,​ "you may meld again or put one card in discard pile"​);​ 
-            } 
-        } else { 
-            string pfx = "those cards are"; 
-            if (meldingCount == 1) pfx = "that card is"; 
-            SendAvErrMsg (currentPlayer.avuuid,​ pfx + " not meldable (to the selected pile anyway)"​);​ 
-        } 
-    } 
- 
-    /** 
-     * @brief Try to add the given list of cards to the meld 
-     * @param melding = integer cardno -> PlayerCardPanel 
-     * @returns 0: can't add to meld 
-     ​* ​         1: successfully added to meld 
-     */ 
-    public integer AddToMeld (array melding) 
-    { 
-        object v; 
- 
-        if (melded.count == 0) { 
- 
-            /* 
-             * Making new meld, must have at least 3 cards. 
-             */ 
-            if (melding.count < 3) return 0; 
- 
-            /* 
-             * Check for run-type meld, ie, all same suit and in sequence. 
-             */ 
-            integer locardno = 999999999; 
-            integer hicardno = -1; 
-            foreach (v, in melding) { 
-                integer cardno = (integer)v; 
-                if (locardno > cardno) locardno = cardno; 
-                if (hicardno < cardno) hicardno = cardno; 
-            } 
-            if ((locardno / 13 != hicardno / 13) ||| (hicardno + 1 - locardno != melding.count)) { 
- 
-                /* 
-                 * Check for set-type meld, ie, all same rank (but different suits). 
-                 */ 
-                integer rank = -1; 
-                foreach (v, in melding) { 
-                    integer cardno = (integer)v; 
-                    if (rank < 0) rank = cardno % 13; 
-                    else if (cardno % 13 != rank) return 0; 
-                } 
-                setTypeMeld = 1; 
-            } 
-        } else if (setTypeMeld) { 
- 
-            /* 
-             * Adding to set-type meld, new card must be same rank. 
-             */ 
-            integer basecardno = (integer)melded.value (0) % 13; 
-            foreach (v, in melding) { 
-                integer cardno = (integer)v; 
-                if (cardno % 13 != basecardno) return 0; 
-            } 
-        } else { 
- 
-            /* 
-             * Run-type meld, new cards must be same suit and  
-             * sequential rank building off of existing meld. 
-             */ 
-            integer lomeldedcardno = 999999999; ​ // lowest cardno in existing meld 
-            integer himeldedcardno = -1;         // highest cardno in existing meld 
-            foreach (,v in melded) { 
-                integer meldedcardno = (integer)v; 
-                if (lomeldedcardno > meldedcardno) lomeldedcardno = meldedcardno;​ 
-                if (himeldedcardno < meldedcardno) himeldedcardno = meldedcardno;​ 
-            } 
-            array yettomeld; ​                  // copy list of cards to be added 
-            foreach (v, in melding) { 
-                yettomeld[v] = v; 
-            } 
-        @scan; 
-            if (yettomeld.count > 0) { 
-                foreach (,v in yettomeld) {    // scan through unmelded cards 
-                    integer meldcardno = (integer)v; 
-                    if (lomeldedcardno - 1 == meldcardno) { 
-                        lomeldedcardno --;       // it tacks on lower end 
-                        yettomeld[v] = undef; 
-                        jump scan; 
-                    } 
-                    if (himeldedcardno + 1 == meldcardno) { 
-                        himeldedcardno ++;       // it tacks on  higher end 
-                        yettomeld[v] = undef; 
-                        jump scan; 
-                    } 
-                } 
-                return 0;                      // not even one card melded, fail 
-            } 
-            if (lomeldedcardno / 13 !=         // make sure suit didn't wrap around 
-                himeldedcardno / 13) return 0; // eg, can't meld AceClubs and KingSpades 
-        } 
- 
-        /* 
-         * Meld is legal, add new card(s) to existing melded array and update display. 
-         */ 
-        foreach (v, in melding) { 
-            melded[v] = v; 
-        } 
-        UpdateDisplay (); 
- 
-        return 1; 
-    } 
- 
-    /** 
-     * @brief Update the meld's card panels with melded cards. 
-     */ 
-    public UpdateDisplay () 
-    { 
-        integer ci = 0; 
-        integer cn; 
-        object v; 
- 
-        if (melded.count <= 4) { 
-            foreach (,v in melded) { 
-                cn = (integer)v; 
-                DisplayCard (ci++, cn); 
-            } 
-            while (ci < 4) { 
-                DisplayCard (ci++, -1); 
-            } 
-        } else { 
-            foreach (,v in melded) { 
-                cn = (integer)v; 
-                if (ci < 2) { 
-                    DisplayCard (ci++, cn); 
-                } 
-            } 
-            DisplayCard (2, -1); 
-            DisplayCard (3, cn); 
-        } 
-    } 
- 
-    /** 
-     * @brief Display a texture on the given card sub-panel 
-     * @param ci = 0..3 giving which card sub-panel 
-     * @param cn < 0: blank 
-     ​* ​         else: card number 
-     */ 
-    private DisplayCard (integer ci, integer cn) 
-    { 
-        if (cardPanels[ci] != undef) { 
-            ((MeldCardPanel)cardPanels[ci]).DisplayCard (cn); 
-        } 
-    } 
-} 
- 
- 
-/** 
- * @brief One of these per card panel in the meld piles, 
- ​* ​       whether there is a card there or not. 
- */ 
-class MeldCardPanel : CardPanel { 
-    public integer cardno; ​ // -1: empty; 0..51: occupied 
-    public integer index; ​  // 0..3 
-    public Meld meld;       // which meld we are part of 
- 
-    public static MeldCardPanel Construct (key id, Meld meld, integer index) 
-    { 
-        MeldCardPanel zhis = (MeldCardPanel)meld.cardPanels[index];​ 
-        if (zhis == undef) { 
-            zhis = new MeldCardPanel (); 
-            zhis.index = index; 
-            meld.cardPanels[index] = zhis; 
-            zhis.meld ​ = meld; 
-        } 
-        zhis.id = id; 
-        return zhis; 
-    } 
- 
-    private constructor () { } 
- 
-    /** 
-     * @brief Someone clicked on a card in the meld pile. 
-     ​* ​       Just pass it along to the meld panel this card belongs to. 
-     */ 
-    public override Touched (key byavuuid) 
-    { 
-        meld.Touched (byavuuid); 
-    } 
-} 
-  
-/​***********************\ 
- ​* ​ RummyHUD Elements ​ * 
-\***********************/​ 
- 
- 
-/** 
- * @brief One of these per HUD attached in this region. 
- */ 
-class Player { 
-    public array cardPanels; ​       // integer 0..19 -> PlayerCardPanel 
-    public integer hasMeldedBeforeThisTurn;​ 
-    public integer hasEverMeldedThisHand;​ 
-    public integer index = -1;      // 0..5 (or -1 if not playing) 
-    public integer lastCardAdded; ​  // last card added to hand (ie, last card drawn) 
-    public integer score; ​          // accumulated score for all hands 
-    public key avuuid; ​             // this avatar'​s uuid 
-    public string avname; ​          // this avatar'​s name 
- 
-    public static Player Construct (key avuuid, string avname) 
-    { 
-        Player zhis = (Player)playersByAvUUID[avuuid];​ 
-        if (zhis == undef) { 
-            llOwnerSay ("​detected player " + avname); 
-            zhis = new Player (); 
-            zhis.avuuid = avuuid; 
-            playersByAvUUID[avuuid] = zhis; 
-        } 
-        zhis.avname = avname; 
-        return zhis; 
-    } 
- 
-    private constructor () { } 
- 
-    /** 
-     * @brief Remove all cards from player'​s hand and update display. 
-     */ 
-    public ClearHand () 
-    { 
-        object v; 
-        foreach (,v in cardPanels) { 
-            PlayerCardPanel pcp = (PlayerCardPanel)v;​ 
-            pcp.RemoveFromHand (); 
-        } 
-        hasMeldedBeforeThisTurn = 0; 
-        hasEverMeldedThisHand ​  = 0; 
-    } 
- 
-    /** 
-     * @brief Add a card to player'​s hand and update display. 
-     */ 
-    public AddCardToHand (integer cardno) 
-    { 
-        lastCardAdded = cardno; 
-        SendAvErrMsg (avuuid, "... " + CardName (cardno)); 
-        object v; 
-        foreach (,v in cardPanels) { 
-            PlayerCardPanel pcp = (PlayerCardPanel)v;​ 
-            if (pcp.cardno < 0) { 
-                pcp.AddCardToHand (cardno); 
-                break; 
-            } 
-        } 
-    } 
- 
-    /** 
-     * @brief Count number of cards in player'​s hand. 
-     */ 
-    public integer NumberCardsInHand () 
-    { 
-        integer n = 0; 
-        object v; 
-        foreach (,v in cardPanels) { 
-            PlayerCardPanel pcp = (PlayerCardPanel)v;​ 
-            n += (integer)(pcp.cardno >= 0); 
-        } 
-        return n; 
-    } 
- 
-    /** 
-     * @brief Get an sorted array of selected card numbers. 
-     * @returns array of cardno->​PlayerCardPanel 
-     */ 
-    public array GetSelectedCards () 
-    { 
-        array seldcards; 
-        object v; 
-        foreach (,v in cardPanels) { 
-            PlayerCardPanel pcp = (PlayerCardPanel)v;​ 
-            if ((pcp.cardno >= 0) && pcp.selected) { 
-                seldcards[pcp.cardno] = pcp; 
-            } 
-        } 
-        return seldcards; 
-    } 
- 
-    /** 
-     * @brief Haven'​t heard from the player in too long. 
-     */ 
-    public UnheardFrom () 
-    { 
-        SendAvErrMsg (avuuid, "hud inactive"​);​ 
- 
-        playersByAvUUID[avuuid] = undef; 
-        if (index >= 0) { 
-            PlayerStatus ps = (PlayerStatus)playersByIndex[index];​ 
-            ps.player = undef; 
-            ps.UpdateDisplay (); 
-            index = -1; 
-        } 
- 
-        object v; 
-        foreach (,v in cardPanels) { 
-            PlayerCardPanel pcp = (PlayerCardPanel)v;​ 
-            allCardPanels[pcp.id] = undef; 
-            if ((currentPlayer != undef) && (pcp.cardno >= 0)) { 
-                DiscardCardPanel.PushToDiscardPile (pcp.cardno);​ 
-            } 
-        } 
-    } 
- 
-    /** 
-     * @brief Update corresponding player status bar. 
-     */ 
-    public UpdateDisplay () 
-    { 
-        if (index >= 0) { 
-            ((PlayerStatus)playersByIndex[index]).UpdateDisplay (); 
-        } 
-    } 
-} 
- 
- 
-/** 
- * @brief One of these per slot in the player'​s hand (HUD). 
- ​* ​       Includes both slots that have a card and those that are empty. 
- */ 
-class PlayerCardPanel : CardPanel { 
-    public integer cardno = -1; // -1 if nothing; else: card number 0..51 
-    public integer selected; ​   // set/cleared by touching 
-                                // - used to select cards for melding / discard 
-    private Player player; 
- 
-    public static PlayerCardPanel Construct (key id, Player player, integer index) 
-    { 
-        PlayerCardPanel zhis = (PlayerCardPanel)player.cardPanels[index];​ 
-        if (zhis == undef) { 
-            zhis = new PlayerCardPanel (id); 
-            player.cardPanels[index] = zhis; 
-            zhis.player = player; 
-        } 
-        zhis.id = id; 
-        zhis.UpdateDisplay (); 
-        return zhis; 
-    } 
- 
-    private constructor (key id) { } 
- 
-    /** 
-     * @brief Called to remove the card from the slot, 
-     ​* ​       such as when the card was discarded or 
-     ​* ​       melded. 
-     */ 
-    public RemoveFromHand () 
-    { 
-        cardno = -1; 
-        selected = 0; 
-        UpdateDisplay (); 
-    } 
- 
-    /** 
-     * @brief Called to add the card to the slot, 
-     ​* ​       such as when drawn from the stock pile. 
-     */ 
-    public AddCardToHand (integer cardno) 
-    { 
-        this.cardno = cardno; 
-        this.selected = 0; 
-        this.UpdateDisplay (); 
-    } 
- 
-    /** 
-     * @brief Player has clicked on a card in his hand (HUD), 
-     ​* ​       presumably to either select or deselect it. 
-     ​* ​       But if they click on an empty slot with just one other  
-     ​* ​       card selected, we move that card to this slot. 
-     */ 
-    public override Touched (key byavuuid) 
-    { 
-        if (byavuuid == player.avuuid) { 
-            if (cardno >= 0) { 
-                SendAvErrMsg (byavuuid, "... [ " + CardName (cardno) + " ]"); 
-                selected = !selected; 
-                UpdateDisplay (); 
-            } else { 
-                array selectedcards = player.GetSelectedCards (); 
-                if (selectedcards.count == 1) { 
-                    PlayerCardPanel oldslot = (PlayerCardPanel)selectedcards.value (0); 
-                    integer cardno = oldslot.cardno;​ 
-                    oldslot.RemoveFromHand (); 
-                    this.AddCardToHand (cardno); 
-                } 
-            } 
-        } 
-    } 
- 
-    /** 
-     * @brief HUD was detached. 
-     */ 
-    public override Detached () 
-    { 
-        if (player != undef) player.UnheardFrom (); 
-    } 
- 
-    /** 
-     * @brief Update display to reflect current object state. 
-     */ 
-    public UpdateDisplay () 
-    { 
-        vector color = <​0,​0,​0>; ​                    // black 
-        if (cardno >= 0) { 
-            color = <​1,​1,​1>; ​                       // white 
-            if (selected) color = <​0.75,​0.75,​1.0>; ​ // sky blue 
-        } 
-        llRegionSayTo (id, RUMMY_CHANNEL,​ "​colr"​ + color); 
-        DisplayCard (cardno); 
-    } 
-} 
-  
-/​***************\ 
- ​* ​ Utilities ​ * 
-\***************/​ 
- 
- 
-/** 
- * @brief Common for all clickable boxes 
- */ 
-class CardPanel { 
-    public key id;                           // uuid of box element 
-    public integer unansweredPings;​ 
-    public abstract Touched (key byavuuid); ​ // called when element touched 
-    public virtual Detached () { }           // called when element detached 
- 
-    private integer lastdisplayedcardno = -99; 
-    public DisplayCard (integer cardno) ​     // utility to display card in box 
-    { 
-        if (cardno != lastdisplayedcardno) { 
-            if (cardno < 0) { 
-                llRegionSayTo (id, RUMMY_CHANNEL,​ "​txur"​ + TEXTURE_BLANK);​ 
-            } else { 
-                string url = "​http://​www.outerworldapps.com/​cards-classic/";​ 
-                url += "​a23456789tjqk"​[cardno%13];​ 
-                url += "​schd"​[cardno/​13];​ 
-                url += "​.png";​ 
-                llRegionSayTo (id, RUMMY_CHANNEL,​ "​sdtu"​ + url); 
-            } 
-            lastdisplayedcardno = cardno; 
-        } 
-    } 
-} 
- 
- 
-/** 
- * @brief Send the given avatar a message, one way or another. 
- */ 
-SendAvErrMsg (key avuuid, string message) 
-{ 
-    try { 
-        llRegionSayTo (avuuid, PUBLIC_CHANNEL,​ message); 
-    } catch (exception e1) { 
-        try { 
-            llInstantMessage (avuuid, message); 
-        } catch (exception e2) { 
-            string avname = llKey2Name (avuuid); 
-            if (avname == ""​) avname = avuuid; 
-            llSay (PUBLIC_CHANNEL,​ avname + ": " + message); 
-        } 
-    } 
-} 
- 
- 
-/** 
- * @brief Get card name string for a given card number. 
- */ 
-list ranks = [ "​Ace",​ "​Two",​ "​Tree",​ "​Four",​ "​Fife",​ "​Six",​ "​Seven",​ "​Eight",​ "​Niner",​ "​Ten",​ "​Jack",​ "​Queen",​ "​King"​ ]; 
-list suits = [ "​Spades",​ "​Clubs",​ "​Hearts",​ "​Diamonds"​ ]; 
-string CardName (integer cardno) 
-{ 
-    if (cardno < 0) return "​(none)";​ 
-    return ranks[cardno%13] + " of " + suits[cardno/​13];​ 
-} 
-string RankName (integer cardno) 
-{ 
-    return ranks[cardno%13];​ 
-} 
-string SuitName (integer cardno) 
-{ 
-    return suits[cardno/​13];​ 
-} 
- 
- 
-/** 
- * @brief Insert needed escapes and enclose string in quotes suitable for JSON. 
- */ 
-string JSONString (string str) 
-{ 
-    integer len = llStringLength (str); 
-    char[] out = new char[] (len * 2 + 2); 
-    integer j = 0; 
-    out[j++] = '"';​ 
-    for (integer i = 0; i < len; i ++) { 
-        char c = str[i]; 
-        switch (c) { 
-            case  8: { 
-                out[j++] = '​\\';​ 
-                out[j++] = '​b';​ 
-                break; 
-            } 
-            case  9: { 
-                out[j++] = '​\\';​ 
-                out[j++] = '​t';​ 
-                break; 
-            } 
-            case 10: { 
-                out[j++] = '​\\';​ 
-                out[j++] = '​n';​ 
-                break; 
-            } 
-            case 12: { 
-                out[j++] = '​\\';​ 
-                out[j++] = '​f';​ 
-                break; 
-            } 
-            case 13: { 
-                out[j++] = '​\\';​ 
-                out[j++] = '​r';​ 
-                break; 
-            } 
-            case '"':​ { 
-                out[j++] = '​\\';​ 
-                out[j++] = '"';​ 
-                break; 
-            } 
-            case '​\\':​ { 
-                out[j++] = '​\\';​ 
-                out[j++] = '​\\';​ 
-                break; 
-            } 
-            default: { 
-                out[j++] = c; 
-                break; 
-            } 
-        } 
-    } 
-    out[j++] = '"';​ 
-    return xmrChars2String (out, 0, j); 
-} 
- 
-</​code>​ 
-===== Card Panel ===== 
-part of rummy card game (runs in all the card display prims and button prims) 
-<code ossl> 
-// cardpanel 
-// - displays a texture on all sides of the object 
-// - also sends a message when touched 
- 
-//    Copyright (C) 2013, Kunta Kinte of www.dreamnation.net 
-// 
-//    This program is free software; you can redistribute it and/or modify 
-//    it under the terms of the GNU General Public License as published by 
-//    the Free Software Foundation; version 2 of the License. 
-// 
-//    This program is distributed in the hope that it will be useful, 
-//    but WITHOUT ANY WARRANTY; without even the implied warranty of 
-//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ​ See the 
-//    GNU General Public License for more details. 
-// 
-//    You should have received a copy of the GNU General Public License 
-//    along with this program; if not, write to the Free Software 
-//    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 ​ USA 
- 
-yoption advflowctl; 
-yoption arrays; 
-yoption norighttoleft;​ 
- 
-constant RUMMY_CHANNEL = -646830961; 
- 
-array knownImages;​ 
-array knownTexts; 
-key serveruuid; 
- 
-default { 
-    state_entry () 
-    { 
-        Restart (); 
-    } 
- 
-    attach (key id) 
-    { 
-        if ((serveruuid != ""​) && (id == NULL_KEY)) { 
-            llRegionSayTo (serveruuid,​ RUMMY_CHANNEL,​ "​DTCH"​);​ 
-        } 
-    } 
- 
-    timer () 
-    { 
-        if (serveruuid == ""​) { 
-            llRegionSay (RUMMY_CHANNEL,​ "​RGCP"​ + llGetOwner ()); 
-        } 
-    } 
- 
-    listen (integer channel, string name, key id, string message) 
-    { 
-        switch (xmrSubstring (message, 0, 4)) { 
- 
-            // set tinting color 
-            case "​colr":​ { 
-                llSetColor ((vector)xmrSubstring (message, 4), ALL_SIDES); 
-                break; 
-            } 
- 
-            // server wants to know if we are still here 
-            case "​ping":​ { 
-                llRegionSayTo (id, RUMMY_CHANNEL,​ "​PONG"​);​ 
-                break; 
-            } 
- 
-            // RummyBoard acknowledgement so stop trying to register 
-            case "​rgcp":​ { 
-                serveruuid = id; 
-                llSetTimerEvent (0); 
-                break; 
-            } 
- 
-            // RummyBoard is resetting so restart to re-register 
-            case "​rstr":​ { 
-                Restart (); 
-                break; 
-            } 
- 
-            // display some text 
-            case "​sdtd":​ { 
-                string text = xmrSubstring (message, 4); 
-                string uuid = (string)knownTexts[text];​ 
-                if (uuid == undef) { 
-                    array json   = osParseJSON (xmrSubstring (message, 4)); 
-                    string data  = (string)json["​data"​];​ 
-                    string extra = (string)json["​extra"​];​ 
-                    if (extra == undef) extra = "";​ 
-                    uuid = osSetDynamicTextureData ("",​ "​vector",​ data, extra, 0); 
-                    // knownTexts[text] = uuid; // caching does not work - get blank text 
-                } else { 
-                    llSetTexture (uuid, ALL_SIDES); 
-                } 
-                break; 
-            } 
- 
-            // display an image 
-            case "​sdtu":​ { 
-                string url  = xmrSubstring (message, 4); 
-                string uuid = (string)knownImages[url];​ 
-                if (uuid == undef) { 
-                    uuid = osSetDynamicTextureURL ("",​ "​image",​ xmrSubstring (message, 4), "",​ 0); 
-                    // knownImages[url] = uuid; // caching does not work - get blank image 
-                } else { 
-                    llSetTexture (uuid, ALL_SIDES); 
-                } 
-                break; 
-            } 
- 
-            // display a texture given its uuid (eg, TEXTURE_BLANK) 
-            case "​txur":​ { 
-                llSetTexture (xmrSubstring (message, 4), ALL_SIDES); 
-                break; 
-            } 
-        } 
-    } 
- 
-    // when touched, just send a message to RummyBoard and let it handle it 
-    touch_start () 
-    { 
-        if (serveruuid != ""​) { 
-            llRegionSayTo (serveruuid,​ RUMMY_CHANNEL,​ "​CPTC"​ + llDetectedKey (0)); 
-        } 
-    } 
-} 
- 
- 
-Restart () 
-{ 
-    serveruuid = "";​ 
-    llSetTexture (TEXTURE_BLANK,​ ALL_SIDES); 
-    llListen (RUMMY_CHANNEL,​ "",​ "",​ ""​);​ 
- 
-    // spread registration messages out over 2 seconds 
-    llSleep (llFrand (2.0)); 
-    llRegionSay (RUMMY_CHANNEL,​ "​RGCP"​ + llGetOwner ()); 
- 
-    // resend registration message if not heard by server 
-    llSetTimerEvent (0.25); 
-} 
- 
-</​code>​ 
-===== Sailboat Control ===== 
-sailboat control script 
-<code ossl> 
-//​sailboatctl 
- 
-//    Copyright (C) 2013, Kunta Kinte of www.dreamnation.net 
-// 
-//    This program is free software; you can redistribute it and/or modify 
-//    it under the terms of the GNU General Public License as published by 
-//    the Free Software Foundation; version 2 of the License. 
-// 
-//    This program is distributed in the hope that it will be useful, 
-//    but WITHOUT ANY WARRANTY; without even the implied warranty of 
-//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ​ See the 
-//    GNU General Public License for more details. 
-// 
-//    You should have received a copy of the GNU General Public License 
-//    along with this program; if not, write to the Free Software 
-//    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 ​ USA 
- 
-yoption advflowctl; 
-yoption arrays; 
-yoption chars; 
-yoption norighttoleft;​ 
-yoption objects; 
-yoption trycatch; 
- 
-constant MINWATERDEPTH ​ = 0.1; 
-constant STEPBKWDSPEED ​ = 1.5;  // knots 
-constant TIMEEPSILON ​   = 0.03; 
-constant TURNAUTOREPEAT = TIMEEPSILON * 2; 
-constant TURNRATERPS ​   = 15.0 * DEG_TO_RAD; 
- 
-float lastStepBkwdAt;​ 
-float lastStepFwdAt;​ 
-float lastTurnLeftAt;​ 
-float lastTurnRightAt;​ 
-integer primlinknum_1sit;​ 
-integer primlinknum_2sit;​ 
-integer speedIndex; 
-key seatedRearAv ​ = NULL_KEY; 
-key seatedFrontAv = NULL_KEY; 
-list speeds = [ 2, 3, 5, 7, 10, 12, 15 ]; // knots 
-vector frontSitPos;​ 
-vector rearSitPos; 
- 
- 
-default 
-{ 
-    state_entry () 
-    { 
-        llSay (PUBLIC_CHANNEL,​ "​initializing please wait"​);​ 
- 
-        llTakeControls (-1, FALSE, TRUE); 
- 
-        // get some commonly used prim link numbers 
-        // also disable sitting on the prims, we will enable them later 
-        integer numberOfPrims = llGetObjectPrimCount (llGetKey ()); 
-        for (integer linknum = 0; ++ linknum <= numberOfPrims;​) { 
-            list pps = llGetLinkPrimitiveParams (linknum, [ PRIM_NAME ]); 
-            string name = (string)pps[0];​ 
-            if (name == "​FirstSeat"​) ​ primlinknum_1sit = linknum; 
-            if (name == "​SecondSeat"​) primlinknum_2sit = linknum; 
- 
-            llLinkSitTarget (linknum, ZERO_VECTOR,​ ZERO_ROTATION);​ 
-            llUnSit (llAvatarOnLinkSitTarget (linknum)); 
-        } 
- 
-        // set up seating positions based on where seats are located 
-        SetUpSeatingPositions (); 
- 
-        // enable sitting on front seat 
-        SetFrontSitTarget (); 
- 
-        llSay (PUBLIC_CHANNEL,​ "​initialization complete"​);​ 
-    } 
- 
-    // so xmrEventDequeue(XMREVENTMASK2_at_target) will work 
-    at_target () { } 
- 
-    timer () 
-    { 
-        HandleTimer (); 
-    } 
- 
-    /** 
-     * @brief Start over from very beginning any time it is rezzed. 
-     */ 
-    on_rez (integer start_param) 
-    { 
-        llSay (PUBLIC_CHANNEL,​ "​resetting please wait"​);​ 
-        SetFrontSitTarget (); 
-        llSay (PUBLIC_CHANNEL,​ "reset complete"​);​ 
-    } 
- 
-    /** 
-     * @brief Detect when avatar sits on or gets off the boat. 
-     */ 
-    changed (integer change) 
-    { 
-        if (change & CHANGED_LINK) { 
-            MaybeSomeoneSatOn (); 
-        } 
- 
-        // notecard was changed, reset 
-        if (change & CHANGED_INVENTORY) { 
-            llResetScript (); 
-        } 
-    } 
- 
-    /** 
-     * @brief Called when seated avatar grants permission to take over controls. 
-     */ 
-    run_time_permissions (integer perms) 
-    { 
-        if (perms & PERMISSION_TAKE_CONTROLS) { 
-            llTakeControls (CONTROL_FWD | CONTROL_BACK | CONTROL_LEFT | CONTROL_RIGHT | 
-                            CONTROL_ROT_LEFT | CONTROL_ROT_RIGHT | CONTROL_UP | CONTROL_DOWN,​ 
-                            TRUE, FALSE); 
-            speedIndex = 0; 
-            UpdateSpeedIndex (0); 
-            llRegionSayTo (seatedFrontAv,​ PUBLIC_CHANNEL,​ "Use arrow keys to move me around"​);​ 
-            llRegionSayTo (seatedFrontAv,​ PUBLIC_CHANNEL,​ "Use Page Up/Down to change my speed"​);​ 
-            llRegionSayTo (seatedFrontAv,​ PUBLIC_CHANNEL,​ "You may have to click on blank area of screen first"​);​ 
-            llRegionSayTo (seatedFrontAv,​ PUBLIC_CHANNEL,​ "You may bring a passenger by having them sit on rear seat"​);​ 
- 
-            llSetSitText ("Ride Along"​);​ 
-            llLinkSitTarget (primlinknum_2sit,​ rearSitPos, ZERO_ROTATION);​ 
-        } 
-    } 
- 
-    /** 
-     * @brief Called when seated avatar presses an arrow button telling boat to move. 
-     */ 
-    control (key id, integer held, integer change) 
-    { 
-        if (id == seatedFrontAv) { 
-            if (held & CONTROL_FWD) ​ StepForward (); 
-            if (held & CONTROL_BACK) StepBackward (); 
- 
-            if (held & (CONTROL_LEFT ​ | CONTROL_ROT_LEFT)) { 
-                TurnLeft (); 
-            } else if (change & (CONTROL_LEFT ​ | CONTROL_ROT_LEFT)) { 
-                lastTurnLeftAt ​ = 0.0; 
-            } 
-            if (held & (CONTROL_RIGHT | CONTROL_ROT_RIGHT)) { 
-                TurnRight (); 
-            } else if (change & (CONTROL_RIGHT | CONTROL_ROT_RIGHT)) { 
-                lastTurnRightAt = 0.0; 
-            } 
- 
-            integer keysdn = change &  held; 
-            if (keysdn & CONTROL_UP) ​  ​UpdateSpeedIndex ( 1); 
-            if (keysdn & CONTROL_DOWN) UpdateSpeedIndex (-1); 
-        } 
-    } 
-} 
- 
- 
-/** 
- * @brief Set up seating positions by sensing positions of seat prims. 
- */ 
-SetUpSeatingPositions () 
-{ 
-    frontSitPos ​ = <​0.3,​0.0,​0.5>; ​ // +x->fwd; +y->​right;​ +z->up 
-    rearSitPos ​  = <​0.3,​0.0,​0.5>;​ 
- 
-    list pps     = llGetLinkPrimitiveParams (primlinknum_1sit,​ [ PRIM_POS_LOCAL ]); 
-    frontSitPos += (vector)pps[0];​ 
-} 
- 
- 
-/** 
- * @brief Detect if someone sat on or got up off of boat. 
- */ 
-MaybeSomeoneSatOn () 
-{ 
-    /* 
-     * Check to see who is on front seat. 
-     */ 
-    key fav = llAvatarOnSitTarget (); 
-    if (seatedFrontAv != fav) { 
- 
-        /* 
-         * Someone either got up or sat on front seat. 
-         * First off, release old controls. 
-         * Also, if someone was sitting on rear seat, kick them off. 
-         */ 
-        llReleaseControls (); 
-        llUnSit (llAvatarOnLinkSitTarget (primlinknum_2sit));​ 
-        seatedRearAv = NULL_KEY; 
- 
-        /* 
-         * If someone else was sitting on boat, unseat them. 
-         */ 
-        if (seatedFrontAv != NULL_KEY) { 
-            llUnSit (seatedFrontAv);​ 
-        } 
- 
-        /* 
-         * If someone is getting on boat, try to take over their controls. 
-         * Otherwise, no one on front seat, disable sitting on rear seat. 
-         */ 
-        seatedFrontAv = fav; 
-        if (seatedFrontAv != NULL_KEY) { 
-            llRequestPermissions (fav, PERMISSION_TAKE_CONTROLS);​ 
-        } else { 
-            SetFrontSitTarget (); 
-        } 
-    } 
- 
-    /* 
-     * Keep track of who is on rear seat. 
-     */ 
-    seatedRearAv = llAvatarOnLinkSitTarget (primlinknum_2sit);​ 
- 
-    /* 
-     * Sorry we only allow front and rear seat. 
-     * And we allow rear seat only when someone is sitting in front. 
-     */ 
-    integer numberOfPrims = llGetObjectPrimCount (llGetKey ()); 
-    for (integer linknum = 0; ++ linknum <= numberOfPrims;​) { 
-        if ((linknum != LINK_ROOT) &&&​ (linknum != primlinknum_2sit ||| seatedFrontAv == NULL_KEY)) { 
-            key av = llAvatarOnLinkSitTarget (linknum); 
-            if (av != NULL_KEY) { 
-                llUnSit (av); 
-                llRegionSayTo (av, PUBLIC_CHANNEL,​ "​sorry,​ only allow sitting on front and rear seats"​);​ 
-                if (seatedFrontAv == NULL_KEY) { 
-                    llRegionSayTo (av, PUBLIC_CHANNEL,​ "​right-click front seat and select 'Sail Me'"​);​ 
-                } else if (seatedRearAv == NULL_KEY) { 
-                    llRegionSayTo (av, PUBLIC_CHANNEL,​ "​right-click rear seat and select 'Ride Along'"​);​ 
-                } 
-            } 
-        } 
-    } 
-} 
- 
- 
-/** 
- * @brief Disable sitting on rear seat and enable sitting on front. 
- ​* ​       It is assumed no one should be sitting at this point. 
- */ 
-SetFrontSitTarget () 
-{ 
-    // unseat anyone sitting on me 
-    llReleaseControls (); 
-    llUnSit (llAvatarOnSitTarget ()); 
-    llUnSit (llAvatarOnLinkSitTarget (primlinknum_2sit));​ 
-    seatedFrontAv = NULL_KEY; 
-    seatedRearAv ​ = NULL_KEY; 
- 
-    // disable sitting on the rear seat (enabled when someone sits on front seat) 
-    // this allows the "​driver"​ to click anywhere on boat to sit on front seat 
-    llLinkSitTarget (primlinknum_2sit,​ ZERO_VECTOR,​ ZERO_ROTATION);​ 
- 
-    // set up where sitting puts the avatar 
-    //   front seat only 
-    //   rear seat disabled until someone sitting in front 
-    llSetSitText ("Sail Me"); 
-    llSitTarget (frontSitPos,​ ZERO_ROTATION);​ 
-} 
- 
- 
-/** 
- * @brief Update current speed index then tell seated avatar what the new speed is. 
- */ 
-UpdateSpeedIndex (integer inc) 
-{ 
-    integer newsi = speedIndex + inc; 
-    if ((newsi >= 0) && (newsi < llGetListLength (speeds))) { 
-        speedIndex = newsi; 
-    } 
- 
-    string msg = "​Speeds:";​ 
-    for (integer i = 0; i < llGetListLength (speeds); i ++) { 
-        msg += " "; 
-        if (i == speedIndex) { 
-            msg += "<<";​ 
-        } 
-        msg += (string)speeds[i] + "​kts";​ 
-        if (i == speedIndex) { 
-            msg += ">>";​ 
-        } 
-    } 
-    llRegionSayTo (seatedFrontAv,​ PUBLIC_CHANNEL,​ msg); 
-} 
- 
- 
-/** 
- * @brief Various normal walking functions. 
- */ 
-StepForward () 
-{ 
-    // figure out how far to move boat forward based 
-    // on speed and how long since last step forward 
-    float now    = llGetTime (); 
-    float dtsec  = now - lastStepFwdAt;​ 
-    float mps    = (integer)speeds[speedIndex] * 0.514444; 
-    float metres = mps * dtsec; 
- 
-    // move boat position forward a little bit 
-    if (dtsec < 0.5) { 
- 
-        // see what direction boat is pointed within the region 
-        rotation rot  = llGetRootRotation (); 
- 
-        // get delta position of X-meters rotated in same direction 
-        // as boat is pointed 
-        vector delta  = <metres, 0.0, 0.0> * rot; 
- 
-        // calculate new position within region based on current position 
-        // if we cross a region boundary, get new current time as the time 
-        // scale changes when crossing region boundaries 
-        vector regpos = llGetRootPosition () + delta; 
-        if ((llGround (delta) < llWater (ZERO_VECTOR) - MINWATERDEPTH) &&&​ 
-            SetNewPosition (regpos)) { 
-            now = llGetTime (); 
-        } 
-    } 
- 
-    // measure next step's distance based on this time 
-    lastStepFwdAt ​ = now; 
-    lastStepBkwdAt = 0.0; 
-} 
- 
-StepBackward () 
-{ 
-    // compute how far backward boat stepped 
-    float now    = llGetTime (); 
-    float dtsec  = now - lastStepBkwdAt;​ 
-    float mps    = STEPBKWDSPEED * 0.514444; 
-    float metres = mps * dtsec; 
- 
-    // move boat position backward that much 
-    if (dtsec < 0.5) { 
-        vector delta = <metres, 0.0, 0.0> * llGetRootRotation (); 
-        if (SetNewPosition (llGetRootPosition () - delta)) { 
-            now = llGetTime (); 
-        } 
-    } 
- 
-    // measure next step's distance based on this time 
-    lastStepFwdAt ​ = 0.0; 
-    lastStepBkwdAt = now; 
-} 
- 
-TurnLeft () 
-{ 
-    // compute number of radians to turn by based on time since last called 
-    float now   = llGetTime (); 
-    float dtsec = now - lastTurnLeftAt;​ 
-    float rads  = TURNRATERPS * dtsec; 
- 
-    // if called at least two times in a row, turn boat that many radians 
-    if (dtsec < 0.5) { 
-        rotation rot = llGetRootRotation () * llEuler2Rot (<0.0, 0.0, rads>); 
-        SetNewRotation (rot); 
-    } 
- 
-    // measure next turn's amount based on this time 
-    lastTurnRightAt = 0.0; 
-    lastTurnLeftAt ​ = now; 
- 
-    // sometimes we don't get autorepeat of turns 
-    // so use timer to get next one until we get key release 
-    HandleTimer (); 
-} 
- 
-TurnRight () 
-{ 
-    // compute number of radians to turn by based on time since last called 
-    float now   = llGetTime (); 
-    float dtsec = now - lastTurnRightAt;​ 
-    float rads  = TURNRATERPS * dtsec; 
- 
-    // if called at least two times in a row, turn boat that many radians 
-    if (dtsec < 0.5) { 
-        rotation rot = llGetRootRotation () / llEuler2Rot (<0.0, 0.0, rads>); 
-        SetNewRotation (rot); 
-    } 
- 
-    // measure next turn's amount based on this time 
-    lastTurnLeftAt ​ = 0.0; 
-    lastTurnRightAt = now; 
- 
-    // sometimes we don't get autorepeat of turns 
-    // so use timer to get next one until we get key release 
-    HandleTimer (); 
-} 
- 
- 
-/** 
- * @brief Set timer event to happen when earlier of blink or stand timer expires. 
- */ 
-HandleTimer () 
-{ 
-    float delta; 
-    float now = llGetTime (); 
- 
-    do { 
- 
-        /* 
-         * Sometimes we don't get autorepeat on turn arrows. 
-         * So if turn in progress we use timer to fill in missing events. 
-         */ 
-        float turnLeftAt ​ = lastTurnLeftAt ​ + TURNAUTOREPEAT;​ 
-        if ((lastTurnLeftAt ​ > 0) &&&​ (now + TIMEEPSILON >= turnLeftAt)) { 
-            TurnLeft (); 
-        } 
-        float turnRightAt = lastTurnRightAt + TURNAUTOREPEAT;​ 
-        if ((lastTurnRightAt > 0) &&&​ (now + TIMEEPSILON >= turnRightAt)) { 
-            TurnRight (); 
-        } 
- 
-        /* 
-         * Figure out when earliest next timer is due. 
-         * Loop back if it has already expired or is just about to expire. 
-         */ 
-        float earliest = now + 9999999.0; 
-        if ((lastTurnLeftAt ​ > 0) &&&​ (earliest > turnLeftAt)) ​ earliest = turnLeftAt; 
-        if ((lastTurnRightAt > 0) &&&​ (earliest > turnRightAt)) earliest = turnRightAt;​ 
-        now   = llGetTime (); 
-        delta = earliest - now; 
-    } while (delta <= TIMEEPSILON);​ 
- 
-    /* 
-     * Next timer not due for a while, so come back then. 
-     */ 
-    llSetTimerEvent (delta); 
-} 
- 
- 
-/** 
- * @brief Set position of the boat and seated avatars as a whole. 
- ​* ​       If pos is outside the region (eg, something < 0.0 or >= 256.0) the boat and 
- ​* ​       any seated avatars will be transparently TPd to the neighboring region. 
- * @returns FALSE: stayed within same region 
- ​* ​          TRUE: now in different region 
- */ 
-integer SetNewPosition (vector pos) 
-{ 
-    rotation rot  = llGetRot (); 
-    integer async = xmrSetObjRegPosRotAsync (pos, rot, XMRSORPRA_FLYACROSS,​ XMREVENTCODE_at_target,​ []); 
-    if (async) { 
-        llSay (PUBLIC_CHANNEL,​ "HANG ON EVERYONE!!!"​);​ 
- 
-        /* 
-         * 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, re-request control key take-over. 
-         */ 
-        if (seatedFrontAv != NULL_KEY) { 
-            integer reqperms = PERMISSION_TAKE_CONTROLS;​ 
-            llRequestPermissions (seatedFrontAv,​ 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 (); 
-            } 
-            llTakeControls (CONTROL_FWD | CONTROL_BACK | CONTROL_LEFT | CONTROL_RIGHT | 
-                            CONTROL_ROT_LEFT | CONTROL_ROT_RIGHT | CONTROL_UP | CONTROL_DOWN,​ 
-                            TRUE, FALSE); 
-        } 
-    } 
-    return async; 
-} 
- 
-SetNewRotation (rotation rot) 
-{ 
-    llSetLinkPrimitiveParamsFast (LINK_ROOT, [ PRIM_ROT_LOCAL,​ rot ]); 
-} 
- 
-</​code>​ 
-===== Timer ===== 
-general timer class library, provides several independent timers in the script 
-<code ossl> 
-//Kunta Timer 
- 
-//    Copyright (C) 2013, Kunta Kinte of www.dreamnation.net 
-// 
-//    This program is free software; you can redistribute it and/or modify 
-//    it under the terms of the GNU General Public License as published by 
-//    the Free Software Foundation; version 2 of the License. 
-// 
-//    This program is distributed in the hope that it will be useful, 
-//    but WITHOUT ANY WARRANTY; without even the implied warranty of 
-//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ​ See the 
-//    GNU General Public License for more details. 
-// 
-//    You should have received a copy of the GNU General Public License 
-//    along with this program; if not, write to the Free Software 
-//    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 ​ USA 
- 
-/** 
- * @brief Generic timer handling - handles multiple independent timers. 
- * 
- ​* ​ Assumes that every state in script has a timer() handler that calls 
- ​* ​ Kunta.Timer.Handler(). ​ Also, all other event handlers in the script 
- ​* ​ that use these timers must call Kunta.Timer.Handler() at the end, 
- ​* ​ including the default state_entry() handler if it uses timers or 
- ​* ​ if any global inits use timers. 
- */ 
- 
-yoption advflowctl; 
-yoption arrays; 
-yoption objects; 
-yoption trycatch; 
- 
-partial class Kunta { 
-    public abstract class Timer { 
-        private static Timer queue; 
-        private static integer inHandle; 
- 
-        private float _when; 
-        private Timer _next = this; 
- 
-        /** 
-         * @brief Timers that expire within this amount of time 
-         ​* ​       are considered expired right now. 
-         */ 
-        public constant EPSILON = 0.001; 
- 
-        /** 
-         * @brief Minimum time allowed by llSetTimerEvent() 
-         */ 
-        public constant LLTIMEREVENTMIN = 0.5; 
- 
-        /** 
-         * @brief Call in all timer() event handlers. 
-         ​* ​       Also call at end of all event handlers that do timer calls, 
-         ​* ​       including default state_entry() if it uses any timers or any 
-         ​* ​       global inits use timers. 
-         */ 
-        public static Handle () 
-        { 
-            if (!inHandle) { 
-                inHandle = TRUE;                    // prevent recursion via xmrEventDequeue() 
-                try { 
-                    llSetTimerEvent (0.0); ​             // prevent more timer events from queuing 
-                    Timer t; 
-                    while ((t = queue) != undef) {      // get earliest element in queue 
-                        float now = llGetTime ();       // see what time it is 
-                        float delta = t._when - now;    // see how long until earliest expires 
-                        if (delta < EPSILON) { 
-                            queue = t._next; ​           // earliest expired, remove from queue 
-                            t._next = t;                // mark it as not being queued 
-                            t.Expired (now); ​           // perform the callback 
-                            continue; 
-                        } 
-                        if (delta < LLTIMEREVENTMIN) {  // see if too short for llSetTimerEvent() 
-                                                        // ... too short, just like llSleep() except 
-                                                        // ... calls event handlers in background 
-                            xmrEventDequeue (delta, 0, 0, -1, -1); 
-                            continue; 
-                        } 
-                        llSetTimerEvent (delta); ​       // long enough, set timer event to that time 
-                        break; ​                         // ... and we're done for now 
-                    } 
-                } finally { 
-                    // in case t.Expired() or xmrEventDequeue() throws 
-                    inHandle = FALSE; 
-                } 
-            } 
-        } 
- 
-        /** 
-         * @brief Call when llGetTime() time base was changed somehow. 
-         * @param inc = llGetTime() return value increased by this much 
-         * 
-         * Example: 
-         * 
-         ​* ​     float oldtimebase = llGetTime (); 
-         ​* ​         ...something that messes with llGetTime()... 
-         ​* ​     float newtimebase = llGetTime (); 
-         ​* ​     Kunta.Timer.Rebase (newtimebase - oldtimebase);​ 
-         */ 
-        public static Rebase (float inc) 
-        { 
-            for (Timer t = queue; t != undef; t = t._next) { 
-                t._when += inc; 
-            } 
-            if (queue != undef) { 
-                float delta = queue._when - llGetTime (); 
-                if (delta < EPSILON) delta = EPSILON; 
-                llSetTimerEvent (delta); 
-            } 
-        } 
- 
-        /** 
-         * @brief Override this method with your action to 
-         ​* ​       be performed when the time expires. 
-         */ 
-        public abstract Expired (float now); 
- 
-        /** 
-         * @brief Queue the timer to expire at the given time. 
-         * @param when = llGetTime() based time to do callback 
-         ​* ​              or 0.0 to remove from queue 
-         */ 
-        public Queue (float when) 
-        { 
-            /* 
-             * If already queued, remove from queue. 
-             */ 
-            if (this._next != this) { 
-                Timer prev = undef; 
-                Timer next; 
-                for (next = queue; next != this; next = next._next) { 
-                    prev = next; 
-                } 
-                if (prev == undef) queue = this._next; 
-                              else prev._next = this._next; 
-                this._next = this; 
-            } 
- 
-            /* 
-             * Save queuing time (or 0 if cancelling). 
-             */ 
-            _when = when; 
- 
-            /* 
-             * If not cancelling, insert into queue sorted by time. 
-             */ 
-            if (when > 0.0) { 
-                Timer next; 
-                Timer prev = undef; 
-                for (next = queue; next != undef; next = next._next) { 
-                    if (next._when > when) break; 
-                    prev = next; 
-                } 
-                if (prev == undef) queue = this; 
-                              else prev._next = this; 
-                this._next = next; 
-            } 
-        } 
- 
-        /** 
-         * @brief Return last queued time. 
-         */ 
-        public float When 
-        { 
-            get { 
-                return _when; 
-            } 
-        } 
- 
-        /** 
-         * @brief Determine if a timer is queued or not. 
-         * @returns TRUE iff timer is on queue 
-         */ 
-        public integer IsQueued () 
-        { 
-            return this._next != this; 
-        } 
-    } 
-} 
- 
-</​code>​ 
QR Code
QR Code wiki:scripting_portal:yengine.sample (generated for current page)