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; n1; --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); } } }