Command Script

This is a script that can be used for animated "vehicles" like a horse:

 

// Command
//
// Control script for single link set synchronized animation
// Author: Jesrad Seraph

// This script has the vehicle parameters and the controls event for your mount.
// It calculates the animation step needed after the rider control inputs
// then sends it to the animated parts of the link_set, who each must have a Movement
// script to listen to these messages and move.

// These are the constants you need to change to adapt this script to another beast:
integer max_idle_anims = 1;
integer max_walk_anims = 2;
integer max_run_anims = 4;
integer max_jump_anims = 4;
// Copy these constants in the Movement scripts for the mobile parts of your beast,
// and change the rotations for the steps (the number of rotations in the lists must
// match the max_anims numbers entered above !)

integer RUNNING;
integer FWD;
integer TURNING;
integer IS_JUMPING;

vector fwd_vel = <16,0,0>;
vector left_vel = <0,0,-4>;
vector jump_vel = <32,0,32>;

integer step;
float timerate = 0.175;

integer chan;
integer max_listeners = 2;
integer ALTERN = 0;

integer roam_state = 0;
vector roam_around;
float roam_range = 8.0;
float follow_range = 200.0;
float roam_delay = 4.0;
key roam_target;

vector PUSH_OFF = <0,9,8>;
vector sit_pos = <0.9,0,0.5>;
vector sit_rot = <0,0,0>;

integer menu_handle;
integer menu_channel;

default
{
    control(key id, integer level, integer edge)
    {
        if(level & CONTROL_FWD)
        {
            FWD = 1;
            llSetStatus(STATUS_PHYSICS, TRUE);
            if (RUNNING > 0)
            {
                llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, fwd_vel);
            } else {
                llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, 0.6 * fwd_vel);
            }
        } else if(level & CONTROL_BACK)
        {
            FWD = -1;
            if (RUNNING > 0)
            {
                llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, -1.0 * fwd_vel);
            } else {
                llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, -0.4 * fwd_vel);
            }
        } else {
            FWD = 0;
            llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, ZERO_VECTOR);
        }

        if(level & (CONTROL_RIGHT|CONTROL_ROT_RIGHT))
        {
            TURNING = -1;
            if (RUNNING > 0)
            {
                llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION, 0.6 * left_vel);
            } else {
                llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION, left_vel);
            }
        } else if(level & (CONTROL_LEFT|CONTROL_ROT_LEFT))
        {
            TURNING = 1;
            if (RUNNING > 0)
            {
                llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION, -0.6 * left_vel);
            } else {
                llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION, -1.0 * left_vel);
            }
        } else {
            TURNING = 0;
            llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION, ZERO_VECTOR);
        }

        if( (level & (CONTROL_UP)) && (edge & (CONTROL_UP)) )
        {
            IS_JUMPING = max_jump_anims;
            llApplyImpulse(jump_vel * llGetMass(), TRUE);
        }

        if( (edge & (CONTROL_DOWN)) && (level & (CONTROL_DOWN)) )
        {
            if (RUNNING == 0)
            {
                llStartAnimation("motorcycle_sit");
                RUNNING = 1;
            } else {
                llStopAnimation("motorcycle_sit");
                RUNNING = 0;
            }
        }
    }

    timer()
    {
        // Steps:
        //
        // gets the next step and sends it along with posible turning parameter

        // all these calculations could be replaced by a sync'd timer in the Movement script...

        integer temp; // anim offset, to save calculations below
        
        if (IS_JUMPING > 0)
        {
            llSetTimerEvent(timerate);
            IS_JUMPING -= 1;
            temp = max_idle_anims + max_walk_anims + max_run_anims;
            if (step < temp)
            {
                step = temp;
            } else {
                step = ((step - temp + 1) % max_jump_anims) + temp;
            }
        } else {
            if ((FWD == 0) && (TURNING == 0))
            {
                // idle animation
                llSetTimerEvent(2*timerate); // saves on lag and helps sync the parts
    
                if (step >= max_idle_anims)
                {
                    // was not idle at last step
                    step = 0;
                } else {
                    // cycling through idle anims
                    step = (step + 1) % max_idle_anims;
                }
            } else {
                llSetTimerEvent(timerate);
    
                if ((RUNNING == 0) || (FWD == 0))
                {
                    // walking animation
    
                    temp = max_idle_anims + max_walk_anims;
                    if ((step < max_idle_anims) || (step >= temp))
                    {
                        // was not walking at last step
                        step = max_idle_anims;
                    } else {
                        step = ((step - max_idle_anims + 1) % max_walk_anims) + max_idle_anims;
                    }
                } else {
                    temp = max_idle_anims + max_walk_anims;
                    if((step < temp) || (step >= temp + max_run_anims))
                    {
                        step = temp;
                    } else {
                        step = ((step - temp + 1) % max_run_anims) + temp;
                    }
                }
            }
        }
        ALTERN = (ALTERN + 1) % max_listeners;
        llSay(chan + ALTERN, (string)step);
    }
    
    changed(integer change)
    {
        if (change & CHANGED_LINK)
        {
            key agent = llAvatarOnSitTarget();
            if (agent)
            {
                if (agent != llGetOwner())
                {
                    llUnSit(agent);
                    llPushObject(agent, PUSH_OFF * llGetRot(), ZERO_VECTOR, FALSE);
                    llPlaySound("cui", 1);
                    llSay(0, "Wah!");
                }
                else
                {
                    llSensorRemove();
                    roam_state = 0;
                    llRequestPermissions(agent, PERMISSION_TRIGGER_ANIMATION | PERMISSION_TAKE_CONTROLS);
                    llCollisionSound("", 0);
                    llPlaySound("wark", 1);
                    llSay(0, "Wark.");
                    llSetStatus(STATUS_PHYSICS, TRUE);
                    llStopLookAt();
                }
            }
            else
            {
                llReleaseControls();
                llStopAnimation("motorcycle_sit");
                llSetStatus(STATUS_PHYSICS, FALSE);
                llPushObject(llGetOwner(), PUSH_OFF, ZERO_VECTOR, TRUE);
                roam_state = 0;
            }
        }
    }

    on_rez(integer a)
    {
        llSleep(1);
        llResetScript();
    }

    state_entry()
    {
        llSetSitText("Ride");
        llSetTouchText("Cuddle");
        llSitTarget(sit_pos, llEuler2Rot(DEG_TO_RAD * sit_rot));

        llSetCameraEyeOffset(<-6.0, 0.0, 2.0>);
        llSetCameraAtOffset(<2.0, 0.0, 1.0>);

        roam_around = llGetPos();
        roam_target = llGetOwner();

        RUNNING = 0;
        FWD = 0;
        TURNING = 0;
        IS_JUMPING = 0;

        llSetVehicleFlags(-1);
        llSetVehicleType(VEHICLE_TYPE_CAR);

        llRemoveVehicleFlags(VEHICLE_FLAG_LIMIT_MOTOR_UP | VEHICLE_FLAG_LIMIT_ROLL_ONLY);
        llSetVehicleFloatParam(VEHICLE_ANGULAR_DEFLECTION_EFFICIENCY, 0.8);
        llSetVehicleFloatParam(VEHICLE_LINEAR_DEFLECTION_EFFICIENCY, 0.8);
        llSetVehicleFloatParam(VEHICLE_ANGULAR_DEFLECTION_TIMESCALE, .1);
        llSetVehicleFloatParam(VEHICLE_LINEAR_DEFLECTION_TIMESCALE, .1);
         
        llSetVehicleFloatParam(VEHICLE_LINEAR_MOTOR_TIMESCALE, .1);
        llSetVehicleFloatParam(VEHICLE_LINEAR_MOTOR_DECAY_TIMESCALE, .1);
        llSetVehicleFloatParam(VEHICLE_ANGULAR_MOTOR_TIMESCALE, .1);
        llSetVehicleFloatParam(VEHICLE_ANGULAR_MOTOR_DECAY_TIMESCALE, .1);
        
        llSetVehicleVectorParam(VEHICLE_LINEAR_FRICTION_TIMESCALE, <1.0, .1, 1000.0>);
        llSetVehicleVectorParam(VEHICLE_ANGULAR_FRICTION_TIMESCALE, <.1, .1, .1>);
        
        llSetVehicleFloatParam(VEHICLE_VERTICAL_ATTRACTION_EFFICIENCY, .8);
        llSetVehicleFloatParam(VEHICLE_VERTICAL_ATTRACTION_TIMESCALE, .1);

        llSetVehicleFloatParam(VEHICLE_BANKING_EFFICIENCY, 0.25);
        llSetVehicleFloatParam(VEHICLE_BANKING_TIMESCALE, 0.1);

        chan = (integer)llFrand(200000000) + 54321;
        llMessageLinked(LINK_SET, chan, "defchan", "");
        llSensorRemove();
        llSetTimerEvent(timerate);
    }

    listen(integer chan, string name, key sayer, string msg)
    {
        if (sayer != llGetOwner())
            return;
        llSetText("", ZERO_VECTOR, 0.0);
        if (msg == "Help")
        {
            llGiveInventory(sayer, "Instructions");
        } else if (msg == "Stay here")
        {
            roam_state = 0;
            llSensorRemove();
        } else if (msg == "Wander")
        {
            roam_state = 1;
            roam_around = llGetPos();
            llSensorRepeat("", NULL_KEY, PASSIVE | ACTIVE, roam_range, TWO_PI, roam_delay - llFrand(roam_delay / 2));
        } else if (msg == "Follow me")
        {
            roam_state = 2;
            roam_target = llGetOwner();
            llSensorRepeat("", roam_target, AGENT | ACTIVE, follow_range, TWO_PI, roam_delay - llFrand(roam_delay / 2));
        }
    }

    sensor(integer count)
    {
        vector facing;
        vector dir;
        float dist;
        integer n;
        integer selected;

        if (roam_state == 2)
        {
            // Following, found owner
            dir = llDetectedPos(0) - llGetPos();
            facing = llRot2Euler(llRotBetween(llRot2Fwd(llGetRot()), dir));
            facing.x = 0.0;
            facing.y = 0.0;
            llRotLookAt(llGetRot() * llEuler2Rot(facing), 1.0, 0.2);
            llSleep(0.2);
            dist = llVecMag(dir);
            if (dist > 4.0)
            {
                llSetStatus(STATUS_PHYSICS, TRUE);
                for (selected = llFloor(dist); selected > 1; --selected)
                {
                    llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, 0.5 * fwd_vel);
                    ALTERN = (ALTERN + 1) % max_listeners;
                    llSay(chan + ALTERN, (string)((selected % max_walk_anims) + max_idle_anims));
                    llSleep(timerate);
                }
                llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, ZERO_VECTOR);
                llSetStatus(STATUS_PHYSICS, FALSE);
            }
            llStopLookAt();
        } else if (roam_state == 1)
        {
            // Wandering, found obstacle
            dist = roam_range + 1;
            // select the closest obstacle
            for(n=0; n 1; --selected)
            {
                llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, 0.5 * fwd_vel);
                ALTERN = (ALTERN + 1) % max_listeners;
                llSay(chan + ALTERN, (string)((selected % max_walk_anims) + max_idle_anims));
                llSleep(timerate);
            }
            llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, ZERO_VECTOR);
            llSetStatus(STATUS_PHYSICS, FALSE);
            llStopLookAt();
        }
    }

    no_sensor()
    {
        vector facing;
        vector dir;
        float dist;
        integer selected;

        if (roam_state == 2)
        {
            // Following, did not found owner
            llInstantMessage(llGetOwner(), "HELP I am lost in " + llGetRegionName() + " at " + (string)llGetPos());
            llSetText("I'm a poor, stranded " + llGetObjectName() + " who lost its owner...", <1,.7,.4>, 1.0);
            llSensorRemove();
        } else if (roam_state == 1)
        {
            // Wandering, no obstacles
            facing.x = 0.0;
            facing.y = 0.0;
            facing.z = llFrand(TWO_PI) - PI;
            dir = (((llCeil(llGetTime()) % llCeil(roam_range)) + 1) * facing) * llGetRot();
            llRotLookAt(llGetRot() * llEuler2Rot(facing), 1.0, 0.2);
            llSleep(0.2);
            dist = llVecMag(dir);
            if (dist < 1.0)
            {
                dir = roam_around - llGetPos();
            }
            llSetStatus(STATUS_PHYSICS, TRUE);
            for (selected = llFloor(dist); selected > 1; --selected)
            {
                llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, fwd_vel);
                ALTERN = (ALTERN + 1) % max_listeners;
                llSay(chan + ALTERN, (string)((selected % max_run_anims) + max_idle_anims + max_walk_anims) );
                llSleep(timerate);
            }
            llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, ZERO_VECTOR);
            llSetStatus(STATUS_PHYSICS, FALSE);
            llStopLookAt();
        }
    }

    touch_start(integer a)
    {
        if (llDetectedType(0) == AGENT | ACTIVE)
        {
            if (llDetectedKey(0) == llGetOwner())
            {
                llPlaySound("wark", 1);
                llSay(0, "Wark!");
                llListenRemove(menu_handle);
                menu_channel = (integer)llFrand(2000000)+1234;
                menu_handle = llListen(menu_channel, "", "", "");
                llDialog(llDetectedKey(0), "Tell " + llGetObjectName() + " to:", ["Stay here", "Wander", "Follow me", "Help"], menu_channel);
            } else {
                llPlaySound("cui", 1);
                llSay(0, "WAH!");
            }
        }
    }
    
    run_time_permissions(integer perm)
    {
        if (perm)
        {
            llTakeControls(    CONTROL_FWD |
                    CONTROL_BACK |
                    CONTROL_RIGHT |
                    CONTROL_LEFT |
                    CONTROL_ROT_RIGHT |
                    CONTROL_ROT_LEFT |
                    CONTROL_UP |
                    CONTROL_DOWN, TRUE, FALSE);
        }
    }
}