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