Don't click here unless you want to be banned.

LSL Wiki : LibrarySailboat

HomePage :: PageIndex :: RecentChanges :: RecentlyCommented :: UserSettings :: You are crawl338.us.archive.org
Scripts for the Flying Tako Sailboat (30AUG2005)

The Flying Tako 2.1 sailboat scripts are provided below (with the exception of the motor script). 2.1 includes an artificial wind function, which is located in the Sail script. This function only works with the SLSF Race Wind System in my Takos (you would need the channel and key, which I am not providing). With 2.0, I completely revamped the script setup. Instead of one script doing everything, I modularized things to some extent. There is now a "Controls" script that provides the interface to the boat and controls vehicle dynamics, a "Sail" script that calculates sail forces and updates the sail prim's rotation, a "Motor" script that performs the back calculations for the outboard motor, and finally a "Color" script that contains functions for changing hull color, setting the sail transparency, displaying ID, etc. At the bottom of the page is a script that controls the sail prim, and a script that makes the streaming particles for the wind indicator. The boat has a few other miscellaneous scripts for setting sit targets which I won't include.

If anyone is interested in making their own line of boats using these scripts or variants thereof, please IM me in-world.

Eloise Pasteur has written up some FlyingTakoTips, which are also provided as a notecard with the retail version.

Steps for making your own sailboat:
1) You will need to make the following prims:
a. Any kind of prim to put the main script in. This prim will be the center of rotation, and will need to be the last object selected when linking all your objects together. In the Flying Tako, the parent prim is the box in the center of the floor. The main script assumes the x-axis of the parent prim is toward the bow, the y-axis is to port, and the z-axis is straight up.
b. A sail prim (see notes below sail script). In fact, a sail is not required, but then you really wouldn't have a sailboat, would you?
c. Various boat parts, none of which is required for the script to work as long as you remove their respective link messages from the main script.
2) Drop the Controls, Sail, and Colors scripts
3) If you look in the scripts, you will notice that the getLinkNums() function looks for linked objects with specific names (e.g. "windindicator"). You will have to name the linked objects those exact names for the getLinkNums() function to work. I added this function so I wouldn't have to keep track of which part had which link number, and it does away with the need to link objects in a specific order (except for the root prim, of course).
4) You will need to provide your own "raised" and "furled" sail textures, or you can do away with the llSetTexture calls in the sail script.
5) If you are having problems getting your own boat to work, I suggest you strip out everything from the script that involves link messages. None of the other linked prims send information back to the root.

The new "Controls" script (30AUG2005):

//Flying Tako Controls Script
//by Kanker Greenacre
//July 2005

//version settings
integer raceVersion=TRUE; integer practiceVersion=FALSE; integer motorVersion=FALSE;
//integer practiceVersion=TRUE; integer raceVersion=FALSE; integer motorVersion=FALSE;
//integer motorVersion=TRUE; integer practiceVersion=FALSE; integer raceVersion=FALSE;
string boatName="Flying Tako ";
string versionNumber="2.1";

//miscellaneous
key owner;//boat owner
key avatar;//person sitting at the helm
key kanker="7abbf31e-d601-4521-ae52-f7457d6e0012";
integer ownerVersion=TRUE;
integer SAIL_UP=FALSE;
integer permSet=FALSE;
integer HUD_ON=TRUE;
string idStr;
integer racing=FALSE;
integer numTouches=0;
integer sailing=TRUE;
integer motoring=FALSE;
list dataList;
integer phantom=FALSE;

//script module flags
integer CONTROLS_MODULE=1;
integer SAIL_MODULE=2;
integer MOTOR_MODULE=3;
integer COLORIZER_MODULE=4;

//reused math variables
vector eulerRot;
vector currEuler;
rotation quatRot;
rotation currRot;
vector fwdVec;
vector upVec;
vector leftVec;

//linear motion variables
float currSpeed;
vector groundSpeed=ZERO_VECTOR;
float spdFactor=0.0;
float leeway;
float rotSpeed=0.7;
float rotDelta;
vector eulerTurnLeft;
vector eulerTurnRight;
rotation quatTurnLeft=ZERO_ROTATION;
rotation quatTurnRight=ZERO_ROTATION;
float rotTweak=0.8;         //scales boat turning rate (80% for now)
float maxSpeedMotor=10.0;

//heeling variables
float heelAngle;
float heelTorque;
float heelAdd;

//environment
float seaLevel;
float windDir;              //we can set this in the practice version
float windSpeed;            //we can set this in the practice version

//linked parts
integer BOW;
integer HULL;
integer STERN;
integer SAIL;
integer WINDVANE;
integer PORTBENCH;
integer STARBOARDBENCH;
integer RUDDER;
integer TILLER;
integer CENTERBOARD;
integer FLOOR;
integer MAST;
integer TRANSOM;
integer REARFLOOR;
integer HUD;
integer NUMBER1;
integer NUMBER2;
integer PROP;

//wind algorithm parameters
float rate=1;
float avgAng=45;
float avgSpd=3;
float halfArc=0.2;
float spdRng=1;

///////////////////////////////////////////////////////////////////////////////////
//not for public release:
integer codeKey=0;
integer pingChannel=0;
integer secretChannel;
integer todaysChannel() {
    return 0;
}
///////////////////////////////////////////////////////////////////////////////////

//set initial vehicle parameters
setVehicleParams() {
    llSetVehicleType         (VEHICLE_TYPE_BOAT);
    llSetVehicleRotationParam(VEHICLE_REFERENCE_FRAME,ZERO_ROTATION);
    llSetVehicleFlags        (VEHICLE_FLAG_NO_DEFLECTION_UP|VEHICLE_FLAG_HOVER_GLOBAL_HEIGHT
                             |VEHICLE_FLAG_LIMIT_MOTOR_UP ); 
    //linear motion
    llSetVehicleVectorParam  (VEHICLE_LINEAR_FRICTION_TIMESCALE,<50.0,2.0,0.5>);;
    llSetVehicleVectorParam  (VEHICLE_LINEAR_MOTOR_DIRECTION,ZERO_VECTOR);
    llSetVehicleFloatParam   (VEHICLE_LINEAR_MOTOR_TIMESCALE,10.0);
    llSetVehicleFloatParam   (VEHICLE_LINEAR_MOTOR_DECAY_TIMESCALE,60);
    llSetVehicleFloatParam   (VEHICLE_LINEAR_DEFLECTION_EFFICIENCY,0.85);
    llSetVehicleFloatParam   (VEHICLE_LINEAR_DEFLECTION_TIMESCALE,1.0);
    //angular motion
    llSetVehicleVectorParam  (VEHICLE_ANGULAR_FRICTION_TIMESCALE,<5,0.1,0.1>);
    llSetVehicleVectorParam  (VEHICLE_ANGULAR_MOTOR_DIRECTION,ZERO_VECTOR);
    llSetVehicleFloatParam   (VEHICLE_ANGULAR_MOTOR_TIMESCALE,0.1);
    llSetVehicleFloatParam   (VEHICLE_ANGULAR_MOTOR_DECAY_TIMESCALE,3);
    llSetVehicleFloatParam   (VEHICLE_ANGULAR_DEFLECTION_EFFICIENCY,1.0);
    llSetVehicleFloatParam   (VEHICLE_ANGULAR_DEFLECTION_TIMESCALE,1.0);
    //vertical attractor
    llSetVehicleFloatParam   (VEHICLE_VERTICAL_ATTRACTION_TIMESCALE,3.0);
    llSetVehicleFloatParam   (VEHICLE_VERTICAL_ATTRACTION_EFFICIENCY,0.8);
    //banking
    llSetVehicleFloatParam   (VEHICLE_BANKING_EFFICIENCY,0.0);
    llSetVehicleFloatParam   (VEHICLE_BANKING_MIX,0.8);
    llSetVehicleFloatParam   (VEHICLE_BANKING_TIMESCALE,1);
    //vertical control
    llSetVehicleFloatParam   (VEHICLE_HOVER_HEIGHT,seaLevel);
    llSetVehicleFloatParam   (VEHICLE_HOVER_EFFICIENCY,2.0);
    llSetVehicleFloatParam   (VEHICLE_HOVER_TIMESCALE,1.0);
    llSetVehicleFloatParam   (VEHICLE_BUOYANCY,1.0);
}

setVehicleParamsMotor() {
    //vertical attractor
    llSetVehicleFloatParam   (VEHICLE_VERTICAL_ATTRACTION_TIMESCALE,1.0);
    llSetVehicleFloatParam   (VEHICLE_VERTICAL_ATTRACTION_EFFICIENCY,0.8);
    //banking
    llSetVehicleFloatParam   (VEHICLE_BANKING_EFFICIENCY,1.0);
    llSetVehicleFloatParam   (VEHICLE_BANKING_MIX,1.0);
    llSetVehicleFloatParam   (VEHICLE_BANKING_TIMESCALE,1);    
}

//automatically detect link nums for each named part
getLinkNums() {
    integer i;
    integer linkcount=llGetNumberOfPrims();  
    for (i=1;i<=linkcount;++i) {
        string str=llGetLinkName(i);
        if (str=="bow") BOW=i;
        if (str=="hull") HULL=i;
        if (str=="stern") STERN=i;
        if (str=="sail") SAIL=i;
        if (str=="mast") MAST=i;
        if (str=="windindicator") WINDVANE=i;
        if (str=="floor") FLOOR=i;
        if (str=="transom") TRANSOM=i;
        if (str=="tiller") TILLER=i;
        if (str=="rudder") RUDDER=i;
        if (str=="centerboard") CENTERBOARD=i;
        if (str=="starboardbench") STARBOARDBENCH=i;
        if (str=="portbench") PORTBENCH=i;
        if (str=="rearfloor") REARFLOOR=i;
        if (str=="hud") HUD=i;
        if (str=="number1") NUMBER1=i;
        if (str=="number2") NUMBER2=i;
        if (str=="prop") PROP=i;          
    }
}

//set camera position for third person view
setCamera() {
    llSetCameraEyeOffset(<-11,0,11>);
    llSetCameraAtOffset(<3,0,3.5>);    
}

//figure out where to put boat when it is rezzed
setInitialPosition() {
    vector pos=llGetPos();
    float groundHeight=llGround(ZERO_VECTOR);
    float waterHeight = llWater(ZERO_VECTOR);
    seaLevel=llWater(ZERO_VECTOR);
    upright();
    if (llGetRegionName()=="Gray" && owner==kanker)
    //if (FALSE)
    {
        rotation initRot=llEuler2Rot(<0,0,225*DEG_TO_RAD>);
        //llSetRot(ZERO_ROTATION);
        vector initPos;
        if (raceVersion) initPos=<36.934,142.546,20.1>;//starting position at my dock
        if (practiceVersion) initPos=<40.046,139.434,20.1>;//starting position at my dock - practice version
        if (motorVersion) initPos=<33.728,145.8,20.1>;
        //initPos=<21,150,20.1>;//test position out in the water
        if (llVecDist(llGetPos(),initPos)<2.0) {
            llSetRot(initRot);
            while (llVecDist(llGetPos(),initPos)>.001) llSetPos(initPos);
        }
        else {
            //if over water, set boat height to sealevel + 0.12m
            if (groundHeight<=waterHeight) {
                pos.z=waterHeight+0.12;
                while (llVecDist(llGetPos(),pos)>.001) llSetPos(pos);
            }            
        }
    }
    else { 
        //if over water, set boat height to sealevel + 0.12m;
        if (groundHeight <= waterHeight) {
            pos.z = waterHeight + 0.12;
            while (llVecDist(llGetPos(),pos)>.001) llSetPos(pos);
        }
    }
}

//set sit target for helmsperson
setSitTarget() {
    llSetSitText("Sit");
    rotation avRot=llEuler2Rot(<0,0,PI_BY_TWO>);
    llSitTarget(<-1.4,-0.45,0.7>,avRot);
    llSetText("",ZERO_VECTOR,1.0);
}

//force boat upright
upright() {
    currRot=llGetRot();
    currEuler=llRot2Euler(currRot);
    leftVec=llRot2Left(currRot);
    heelAngle=llAsin(leftVec.z);
    eulerRot=<-heelAngle,0,0>;
    quatRot=llEuler2Rot(eulerRot);
    llRotLookAt(quatRot*currRot,0.2,0.2);
}

//not sure if i can use this yet
moor() {
    llMessageLinked(LINK_THIS,SAIL_MODULE,"moor",NULL_KEY);
    llMessageLinked(LINK_THIS,MOTOR_MODULE,"off",NULL_KEY);
    llOwnerSay("Mooring.");
    if (permSet) llStopAnimation("helm");
    upright();
    llReleaseControls();
    llSetStatus(STATUS_PHYSICS,FALSE);
    llSetTimerEvent(0);
    currSpeed=0;
    //llResetScript();
}

//reset stuff
startup() {
    owner=llGetOwner();
    llSetStatus(STATUS_ROTATE_X | STATUS_ROTATE_Z | STATUS_ROTATE_Y,TRUE);
    llSetStatus(STATUS_PHYSICS,FALSE);
    llSetStatus(STATUS_PHANTOM,FALSE);
    llSetStatus(STATUS_BLOCK_GRAB,TRUE);
    llSetTimerEvent(0);
    setInitialPosition();
    setVehicleParams();
    setSitTarget();
    getLinkNums();                               
    llMessageLinked(HUD,1000,"",NULL_KEY);      //reset HUD
    llMessageLinked(SAIL,1000,"",NULL_KEY);     //reset sail
    llMessageLinked(WINDVANE,1000,"",NULL_KEY); //turn off windindicator
    setCamera();
    currSpeed=0;
    if (practiceVersion) {
        windDir=45*DEG_TO_RAD;
        windSpeed=3;
        llSetLinkColor(MAST,<0.3,0.3,0.3>,ALL_SIDES);
        string tmp=boatName+versionNumber+" Touring";
        llSetObjectName(tmp);
        if (owner==kanker) llMessageLinked(LINK_THIS,COLORIZER_MODULE,"color lemon",NULL_KEY);
    }
    if (raceVersion) {
        idStr=llGetObjectDesc();
        string tmp=boatName+versionNumber+" Racing";
        if (idStr!="(No Description)" & idStr!="") {
            tmp+=" #"+idStr;
            llSetObjectName(tmp);
        }
        else llSetObjectName(tmp);
        llSetLinkColor(MAST,<0.9,0.9,0.9>,ALL_SIDES);
        if (owner==kanker) llMessageLinked(LINK_THIS,COLORIZER_MODULE,"color <0.2,0.6,0.7>",NULL_KEY);
    }
    if (motorVersion) {
        string tmp="Motor Tako "+versionNumber;
        llSetObjectName(tmp);
        llSetLinkColor(MAST,<1.0,1.0,1.0>,ALL_SIDES);
        if (owner==kanker) llMessageLinked(LINK_THIS,COLORIZER_MODULE,"color green",NULL_KEY);
        //llSetLinkColor(MAST,<0.35,0.5,0.6>,ALL_SIDES);
    }
    llMessageLinked(LINK_THIS,SAIL_MODULE,"reset",NULL_KEY);
    llMessageLinked(LINK_THIS,MOTOR_MODULE,"reset",NULL_KEY);
    llMessageLinked(LINK_THIS,COLORIZER_MODULE,"reset",NULL_KEY);
    llListen(0,"",owner,"");
    if (raceVersion) llListen(pingChannel,"",NULL_KEY,"");
    llOwnerSay("Ready.");
    //llOwnerSay("free memory: "+(string)llGetFreeMemory());
}



//////////////////////////////////////////////////////////////////////////////////////////////
//state default///////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////

default {
    
    state_entry() {
        startup();
        llSetStatus(STATUS_BLOCK_GRAB,TRUE);
    }
    
    on_rez(integer param) {
        //reset();
        llResetScript();
    }

    changed(integer change) {
        avatar=llAvatarOnSitTarget();
        if (change & CHANGED_LINK) {
            if (avatar==NULL_KEY) {
                if (!(llGetAgentInfo(owner) & AGENT_ON_OBJECT)) {
                    llMessageLinked(WINDVANE,1000,"",NULL_KEY);//turn off windindicator
                    if (SAIL_UP) {
                        llOwnerSay("Mainsail lowered.");
                        llMessageLinked(LINK_THIS,SAIL_MODULE,"changed",NULL_KEY);
                    }
                    if (permSet) llStopAnimation("helm");
                    if (permSet) llReleaseControls();
                    permSet=FALSE;
                    llResetScript();
                }
           }
           else {
                if (ownerVersion && avatar!=owner) llWhisper(0,"Only the owner can operate this boat.");
                else if ((llGetAgentInfo(owner) & AGENT_ON_OBJECT)) {
                    //llMessageLinked(WINDVANE,4,"",NULL_KEY);//turn on windindicator
                    llWhisper(0,"Say 'notecard' for instructions or 'raise' to start sailing.");
                    if (llAvatarOnSitTarget()==owner) llRequestPermissions(owner,PERMISSION_TAKE_CONTROLS | PERMISSION_TRIGGER_ANIMATION);          
                }              
            }
        }
    }    
            
    listen(integer channel, string name, key id, string msg) {
        if (channel==0) {
            if (owner==id & llAvatarOnSitTarget()==owner) {
                if (llGetAgentInfo(owner) & AGENT_ON_OBJECT) {
                    if (llGetSubString(msg,0,4)=="sheet") {
                        llMessageLinked(LINK_THIS,SAIL_MODULE,msg,NULL_KEY);
                    }
                    else if (msg=="raise") {
                        sailing=TRUE;
                        motoring=FALSE;
                        if (!permSet) llRequestPermissions(owner,PERMISSION_TAKE_CONTROLS | PERMISSION_TRIGGER_ANIMATION);
                        permSet=TRUE;
                        llStartAnimation("helm");
                        llSetStatus(STATUS_PHYSICS,TRUE);
                        llMessageLinked(LINK_THIS,SAIL_MODULE,msg,NULL_KEY);
                        llMessageLinked(LINK_THIS,MOTOR_MODULE,"off",NULL_KEY);
                        //if (practiceVersion) llMessageLinked(WINDVANE,2000+llRound(windDir),"",NULL_KEY);
                        //else llMessageLinked(WINDVANE,4,"",NULL_KEY);
                        
                    }
                    else if (msg=="lower") llMessageLinked(LINK_THIS,SAIL_MODULE,msg,NULL_KEY);
                    else if (llGetSubString(msg,0,4)=="color") llMessageLinked(LINK_THIS,COLORIZER_MODULE,msg,NULL_KEY);
                    else if (msg=="moor") {
                        moor();
                        llResetScript();
                    }
                    else if (llGetSubString(msg,0,3)=="vane") {
                        if (llGetSubString(msg,5,-1)=="off") llMessageLinked(WINDVANE,1000,"",NULL_KEY);
                        else if (llGetSubString(msg,5,-1)=="on") {
                            if (practiceVersion) llMessageLinked(WINDVANE,2000+llRound(windDir),"",NULL_KEY);
                            else llMessageLinked(WINDVANE,4,"",NULL_KEY);
                        }
                    }
                    else if (llGetSubString(msg,0,3)=="anim") {
                        if (llGetSubString(msg,5,-1)=="off") {
                            if (permSet) llStopAnimation("helm");
                        }
                        else if (llGetSubString(msg,5,-1)=="on") {
                            if (permSet) llStartAnimation("helm");
                        }
                    } 
                    else if (llGetSubString(msg,0,7)=="wind dir") {
                        if (!raceVersion) {
                            windDir=(float)llGetSubString(msg,8,-1);
                            string str="windparams "+(string)windDir+" "+(string)windSpeed+" "+(string)halfArc+" "+(string)spdRng+" "+(string)rate;
                            llMessageLinked(LINK_THIS,SAIL_MODULE,str,NULL_KEY);
                        }
                    }
                    else if (llGetSubString(msg,0,7)=="wind spd") {
                        if (!raceVersion) {
                            windSpeed=(float)llGetSubString(msg,8,-1);
                            string str="windparams "+(string)windDir+" "+(string)windSpeed+" "+(string)halfArc+" "+(string)spdRng+" "+(string)rate;
                            llMessageLinked(LINK_THIS,SAIL_MODULE,str,NULL_KEY);
                        }
                    }
                    else if (llGetSubString(msg,0,4)=="dir+-") {
                        if (!raceVersion) {
                            halfArc=(integer)llGetSubString(msg,5,-1);
                            string str="windparams "+(string)windDir+" "+(string)windSpeed+" "+(string)halfArc+" "+(string)spdRng+" "+(string)rate;
                            llMessageLinked(LINK_THIS,SAIL_MODULE,str,NULL_KEY);
                        }
                    }
                    else if (llGetSubString(msg,0,4)=="spd+-") {
                        if (!raceVersion) {
                            spdRng=(float)llGetSubString(msg,5,-1);
                            string str="windparams "+(string)windDir+" "+(string)windSpeed+" "+(string)halfArc+" "+(string)spdRng+" "+(string)rate;
                            llMessageLinked(LINK_THIS,SAIL_MODULE,str,NULL_KEY);
                        }
                    }
                    else if (llGetSubString(msg,0,3)=="rate") {
                        if (!raceVersion) {
                            rate=(float)llGetSubString(msg,4,-1);
                            string str="windparams "+(string)windDir+" "+(string)windSpeed+" "+(string)halfArc+" "+(string)spdRng+" "+(string)rate;
                            llMessageLinked(LINK_THIS,SAIL_MODULE,str,NULL_KEY);
                        }
                    }
                    else if (llGetSubString(msg,0,1)=="id") {
                        if (raceVersion) {
                            llMessageLinked(LINK_THIS,COLORIZER_MODULE,msg,NULL_KEY);
                            if (llGetSubString(msg,3,-1)!="off") {
                                idStr=llGetSubString(msg,3,-1);
                                string tmp=boatName+" #"+idStr;
                                llSetObjectName(tmp);
                                llSetObjectDesc(idStr);
                            }
                        }
                    }
                    else if (msg=="hud") {
                        if (sailing) llMessageLinked(LINK_THIS,SAIL_MODULE,msg,NULL_KEY);
                        else llMessageLinked(LINK_THIS,MOTOR_MODULE,msg,NULL_KEY);
                    }
                    else if (llGetSubString(msg,0,4)=="alpha") llMessageLinked(LINK_THIS,COLORIZER_MODULE,msg,NULL_KEY);
                    else if (msg=="motor") {
                        if (motorVersion) {
                            if (!permSet) llRequestPermissions(owner,PERMISSION_TAKE_CONTROLS | PERMISSION_TRIGGER_ANIMATION);  
                            permSet=TRUE;
                            llStartAnimation("helm");
                            llSetStatus(STATUS_PHYSICS,TRUE);
                            llMessageLinked(LINK_THIS,SAIL_MODULE,msg,NULL_KEY);
                            llMessageLinked(LINK_THIS,MOTOR_MODULE,msg,NULL_KEY);
                            motoring=TRUE;
                            sailing=FALSE;
                            llMessageLinked(WINDVANE,1000,"",NULL_KEY); //turn off wind indicator
                            setVehicleParamsMotor();
                            llOwnerSay("Motoring");
                        }
                        else llOwnerSay("No motor!");
                    }
                    else if (msg=="off") {
                        if (motorVersion) {
                            sailing=TRUE;
                            motoring=FALSE;
                            setVehicleParams();
                            if (practiceVersion) llMessageLinked(WINDVANE,2000+llRound(windDir),"",NULL_KEY);
                            else llMessageLinked(WINDVANE,4,"",NULL_KEY);
                            llMessageLinked(LINK_THIS,SAIL_MODULE,msg,NULL_KEY);
                            llMessageLinked(LINK_THIS,MOTOR_MODULE,msg,NULL_KEY);                   
                        }
                    }
                    else if (msg=="knots") {
                        llMessageLinked(LINK_THIS,SAIL_MODULE,msg,NULL_KEY);
                        llMessageLinked(LINK_THIS,MOTOR_MODULE,msg,NULL_KEY);
                    }
                    if (msg=="register") {
                        if (raceVersion) {
                            integer chan=todaysChannel();
                            llShout(pingChannel,"ping");
                            llSleep(2.0);
                            string regPacket=idStr+","+(string)llGetOwner();
                            llShout(chan,regPacket);
                        }
                        else {
                            llSay(0,"Your boat is not race legal and cannot be registered.");
                        }
                    }
                    else if (msg=="pp") {
                        if (!raceVersion) {
                            if (!phantom) {
                                llOwnerSay("phantom ON");
                                llSetStatus(STATUS_PHANTOM,TRUE);
                                phantom=TRUE;
                            }
                            else {
                                llOwnerSay("phantom OFF");
                                llSetStatus(STATUS_PHANTOM,FALSE);
                                phantom=FALSE;
                            }
                        }
                    }
                    else if (msg=="practice") {
                        if (!raceVersion) llMessageLinked(LINK_THIS,SAIL_MODULE,msg,NULL_KEY);
                    }             
                }
            }
            if (msg=="notecard") {
                llGiveInventory(id,"Sailing the Tako");
                llGiveInventory(id,"Instructions");
            } 
        }
        else if (channel==pingChannel) {
            secretChannel=todaysChannel();
            llListen(secretChannel,"",NULL_KEY,"");
        }
        else if (channel==secretChannel) {
            string str="windparams "+msg;
            llMessageLinked(LINK_THIS,SAIL_MODULE,str,NULL_KEY);            
        }
    }    
    
    run_time_permissions(integer perms) {
        if (perms & (PERMISSION_TAKE_CONTROLS)) {
            llTakeControls(CONTROL_RIGHT | CONTROL_LEFT | CONTROL_ROT_RIGHT |
            CONTROL_ROT_LEFT | CONTROL_FWD | CONTROL_BACK | CONTROL_DOWN | CONTROL_UP,TRUE,FALSE);
            permSet=TRUE;
        }
    }
    
    control(key id, integer held, integer change) {
        //turning controls
        if ( (change & held & CONTROL_LEFT) || (held & CONTROL_LEFT) || (change & held & CONTROL_ROT_LEFT) || (held & CONTROL_ROT_LEFT) ) {
            if (sailing) llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION,<rotSpeed/2.0,0.0,rotSpeed>);
            else llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION,<-rotSpeed,0.0,rotSpeed/1.5>);
        }
        else if ( (change & held & CONTROL_RIGHT) || (held & CONTROL_RIGHT) || (change & held & CONTROL_ROT_RIGHT) || (held & CONTROL_ROT_RIGHT) ) {
            if (sailing) llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION,<-rotSpeed/2.0,0.0,-rotSpeed>);
            else llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION,<rotSpeed,0.0,-rotSpeed/1.5>);
        }
        else if ( (change & ~held & CONTROL_LEFT) || (change & ~held & CONTROL_ROT_LEFT) ) {
            llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION,<0.0,0.0,0.0>);
        }
        else if ( (change & ~held & CONTROL_RIGHT) || (change & ~held & CONTROL_ROT_RIGHT) ) {
            llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION,<0.0,0.0,0.0>);
        }
        //sail/throttle controls
        if ( (held & CONTROL_FWD) && (held & CONTROL_UP) ) {
            if (sailing) llMessageLinked(LINK_THIS,SAIL_MODULE,"fwd_big",NULL_KEY);
            else {
                currSpeed+=0.3;
                if (currSpeed>maxSpeedMotor) currSpeed=maxSpeedMotor;
                llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION,<currSpeed,0,0>);
                llMessageLinked(LINK_THIS,MOTOR_MODULE,"fwd",NULL_KEY);                
            }
        }
        else if ( (held & CONTROL_FWD) || (change & held & CONTROL_FWD) ) {
            if (sailing) llMessageLinked(LINK_THIS,SAIL_MODULE,"fwd_small",NULL_KEY);
            else {
                currSpeed+=0.3;
                if (currSpeed>maxSpeedMotor) currSpeed=maxSpeedMotor;
                llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION,<currSpeed,0,0>);
                llMessageLinked(LINK_THIS,MOTOR_MODULE,"fwd",NULL_KEY);                
            }
        }
        else if ( (held & CONTROL_BACK) && (held & CONTROL_UP) ) {
            if (sailing) llMessageLinked(LINK_THIS,SAIL_MODULE,"back_big",NULL_KEY);
            else {
                currSpeed-=0.3;
                if (currSpeed<-2) currSpeed=-2;
                llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION,<currSpeed,0,0>);
                llMessageLinked(LINK_THIS,MOTOR_MODULE,"back",NULL_KEY);                  
            }
        }
        else if ( (held & CONTROL_BACK) || (change & held & CONTROL_BACK) ) {
            if (sailing) llMessageLinked(LINK_THIS,SAIL_MODULE,"back_small",NULL_KEY);
            else {
                currSpeed-=0.3;
                if (currSpeed<-2) currSpeed=-2;
                llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION,<currSpeed,0,0>);
                llMessageLinked(LINK_THIS,MOTOR_MODULE,"back",NULL_KEY);                  
            }
        }
        //motor to idle
        if ( (change & held & CONTROL_DOWN ) ) {
            if (motoring) {
                currSpeed=0.0;
                llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION,<0,0,0>);
                llMessageLinked(LINK_THIS,MOTOR_MODULE,"idle",NULL_KEY);                
            }
        }
    }    
    
    link_message(integer from,integer to,string str,key id) {
        if (to==CONTROLS_MODULE) {
            dataList=llCSV2List(str);
            currSpeed=llList2Float(dataList,0);
            leeway=llList2Float(dataList,1);
            rotSpeed=rotTweak*llList2Float(dataList,2);
            heelTorque=5*llList2Float(dataList,3);
            llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION,<currSpeed,leeway,0>);
            llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION,<heelTorque,0.0,0.0>);
        }
    }

}

The new "Sail" script (goes in the root prim):

//Flying Tako Sail Script
//by Kanker Greenacre
//July 2005

//version settings
integer raceVersion=TRUE; integer practiceVersion=FALSE; integer motorVersion=FALSE; integer olympicVersion=FALSE;
//integer practiceVersion=TRUE; integer raceVersion=FALSE; integer motorVersion=FALSE; integer olympicVersion=FALSE;
//integer olympicVersion=TRUE; integer motorVersion=FALSE; integer practiceVersion=FALSE; integer raceVersion=FALSE;
string boatName="Flying Tako ";
string versionNumber="2.1";

//script module flags
integer CONTROLS_MODULE=1;
integer SAIL_MODULE=2;
integer MOTOR_MODULE=3;
integer COLORIZER_MODULE=4;

//environment
vector wind;
float windAngle;
float absWindAngle;
float depth;
float seaLevel;

//reused math variables
vector eulerRot;
vector currEuler;
rotation quatRot;
rotation currRot;

//boat variables
float zRotAngle;
vector fwdVec;
vector upVec;
vector leftVec;
float compass;

//heeling variables
float heelAngle;
float heelTorque;
float heelAdd;

//linear motion variables
float currSpeed;
vector groundSpeed=ZERO_VECTOR;
float spdFactor=0.0;
float leeway;

//angular motion variables
float rotSpeed;
float rotDelta;
vector eulerTurnLeft;
vector eulerTurnRight;
rotation quatTurnLeft=ZERO_ROTATION;
rotation quatTurnRight=ZERO_ROTATION;

//sail variables
integer boomAngle;
integer currBoomAngle=0;
integer delta;
integer incr;
float optBoomAngle;
float trimFactor;

//performance constants
float timerFreq=1.5;        //timer frequency, seconds
float maxSpeed=10.0;        //maximum allowed speed
integer sheetAngle=5;       //initial sheet angle setting
integer ironsAngle=35;      //this is as close as the boat can sail to the wind
float slowingFactor=0.7;    //speed drops by this factor every timerFreq seconds if sailing into the wind
float leewayTweak=0.5;      //scales leeway (0=no leeway)
float speedTweak=1.0;       //scales speed
float rotTweak=5.0;         //scales boat turning rate
float heelTweak=0.5;        //scales amount of heeling
float maxWindSpeed=14.0;    //used for heeling calculation
float windDir;              //we can set this in the practice version
float windSpeed;            //we can set this in the practice version

//motor settings
integer fromMotoring=FALSE;
float maxSpeedMotor=10.0;
float throttle=0;

//miscellaneous
string dataString;
key owner;//boat owner
key avatar;//person sitting at the helm
key kanker="7abbf31e-d601-4521-ae52-f7457d6e0012";
integer ownerVersion=TRUE;
integer SAIL_UP=FALSE;
integer permSet=FALSE;
integer HUD_ON=TRUE;
string idStr;
integer racing=FALSE;
integer numTouches=0;
integer sailing=TRUE;
integer motoring=FALSE;
float mpsToKts=1.944;
float convert=1;
string units=" m/s";
integer showKnots=FALSE;
float time;
float offset;
float theta;
integer practice=FALSE;

//linked parts
integer BOW;
integer HULL;
integer STERN;
integer SAIL;
integer WINDVANE;
integer PORTBENCH;
integer STARBOARDBENCH;
integer RUDDER;
integer TILLER;
integer CENTERBOARD;
integer FLOOR;
integer MAST;
integer TRANSOM;
integer REARFLOOR;
integer HUD;
integer NUMBER1;
integer NUMBER2;
integer PROP;

//wind algorithm parameters
float angRate=10;
float avgAng=0;
float avgSpd=5;
float halfArc=0.2;
float spdRng=3;
integer raceWindOn=FALSE;

//prevailing wind algorithm
calcWindDir() {
    time=llGetTimeOfDay();
    theta=time/14400*TWO_PI*angRate;
    offset=llSin(theta)*llSin(theta*2)*llSin(theta*9)*llCos(theta*4);
    windDir=avgAng+halfArc*offset;
    llMessageLinked(WINDVANE,2000+llRound(windDir*RAD_TO_DEG),"",NULL_KEY);
}

calcWindSpd() {
    offset=llSin(theta)*llSin(theta*4)+llSin(theta*13)/3;
    windSpeed=avgSpd+spdRng*offset;
    if (windSpeed<0) windSpeed=0;   
}

//calculate wind relative to boat
calcWindAngle() {
    if (raceWindOn) {
        calcWindDir();
        calcWindSpd();
    }
    else {
        wind=llWind(ZERO_VECTOR);
        windDir=llAtan2(-wind.y,-wind.x);//direction wind is blowing FROM
        windSpeed=llVecMag(wind);
    }
    currRot=llGetRot();
    currEuler=llRot2Euler(currRot);
    zRotAngle=currEuler.z;//boat heading
    leftVec=llRot2Left(currRot);
    windAngle=windDir-zRotAngle;
    while (windAngle>PI) windAngle-=TWO_PI;//bw -PI and PI
    while (windAngle<-PI) windAngle+=TWO_PI;//bw -PI and PI    
}

//calculate heel angle based on wind and sail settings
calcHeelAngle() {
    heelAngle=llAsin(leftVec.z);
    if (SAIL_UP)
        if (llFabs(windAngle+boomAngle)>3*DEG_TO_RAD)
            heelTorque=SAIL_UP*llSin(windAngle)*llCos(heelAngle)*PI_BY_TWO*(windSpeed/maxWindSpeed)*llCos(boomAngle*DEG_TO_RAD)*heelTweak;
        else heelTorque=0;
    else heelTorque=0;
    heelAdd=heelTorque-heelAngle;
    eulerRot=<heelAdd,0,0>;
    quatRot=llEuler2Rot(eulerRot);
}

//calculate angle of sail (or boom) based on sheet setting and the wind
calcBoomDelta() {
    boomAngle=sheetAngle;
    if (boomAngle>llFabs(windAngle*RAD_TO_DEG)) boomAngle=llRound(llFabs(windAngle*RAD_TO_DEG));
    if (windAngle<0) boomAngle*=-1;
    delta=boomAngle-currBoomAngle;
    currBoomAngle=boomAngle;
    llMessageLinked(SAIL,delta,"",NULL_KEY);//tell sail to rotate by delta
}

//calculate boat speed
calcSpeed() {
    groundSpeed=llGetVel();
    absWindAngle=llFabs(windAngle);
    if (llFabs(absWindAngle*RAD_TO_DEG-llFabs(boomAngle))<10) trimFactor=0;
    else {
        optBoomAngle=0.5*absWindAngle*RAD_TO_DEG;
        trimFactor=(90.-llFabs(optBoomAngle-llFabs(boomAngle)))/90.;
    }
    if (absWindAngle<ironsAngle*DEG_TO_RAD) currSpeed*=slowingFactor;
    else {
        if (SAIL_UP) {
            currSpeed=speedTweak*(llCos(windAngle/2.)+0.5)*windSpeed*trimFactor;
            if (currSpeed>maxSpeed) currSpeed=maxSpeed;
        }
        else currSpeed*=0.8;
    }
}

//calculate leeway (lateral drift) due to wind
calcLeeway() {
    leeway=SAIL_UP*-llSin(windAngle)*llSin(heelAngle)*windSpeed*leewayTweak;    
}

//calculate turning rate based on current speed
calcTurnRate() {
    spdFactor=llVecMag(groundSpeed)/(maxSpeed);
    //rotSpeed=0.4-(spdFactor)*0.4;
    rotSpeed=0.5+(spdFactor)/2.0;
}

//gets depth of water below boat
calcDepth() {
    depth=llWater(ZERO_VECTOR)-llGround(ZERO_VECTOR);
}

//update heads-up display (in 1st and 3rd person view)
updateHUD() {
    compass=PI_BY_TWO-zRotAngle;
    while (compass<0) compass+=TWO_PI;
    calcDepth();
    dataString ="Heading:          "     +(string)((integer)(compass*RAD_TO_DEG))+" deg\n";
    dataString+="Wind Angle:       "  +(string)((integer)(windAngle*RAD_TO_DEG))+" deg\n";
    dataString+="Wind Speed:   "  +llGetSubString((string)(windSpeed*convert),0,3)+units+"\n";
    dataString+="Ground Speed: "+llGetSubString((string)(llVecMag(groundSpeed*convert)),0,3)+units+"\n";
    dataString+="Depth:             "       +llGetSubString((string)depth,0,3)+" m\n";
    dataString+="Sail Angle:         "  +(string)(-boomAngle)+" deg\n";
    dataString+="Sheet Angle:      " +(string)sheetAngle+" deg\n\n\n\n";
    llSetText(dataString,ZERO_VECTOR,1.0);
    llMessageLinked(HUD,1001,dataString,NULL_KEY);//sends data to HUD prim above and behind the boat
}

//automatically detect link nums for each named part
getLinkNums() {
    integer i;
    integer linkcount=llGetNumberOfPrims();  
    for (i=1;i<=linkcount;++i) {
        string str=llGetLinkName(i);
        if (str=="bow") BOW=i;
        if (str=="hull") HULL=i;
        if (str=="stern") STERN=i;
        if (str=="sail") SAIL=i;
        if (str=="mast") MAST=i;
        if (str=="windindicator") WINDVANE=i;
        if (str=="floor") FLOOR=i;
        if (str=="transom") TRANSOM=i;
        if (str=="tiller") TILLER=i;
        if (str=="rudder") RUDDER=i;
        if (str=="centerboard") CENTERBOARD=i;
        if (str=="starboardbench") STARBOARDBENCH=i;
        if (str=="portbench") PORTBENCH=i;
        if (str=="rearfloor") REARFLOOR=i;
        if (str=="hud") HUD=i;
        if (str=="number1") NUMBER1=i;
        if (str=="number2") NUMBER2=i;
        if (str=="prop") PROP=i;          
    }
}

//raise sail: start timer
raiseSail() {
    SAIL_UP=TRUE;
    llOwnerSay("Raising mainsail.");
    llMessageLinked(SAIL,1002,"",NULL_KEY);
    llMessageLinked(WINDVANE,4,"",NULL_KEY);
    llSetTimerEvent(timerFreq);        
} 

//lower sail but leave physics on
lowerSail() {
    llOwnerSay("Lowering mainsail.");
    llMessageLinked(SAIL,1000,"",NULL_KEY);//lower sail w/ reset
    currBoomAngle=0;
    sheetAngle=5;
    SAIL_UP=FALSE; 
}


//////////////////////////////////////////////////////////////////////////////////////////////
//state default///////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////

default {
    
    state_entry() {
        getLinkNums();
        llSetText("",ZERO_VECTOR,1.0);
        llMessageLinked(HUD,1001,"",NULL_KEY);
        avgAng=300*DEG_TO_RAD;
        halfArc=18*DEG_TO_RAD;
    }
    
    on_rez(integer param) {
        raceWindOn=FALSE;
    }

    link_message(integer from,integer to,string msg,key id) {
        if (from==LINK_ROOT && to==SAIL_MODULE) {
            if (msg=="fwd_small") {
                sheetAngle+=2;
                if (sheetAngle>90) sheetAngle=90;     
            }
            else if (msg=="back_small") {
                sheetAngle-=2;
                if (sheetAngle<5) sheetAngle=5;       
            } 
            else if (msg=="fwd_big") {
                sheetAngle+=7;
                if (sheetAngle>90) sheetAngle=90;     
            }
            else if (msg=="back_big") {
                sheetAngle-=7;
                if (sheetAngle<5) sheetAngle=5;       
            }
            else if (llGetSubString(msg,0,4)=="sheet") {
                incr=(integer)llDeleteSubString(msg,0,4);
                sheetAngle+=incr;
                if (sheetAngle>90) sheetAngle=90;
            }
            else if (msg=="raise") raiseSail();
            else if (msg=="lower") lowerSail();
            else if (msg=="moor") {
                llSetTimerEvent(0);
                if (SAIL_UP) lowerSail();
            }
            else if (llGetSubString(msg,0,9)=="windparams") {
                if (!practiceVersion | practice) { 
                    list in=llParseString2List(msg,[" "],[""]);
                    avgAng=llList2Integer(in,1)*DEG_TO_RAD;
                    avgSpd=llList2Integer(in,2);
                    if (llGetListLength(in)==6) {
                        halfArc=llList2Integer(in,3)*DEG_TO_RAD;
                        spdRng=llList2Float(in,4);
                        angRate=llList2Integer(in,5)*10.0;
                    }
                    raceWindOn=TRUE;
                }
            }
            else if (msg=="hud") {
                if (HUD_ON) {
                    HUD_ON=FALSE;
                    llSetText("",ZERO_VECTOR,1.0);
                    llMessageLinked(HUD,1001,"",NULL_KEY);                        
                }
                else HUD_ON=TRUE;
            }
            else if (msg=="motor") {
                if (SAIL_UP) lowerSail();
                llSetTimerEvent(0);
                motoring=TRUE;
                sailing=FALSE;
            }
            else if (msg=="off") {
                llSetTimerEvent(timerFreq);
                motoring=FALSE;
                sailing=TRUE;
            }
            else if (msg=="reset") llResetScript();
            else if (msg=="knots") {
                if (showKnots==FALSE) {
                    convert=mpsToKts;
                    units=" kts";
                    showKnots==TRUE;
                }
                else {
                    convert=1;
                    units=" m/s";
                    showKnots=FALSE;
                }
            }
            else if (msg=="practice") {
                if (practice) {
                    llOwnerSay("practice wind OFF");
                    practice=FALSE;
                    raceWindOn=FALSE;
                }
                else {
                    llOwnerSay("practice wind ON");
                    practice=TRUE;
                    raceWindOn=TRUE;
                }
            }
        }        
    }
    
    timer() {
        calcWindAngle();
        if (SAIL_UP) calcBoomDelta();
        calcHeelAngle();
        calcSpeed();
        calcLeeway();
        calcTurnRate();
        if (HUD_ON) updateHUD();
        dataString=(string)currSpeed+","+(string)leeway+","+(string)rotSpeed+","+(string)heelTorque;
        llMessageLinked(LINK_THIS,CONTROLS_MODULE,dataString,NULL_KEY);
    }

}

The new "Colors" script (goes in root prim):

//Flying Tako Colors Script
//by Kanker Greenacre
//July 2005

//miscellaneous
string dataString;
key owner;//boat owner
key avatar;//person sitting at the helm
key kanker="7abbf31e-d601-4521-ae52-f7457d6e0012";
vector currentColor=<1,1,1>;

//script module flags
integer CONTROLS_MODULE=1;
integer SAIL_MODULE=2;
integer MOTOR_MODULE=3;
integer COLORIZER_MODULE=4;

//linked parts
integer BOW;
integer HULL;
integer STERN;
integer SAIL;
integer NUMBER1;
integer NUMBER2;

//select colors
list colorNames=["puke","red","baby blue","lemon","blah","hot hot pink","blue","gray",
                 "beige","schwarz","tangerine","orenji","eloise","white","trep","tan","green"];
list colorVectors=[<149,159,62>,<146,29,8>,<62,115,187>,<237,234,118>,<171,175,111>,<244,2,91>,<5,35,62>,<113,129,134>,
                   <226,182,138>,<0,0,0>,<232,92,23>,<237,115,17>,<204,0,204>,<255,255,255>,<63,95,127>,<128,64,0>,<84,126,2>];

//set hull color to one from the list
setColor(string color) {
    integer colorIndex=llListFindList(colorNames,[color]);
    vector rgb=llList2Vector(colorVectors,colorIndex)/256.;
    setColorVec(rgb);
}

//set hull color from input rgb vector
setColorVec(vector rgb) {
    currentColor=rgb;
    llSetLinkColor(BOW,rgb,ALL_SIDES);
    llSetLinkColor(HULL,rgb,ALL_SIDES);
    llSetLinkColor(STERN,rgb,ALL_SIDES);
    llSetLinkColor(NUMBER1,rgb,ALL_SIDES);
    llSetLinkColor(NUMBER2,rgb,ALL_SIDES);
}

//new random color for the hull
colorize() {
    vector color;
    color.x=(llFrand(256)/255.);
    color.y=llFrand(256)/255.;
    color.z=llFrand(256)/255.;
    setColorVec(color);
}

//automatically detect link nums for each named part
getLinkNums() {
    integer i;
    integer linkcount=llGetNumberOfPrims();  
    for (i=1;i<=linkcount;++i) {
        string str=llGetLinkName(i);
        if (str=="bow") BOW=i;
        if (str=="hull") HULL=i;
        if (str=="stern") STERN=i;
        if (str=="sail") SAIL=i;
        if (str=="number1") NUMBER1=i;
        if (str=="number2") NUMBER2=i;
    }
}

default {
    
    state_entry() {
        getLinkNums();    
    }  

    on_rez(integer param) {
        llResetScript();
    }
    
    link_message(integer from,integer to,string msg,key id) {
        if (from==LINK_ROOT && to==COLORIZER_MODULE) {
            if (llGetSubString(msg,0,4)=="color") {
                if (llGetSubString(msg,6,-1)=="random") colorize();
                else if (llGetSubString(msg,6,6)=="<") setColorVec((vector)llGetSubString(msg,6,-1));
                else setColor(llGetSubString(msg,6,-1));
            }
            else if (llGetSubString(msg,0,4)=="alpha") {
                integer num=(integer)llGetSubString(msg,6,-1);
                num+=2000;
                llMessageLinked(SAIL,num,"",NULL_KEY);
            }
            else if (msg=="reset") llResetScript();
            else if (llGetSubString(msg,0,1)=="id") {
                if (llGetSubString(msg,3,-1)=="off") {
                    llMessageLinked(NUMBER1,1001,"",NULL_KEY);
                    llMessageLinked(NUMBER2,1001,"",NULL_KEY);
                }
                else {
                    string idStr=llGetSubString(msg,3,-1);
                    llMessageLinked(NUMBER1,1000,idStr,NULL_KEY);
                    llMessageLinked(NUMBER2,1000,idStr,NULL_KEY);
                    llSetLinkColor(NUMBER1,currentColor,ALL_SIDES);
                    llSetLinkColor(NUMBER2,currentColor,ALL_SIDES);
                }
            }
        }   
    }

}

Here is the other sail script for rotating the sail and setting textures (goes in the sail prim):

rotation childRot;
rotation quatRot;
rotation initRot;
vector eulerRot;

integer i;
key defaultFurledKey="f859c1a2-8fa4-5a6d-aa8b-15b81ef303a4";
key defaultRaisedRightKey="4922b72e-6c1c-3808-930e-3531b7af07c4";
key defaultRaisedLeftKey="4922b72e-6c1c-3808-930e-3531b7af07c4";
key furledKey;
key raisedRightKey;
key raisedLeftKey;

integer foundFurled;
integer foundRaisedRight;
integer foundRaisedLeft;

lower() {
    llSetLocalRot(initRot);
    llSetTexture(furledKey,1);//left side
    llSetTexture(furledKey,3);//right side    
}

raise () {
    llSetTexture(raisedLeftKey,1);
    llSetTexture(raisedRightKey,3);    
}

reset() {
    llSetLocalRot(initRot);    
    llSetAlpha(1.0,ALL_SIDES);
}

default {
    
    state_entry() {
        furledKey=defaultFurledKey;
        raisedRightKey=defaultRaisedRightKey;
        raisedLeftKey=defaultRaisedLeftKey;
        llSetObjectName("sail");
        initRot=llEuler2Rot(<0,0,PI_BY_TWO>);
        llSetLocalRot(initRot);
        llSetTexture("transparent",ALL_SIDES);
        llSetTexture(furledKey,1);//left side
        llSetTexture(furledKey,3);//right side
        llSetAlpha(1.0,ALL_SIDES);
        llSetStatus(STATUS_PHANTOM,FALSE);       
    }
    
    on_rez(integer param) {
        llAllowInventoryDrop(TRUE);
    }
    
    link_message(integer sender,integer num,string str,key id) {
        if (num==1000) {//reset
            lower();
            reset();
        }
        else if (num==1001) {//lower without reset
            lower();
        }
        else if (num==1002) {//raise sail
            raise();
        }
        else if (num>1999) {//change alpha setting
            float alpha=(float)num;
            alpha-=2000;
            alpha/=100.;
            llSetAlpha(alpha,1);
            llSetAlpha(alpha,3);
        }   
        else {//rotate sail, CCW is positive
            childRot=llGetLocalRot();
            eulerRot=<0,0,num*DEG_TO_RAD>;
            quatRot=llEuler2Rot(eulerRot);
            llSetLocalRot(quatRot*childRot);
        }     
    }
    
    changed(integer change){
        if( (change & CHANGED_INVENTORY) | (change & CHANGED_ALLOWED_DROP) ) {
            integer numInvTex=llGetInventoryNumber(INVENTORY_TEXTURE);
            foundFurled=FALSE;
            foundRaisedRight=FALSE;
            foundRaisedLeft=FALSE;
            for (i=0;i<numInvTex;++i) {
                string name=llGetInventoryName(INVENTORY_TEXTURE,i);
                if (name=="sail_furled") {
                    furledKey=llGetInventoryKey(name);
                    foundFurled=TRUE;
                    llOwnerSay("New furled sail texture: "+(string)furledKey);
                    lower();
                }
                else if (name=="sail_raised_right") {
                    raisedRightKey=llGetInventoryKey(name);
                    foundRaisedRight=TRUE;
                    llOwnerSay("New raised sail texture: "+(string)raisedRightKey);
                    raise();
                }
                else if (name=="sail_raised_left") {
                    raisedLeftKey=llGetInventoryKey(name);
                    foundRaisedLeft=TRUE;
                    llOwnerSay("New raised sail texture: "+(string)raisedLeftKey);
                    raise();
                }
            }
            if (!foundFurled) furledKey=defaultFurledKey;
            if (!foundRaisedRight) raisedRightKey=defaultRaisedRightKey;
            if (!foundRaisedLeft) raisedLeftKey=defaultRaisedLeftKey;
        }
    }
    
}

A word or two about the "Flying Tako" sail (an Ezhar Fairlight-inspired design). The sail is a prim box with the following settings: size x: 0.01, size y: 7.0, size z: 7.0, cut begin: 0.37, cut end: 0.87, top size x: 1.0, top size y: 1.0. By cutting the box this way, we have created a flat shape with an axis of rotation along the side, much like Ezhar's Deluxe Door Script. This greatly simplifies script rotations, as you may have noticed in the sail script.


Wind indicator script (goes in ball at top of mast, or wherever you want to put it):

vector windDir;
list particleVars=[PSYS_PART_FLAGS,
            PSYS_PART_FOLLOW_SRC_MASK,  
            PSYS_SRC_PATTERN,
            PSYS_SRC_PATTERN_DROP,
            PSYS_PART_MAX_AGE,3.0,
            PSYS_PART_START_COLOR,<1,1,1>,
            PSYS_PART_START_ALPHA,1.0,
            PSYS_PART_START_SCALE,<.3,.3,.3>,
            PSYS_SRC_BURST_RATE,0.1,
            PSYS_SRC_BURST_SPEED_MIN,0.5,
            PSYS_SRC_BURST_SPEED_MAX,0.5,
            PSYS_SRC_BURST_PART_COUNT,1];
list tmpVars;
integer fixed=FALSE;
            
setParticles() {
    windDir=llWind(<0,0,0>);
    windDir*=0.15;
    tmpVars=particleVars;
    tmpVars+=[PSYS_SRC_ACCEL,windDir];
    llParticleSystem(tmpVars);
}   

default {
    state_entry() {
        llSetObjectName("windindicator");
        llParticleSystem([]);
        llSetTimerEvent(4.0);
    }

    timer() {
        setParticles();
    }
    
    link_message(integer sender_num, integer num, string str, key id) {
        if (num==1000) {
            llParticleSystem([]);
            llSetTimerEvent(0);
        }
        else if (num>1000) {
            llSetTimerEvent(0);
            num-=2000;
            float windAng=num*DEG_TO_RAD;
            windAng+=PI;
            windDir=<llCos(windAng),llSin(windAng),0>;
            windDir*=0.5;
            tmpVars=particleVars;
            tmpVars+=[PSYS_SRC_ACCEL,windDir];
            llParticleSystem(tmpVars);
            fixed=TRUE;
        }   
        else {
            llSetTimerEvent((float)num);
            fixed=FALSE;
        }
    }
}
There is no comment on this page. [Display comments/form]