Welcome to the Second Life Forums Archive

These forums are CLOSED. Please visit the new forums HERE

Multimove hits v2.0

Jesrad Seraph
Nonsense
Join date: 11 Dec 2004
Posts: 1,463
10-21-2005 05:10
The experimental-turned-popular set of scripts for making large vehicles made of multiple objects has reached version 2 :)

What's new:
- rewritten from scratch
- slightly less laggy
- a LOT smoother
- autopilot for those big slow space cruisers
- simpler to use (= less confusing, hopefully)

Otherwise it's the same "drop in your objects and swoosh away" kind of set of scripts ;)

Notes:
Don't use autopilot AND control in the same vehicle.
"unichan" should be unique for each different vehicle, and must be the same for all the scripts across all the vehicle's objects.
You can (and should) change the settings in Control and Autopilot for horizontal, vertical and rotational speeds, sit target and camera offsets.
These scripts are delivered "as is", feel free to modify, copy and redistribute them as long as you permit free further modification, copy and redistribution.

Autopilot script (goes into an easily accessible child prim, touch it to bring up the autopilot menu):
CODE

// Multimove Autopilot
//
// For navigating big space cruisers

integer unichan = -599981;
integer listener;
integer maxl = 3;
integer my_chan;
integer handle;
integer action;
string opt;
list buttons;

integer azimut;
float vel;
float vvel;
float mul;

float speed = 1.0;
float vspeed = 1.0;
float rspeed = 3.0;
float rate = 0.15;

integer curaz;

list angles = ["225", "270", "315", "180", "back", "0", "135", "90", "45"];
list menu = ["Turn", "Move", "SelfDestruct"];
list mv = ["Forward", "Stop", "Back", "Up", "Level", "Down", "Faster", "Slower"];

doMenu()
{
opt = "";
buttons = [];

if (action == 0) { opt = "Commanding:\nSpeed: " + (string)speed; buttons = menu; } else
if (action == 1) { opt = "Turning to:\nCurrent: " + (string)azimut; buttons = angles; } else
if (action == 2) { opt = "Moving at:\nHorizontal: " + (string)((integer)(vel*mul*100)) + "%\nVertical: " + (string)((integer)(vvel*mul*100)) + "%"; buttons = mv; } else
if (action == 3) { opt = "Do you confirm SELF DESTRUCTION ?"; buttons = ["YES", "NO"]; }

llDialog(llGetOwner(), opt, buttons, my_chan);
}

default
{
on_rez(integer p)
{
llSleep(2.0);
llResetScript();
}

state_entry()
{
llSetTimerEvent(0.0);
azimut = 0;
curaz = 0;
vel = 0.0;
vvel = 0.0;
mul = 1.0;
action = 0;
opt = "";
buttons = [];
llSetText(llGetRegionName() + " @ " + (string)llGetRootPosition(), <1,1,0>, 1.0);
for (listener = 0; listener < maxl; listener = listener + 1)
llShout(unichan + listener, (string)(llGetRootPosition() + llGetRegionCorner()) + "*0");
}

touch_start(integer c)
{
if (llDetectedKey(0) != llGetOwner()) return;

llListenRemove(handle);
my_chan = (integer)llFrand(llAbs(unichan) - 20) + 19;
handle = llListen(my_chan, "", llGetOwner(), "");

doMenu();
}

listen(integer chan, string name, key id, string mesg)
{
if (action == 0)
{
if (mesg == "Turn") { action = 1; doMenu(); } else
if (mesg == "Move") { action = 2; doMenu(); } else
if (mesg == "SelfDestruct") { action = 3; doMenu(); }
} else if (action == 1)
{
action = 0;
if (mesg == "back") { doMenu(); } else
{ azimut = (integer)mesg; llSetTimerEvent(rate); llOwnerSay("Course set to " + mesg); }
} else if (action == 2)
{
action = 0;
if (mesg == "Forward") { vel = speed; mul = 1.0; llSetTimerEvent(rate); llOwnerSay("Engines engaged."); } else
if (mesg == "Stop") { vel = 0.0; llOwnerSay("Engines stopped."); } else
if (mesg == "Back") { vel = -speed; mul = 1.0; llSetTimerEvent(rate); llOwnerSay("Engines reversed."); } else
if (mesg == "Up") { vvel = vspeed; mul = 1.0; llSetTimerEvent(rate); llOwnerSay("Engines engaged."); } else
if (mesg == "Level") { vvel = 0.0; llOwnerSay("Engines stopped."); } else
if (mesg == "Down") { vvel = -vspeed; mul = 1.0; llSetTimerEvent(rate); llOwnerSay("Engines reversed."); } else
if (mesg == "Faster") { mul *= 2.0; llOwnerSay("Engine power now " + (string)((integer)(mul*100)) + "%"); } else
if (mesg == "Slower") { mul /= 2.0; llOwnerSay("Engine power now " + (string)((integer)(mul*100)) + "%"); }
} else if (action == 3)
{
if (mesg == "YES") { llSetTimerEvent(0.0); llShout(unichan, "k"); } else action = 0;
}
}

timer()
{
vector pos = vel*mul*llRot2Fwd(llGetRootRotation()) + vvel*mul*llRot2Up(llGetRootRotation()) + llGetRootPosition();
listener = (listener + 1) % maxl;
if (curaz != azimut)
{
integer doaz = azimut - curaz;
if (llAbs(doaz) <= (integer)rspeed)
{
curaz = azimut;
} else {
if(((doaz > 0) && (doaz < 180)) || (doaz < -180))
{
curaz = (curaz + (integer)(mul*rspeed)) % 360;
} else curaz = (curaz - (integer)(mul*rspeed)) % 360;
if (curaz < 0) curaz += 360;
}
} else {
if ((vel == 0.0) && (vvel == 0.0)) llSetTimerEvent(0.0);
}
llShout(unichan + listener, (string)(pos + llGetRegionCorner()) + "*" + (string)curaz);
llSetText(llGetRegionName() + " @ " + (string)pos, <1,1,0>, 1.0);
}
}


Control script (goes into the "pilot seat" child prim, tweak the constants at the beginning to suit your vehicle)
CODE

// MultiMove Controller Script
//
// Manages pilot seat and movement for the whole set

integer unichan = -599981; // this is the channel used by the controller and
// objects to sync and move

float deltat = 0.15; // interval between updates

integer controlstaken; // control keys taken by the script, initialised later

integer listener;
integer maxl = 3;

vector sitoffset = <0.2,0,-0.5>; // sitting position
vector camoffset = <-24,0,9>; // camera position
vector camtarget = <12,0,0>; // camera direction
float speed = 1.0; // distance to move within deltat
float vspeed = 0.5; // vertical distance to move within deltat
integer rspeed = 3; // angle in degrees to rotate left or right within deltat

integer azimut; // direction in degrees around the Z axis (vertical)

default
{
on_rez(integer c)
{
llResetScript();
}

state_entry()
{
llMinEventDelay(deltat);
llSitTarget(sitoffset, ZERO_ROTATION);
llSetCameraAtOffset(camtarget);
llSetCameraEyeOffset(camoffset);
controlstaken = CONTROL_FWD|CONTROL_BACK|CONTROL_ROT_LEFT|CONTROL_ROT_RIGHT|CONTROL_LEFT|CONTROL_RIGHT|CONTROL_UP|CONTROL_DOWN;
}

changed(integer c)
{
if (c & CHANGED_LINK)
{
key id = llAvatarOnSitTarget();
if (id != NULL_KEY)
{
if (id == llGetOwner())
{
llRequestPermissions(id, PERMISSION_TAKE_CONTROLS);
} else llUnSit(id);
} else {
llReleaseControls();
}
}
}

run_time_permissions(integer p)
{
if (p & PERMISSION_TAKE_CONTROLS)
{
integer n;
for (n=0; n<maxl; ++n)
llShout(unichan + n, (string)(llGetRootPosition() + llGetRegionCorner()) + "*" + (string)azimut);

llSleep(1.0);
llTakeControls(controlstaken, TRUE, FALSE);
} else {
llReleaseControls();
llShout(unichan, (string)llGetRootPosition() + "*" + (string)azimut);
}
}

control(key id, integer pressed, integer change)
{
vector target = llGetRootPosition() + llGetRegionCorner();
rotation rtarget = llGetRootRotation();

if (pressed & (CONTROL_LEFT|CONTROL_ROT_LEFT))
{
// turning left
azimut = (azimut + rspeed) % 360;
rtarget *= llEuler2Rot(<0,0,DEG_TO_RAD * (float)rspeed>);
} else if (pressed & (CONTROL_RIGHT|CONTROL_ROT_RIGHT))
{
// turning right
azimut = (azimut - rspeed) % 360;
rtarget *= llEuler2Rot(<0,0,-DEG_TO_RAD * (float)rspeed>);
}

if (pressed & CONTROL_FWD)
{
// going forward
target += speed * llRot2Fwd(rtarget);

} else if (pressed & CONTROL_BACK)
{
// going backward
target += -speed * llRot2Fwd(rtarget);
}

if (pressed & CONTROL_UP)
{
// going forward
target += vspeed * llRot2Up(rtarget);

} else if (pressed & CONTROL_DOWN)
{
// going backward
target += -vspeed * llRot2Up(rtarget);
}

if (pressed | change)
{
listener = (listener + 1) % maxl;
llShout(unichan + listener, (string)target + "*" + (string)azimut);
}
}
}


And finally, the Move script (copy three times into Move0, Move1 and Move2 and put all three in each of your vehicle's objects):
CODE

// Move
//
// Multimove follower

integer unichan = -599981; // this is the channel used by the controller and
// objects to sync

integer handle; // handle for the listen function

vector my_offset; // original offset, set at first activation
rotation my_orientation; // original rotation, set at first activation
integer my_num; // position in the chain of redundancy

default
{
on_rez(integer p)
{
llResetScript();
}

state_entry()
{
my_num = (integer)llGetSubString(llGetScriptName(), -1, -1);
handle = llListen(unichan + my_num, "", "", "");
}

listen(integer chan, string name, key id, string mesg)
{
if (mesg == "k") llDie();

integer index = llSubStringIndex(mesg, "*");

my_offset = llGetPos() + llGetRegionCorner() - (vector)llGetSubString(mesg, 0, index - 1);
my_orientation = llGetRot();
my_offset = my_offset / my_orientation;
state running;
}

state_exit()
{
llListenRemove(handle);
}
}

state running
{
on_rez(integer p)
{
state default;
}

state_entry()
{
handle = llListen(unichan + my_num, "", "", "");
}

listen(integer chan, string name, key id, string mesg)
{
if (mesg == "k") llDie();

list info = llParseString2List(mesg, ["*"], []);
rotation rtarget = my_orientation * llEuler2Rot(<0,0,(float)((integer)llList2String(info, 1)) * DEG_TO_RAD>);
vector target = my_offset * rtarget + (vector)llList2String(info, 0) - llGetRegionCorner();
llSetPrimitiveParams([PRIM_POSITION, target, PRIM_ROTATION, rtarget]);
}

state_exit()
{
llListenRemove(handle);
}
}
_____________________
Either Man can enjoy universal freedom, or Man cannot. If it is possible then everyone can act freely if they don't stop anyone else from doing same. If it is not possible, then conflict will arise anyway so punch those that try to stop you. In conclusion the only strategy that wins in all cases is that of doing what you want against all adversity, as long as you respect that right in others.
Jesrad Seraph
Nonsense
Join date: 11 Dec 2004
Posts: 1,463
10-26-2005 09:24
Tested successfully in v1.7 :)

Also, here's a remote control. Use it the same way you'd use the autopilot, except you speak to it directly on chanel 128. Format of commands:
"m1" for forward, "m0" for stop, "m-1" for backward
"t0" for heading East, "t90" for heading North, etc...
"v1" for moving up, "v0" for level, "v-1" for moving down
"f" for twice faster movement, "s" for half as fast movement

Yes, it's not really easy. That's because it's meant to be used with a TLTP browser/server combo that I'll release soon ;) Then you can pilot your ship from the HUD.

CODE

// Multimove Autopilot
//
// For navigating big space cruisers

integer unichan = -599981;
integer listener;
integer maxl = 3;
integer my_chan = 128;
integer handle;
integer action;
string opt;
list buttons;

integer azimut;
float vel;
float vvel;
float mul;

float speed = 1.0;
float vspeed = 1.0;
float rspeed = 3.0;
float rate = 0.15;

integer curaz;

default
{
on_rez(integer p)
{
llSleep(2.0);
llResetScript();
}

state_entry()
{
llSetTimerEvent(0.0);
azimut = 0;
curaz = 0;
vel = 0.0;
vvel = 0.0;
mul = 1.0;
action = 0;
opt = "";
buttons = [];
for (listener = 0; listener < maxl; listener = listener + 1)
llShout(unichan + listener, (string)(llGetRootPosition() + llGetRegionCorner()) + "*0");
handle = llListen(my_chan, "", "", "");
}

listen(integer chan, string name, key id, string msg)
{
if ((llGetOwner() != id) && (llGetOwnerKey(id) != llGetOwner()) return;

string t = llGetSubString(msg, 0, 0);

if (t == "t") { azimut = (integer)llDeleteSubString(msg, 0, 0); llSetTimerEvent(rate); llOwnerSay("Course set to " + (string)azimut + " degree"); } else
if (t == "s") { mul /= 2.0; llOwnerSay("Engine power now " + (string)((integer)(mul*100)) + "%"); } else
if (t == "f") { mul *= 2.0; llOwnerSay("Engine power now " + (string)((integer)(mul*100)) + "%"); } else
if (t == "m") { vel = (integer)llDeleteSubString(msg, 0, 0); llSetTimerEvent(rate); llOwnerSay("Engine set to " + (string)((integer)vel) + " m/s"); } else
if (t == "v") { vvel = (integer)llDeleteSubString(msg, 0, 0); llSetTimerEvent(rate); llOwnerSay("Vertical set to " + (string)((integer)vvel) + " m/s"); } else
if (t == "k") { llSetTimerEvent(0.0); llShout(unichan, "k"); }
}

timer()
{
vector pos = vel*mul*llRot2Fwd(llGetRootRotation()) + vvel*mul*llRot2Up(llGetRootRotation()) + llGetRootPosition();
listener = (listener + 1) % maxl;
if (curaz != azimut)
{
integer doaz = azimut - curaz;
if (llAbs(doaz) <= (integer)rspeed)
{
curaz = azimut;
} else {
if(((doaz > 0) && (doaz < 180)) || (doaz < -180))
{
curaz = (curaz + (integer)(mul*rspeed)) % 360;
} else curaz = (curaz - (integer)(mul*rspeed)) % 360;
if (curaz < 0) curaz += 360;
}
} else {
if ((vel == 0.0) && (vvel == 0.0)) llSetTimerEvent(0.0);
}
llShout(unichan + listener, (string)(pos + llGetRegionCorner()) + "*" + (string)curaz);
}
}
_____________________
Either Man can enjoy universal freedom, or Man cannot. If it is possible then everyone can act freely if they don't stop anyone else from doing same. If it is not possible, then conflict will arise anyway so punch those that try to stop you. In conclusion the only strategy that wins in all cases is that of doing what you want against all adversity, as long as you respect that right in others.
Jesrad Seraph
Nonsense
Join date: 11 Dec 2004
Posts: 1,463
11-04-2005 05:57
Here is an entirely untested version that should allow for the creation of multi-vehitar. It is a set of two scripts, control and move. Control goes into the "pilot" attachment, and move goes into one of the other AV's attachments. You should adjust positions, rotations, etc... before dropping control in the main attachment.

In simpler words: with this you should be able to make a group of people fly in a formation. This should let one build a large and complex build that is litterally "glued" onto a number of AVs. If an AV gets lost and goes too far from the group (s)he cmight get back by own means by touching the attachment and getting back near the group.

control:
CODE

// Physical MultiVehitar Controller
//
// Controls weare and other avatars' position to maintain the multiple vehitar as one

integer unichan = -779991; // this is the channel used by the controller and
// objects to sync and move

float deltat = 0.3; // interval between updates

integer controlstaken; // control keys taken by the script, initialised later

integer listener;
integer maxl = 1;

float speed = 4.0; // distance to move within deltat
float vspeed = 2.0; // vertical distance to move within deltat
integer rspeed = 3; // angle in degrees to rotate left or right within deltat

float delta = 0.4;

integer azimut; // direction in degrees around the Z axis (vertical)

default
{
on_rez(integer c)
{
llResetScript();
}

state_entry()
{
llMinEventDelay(deltat);
controlstaken = CONTROL_FWD|CONTROL_BACK|CONTROL_UP|CONTROL_DOWN;
}

attach(key id)
{
if (id != NULL_KEY)
{
llRequestPermissions(id, PERMISSION_TAKE_CONTROLS | PERMISSION_TRIGGER_ANIMATION);
return;
} else if (llGetPermissions() & PERMISSION_TAKE_CONTROLS)
{
llReleaseControls();
llSleep(0.5);
for (listener=0; listener<maxl; listener += 1)
llShout(unichan + listener, (string)(llGetRootPosition() + llGetRegionCorner()) + "*" + (string)azimut);
}
llStopMoveToTarget();
llStopLookAt();
}

run_time_permissions(integer p)
{
if (p & PERMISSION_TAKE_CONTROLS)
{
for (listener=0; listener<maxl; listener += 1)
llShout(unichan + listener, (string)(llGetRootPosition() + llGetRegionCorner()) + "*" + (string)azimut);

llSleep(1.0);
llTakeControls(controlstaken, TRUE, FALSE);
llSetTimerEvent(1.0);
}
if (p & PERMISSION_TRIGGER_ANIMATION)
{
llStartAnimation("hover");
}
}

control(key id, integer pressed, integer change)
{
vector target = llGetRootPosition();
rotation rtarget = llGetRootRotation();

llResetTime();

if (pressed & CONTROL_FWD)
{
// going forward
target += speed * llRot2Fwd(rtarget);

} else if (pressed & CONTROL_BACK)
{
// going backward
target += -speed * llRot2Fwd(rtarget);
}

if (pressed & CONTROL_UP)
{
// going forward
target += vspeed * llRot2Up(rtarget);

} else if (pressed & CONTROL_DOWN)
{
// going backward
target += -vspeed * llRot2Up(rtarget);
}

if (pressed | change)
{
listener = (listener + 1) % maxl;
llShout(unichan + listener, (string)(target + llGetRegionCorner()) + "*" + (string)rtarget);
llMoveToTarget(target, delta);
}
}

timer()
{
if (llGetTime() < 0.5) return;
listener = (listener + 1) % maxl;
llShout(unichan + listener, (string)(llGetRootPosition() + llGetRegionCorner()) + "*" + (string)llGetRootRotation());
}
}


Move:
CODE

// Move
//
// Physical Multimove follower

integer unichan = -779991; // this is the channel used by the controller and
// objects to sync

integer handle; // handle for the listen function

float delta = 0.4;

vector my_offset; // original offset, set at first activation
rotation my_orientation; // original rotation, set at first activation
integer my_num; // position in the chain of redundancy

default
{
on_rez(integer p)
{
llResetScript();
}

state_entry()
{
my_num = (integer)llGetSubString(llGetScriptName(), -1, -1);
handle = llListen(unichan + my_num, "", "", "");
}

attach(key id)
{
if (id != NULL_KEY) { llRequestPermissions(id, PERMISSION_TRIGGER_ANIMATION); } else
llStopAnimation("hover");
}

run_time_permissions(integer p)
{
if (p) llStartAnimation("hover");
}

listen(integer chan, string name, key id, string mesg)
{
list temp = llParseString2List(mesg, ["*"], []);

my_offset = llGetPos() + llGetRegionCorner() - (vector)llList2String(temp, 0);
my_orientation = llGetRot() / (rotation)llList2String(temp, 1);
my_offset = my_offset / my_orientation;
state running;
}

state_exit()
{
llListenRemove(handle);
}
}

state running
{
state_entry()
{
handle = llListen(unichan + my_num, "", "", "");
}

attach(key id)
{
if (id != NULL_KEY) { llRequestPermissions(id, PERMISSION_TRIGGER_ANIMATION); } else
llStopAnimation("hover");
}

touch_start(integer c)
{
if (llDetectedKey(0) == llGetOwner()) { llStopMoveToTarget(); llStopLookAt(); }
}

run_time_permissions(integer p)
{
if (p) llStartAnimation("hover");
}

listen(integer chan, string name, key id, string mesg)
{
list info = llParseString2List(mesg, ["*"], []);
rotation rtarget = my_orientation * (rotation)llList2String(info, 1);
vector target = my_offset * rtarget + (vector)llList2String(info, 0) - llGetRegionCorner();
llRotLookAt(rtarget, delta, delta);
llMoveToTarget(target, delta);
}

state_exit()
{
llListenRemove(handle);
}
}
_____________________
Either Man can enjoy universal freedom, or Man cannot. If it is possible then everyone can act freely if they don't stop anyone else from doing same. If it is not possible, then conflict will arise anyway so punch those that try to stop you. In conclusion the only strategy that wins in all cases is that of doing what you want against all adversity, as long as you respect that right in others.
chutz Anansi
Registered User
Join date: 13 Jul 2004
Posts: 6
:-(
12-19-2005 20:50
Awesome script but for some reason i cannot get it to work . would someone mind helping me out on this sometime?
Jesrad Seraph
Nonsense
Join date: 11 Dec 2004
Posts: 1,463
12-19-2005 23:23
To use, you need either the Control OR the Autopilot script, and 3 copies of the Move scripts. Name them Move0, Move1, and Move2.

Once your build is complete, you should Take it entirely (select it all then right-click to take) just in case, re-rez it, then drop all 3 Move scripts in EACH object that composes it. Put the Control or Autopilot script in a child prim of the most central object (Control should go in the "pilot seat" prim of your object, preferably). Don't put Control or Autopilot in the root prim with the Move scripts, else it won't work. You should reselect the whole build and take it again once the scripts are all in for convenience (so you can rez the whole build at once easily).

If you use Control, sit on the object that holds it to move the vehicle. If you use Autopilot tclick on the child prim that contains it to bring up the course menu.
_____________________
Either Man can enjoy universal freedom, or Man cannot. If it is possible then everyone can act freely if they don't stop anyone else from doing same. If it is not possible, then conflict will arise anyway so punch those that try to stop you. In conclusion the only strategy that wins in all cases is that of doing what you want against all adversity, as long as you respect that right in others.
Brookston Holiday
Registered User
Join date: 29 May 2005
Posts: 58
02-06-2006 15:16
HELP!!!

I can't seem to figure this out...

no matter what i seem to do, the scripts make my objects go to the right instead of forward when i hit forward. What am i doing wrong?
Jesrad Seraph
Nonsense
Join date: 11 Dec 2004
Posts: 1,463
02-06-2006 15:32
Your central object's root prim is facing sideways. Forward is the Red arrow (= X axis), it points to the East by default.
_____________________
Either Man can enjoy universal freedom, or Man cannot. If it is possible then everyone can act freely if they don't stop anyone else from doing same. If it is not possible, then conflict will arise anyway so punch those that try to stop you. In conclusion the only strategy that wins in all cases is that of doing what you want against all adversity, as long as you respect that right in others.
Jesrad Seraph
Nonsense
Join date: 11 Dec 2004
Posts: 1,463
02-09-2006 04:54
Great news !

Now Multimove can interface with any TLTP browser using this remote script:
CODE

// Multimove Autopilot
//
// For navigating big space cruisers

integer unichan = -599599;
integer listener;
integer maxl = 4;
integer my_chan = 128;
integer handle;
string opt;
list buttons;

integer azimut;
integer vel;
integer vvel;
integer rvel;
float mul;

float speed = 1.0;
float vspeed = 1.0;
integer rspeed = 3;
float rate = 0.15;

integer curaz;

default
{
on_rez(integer p)
{
llSleep(2.0);
llResetScript();
}

state_entry()
{
llSetTimerEvent(0.0);
azimut = 0;
curaz = 0;
vel = 0;
vvel = 0;
rvel = 0;
mul = 1.0;
opt = "";
buttons = [];
for (listener = 0; listener < maxl; listener = listener + 1)
llShout(unichan + listener, (string)(llGetRootPosition() + llGetRegionCorner()) + "*0");
handle = llListen(my_chan, "", "", "");
}

listen(integer chan, string name, key id, string msg)
{
if ((llGetOwner() != id) && (llGetOwnerKey(id) != llGetOwner())) return;

string t = llGetSubString(msg, 0, 0);

if (t == "m") { vel = (integer)llDeleteSubString(msg, 0, 0); llSetTimerEvent(rate); llOwnerSay("Engine set to " + (string)((integer)vel) + " m/s"); } else
if (t == "v") { vvel = (integer)llDeleteSubString(msg, 0, 0); llSetTimerEvent(rate); llOwnerSay("Vertical set to " + (string)((integer)vvel) + " m/s"); } else
if (t == "r") { rvel = (integer)llDeleteSubString(msg, 0, 0); llSetTimerEvent(rate); } else
if (t == "s") { mul /= 2.0; llOwnerSay("Engine power now " + (string)((integer)(mul*100)) + "%"); } else
if (t == "f") { mul *= 2.0; llOwnerSay("Engine power now " + (string)((integer)(mul*100)) + "%"); } else
if (t == "t") { azimut = (integer)llDeleteSubString(msg, 0, 0); llSetTimerEvent(rate); llOwnerSay("Course set to " + (string)azimut + " degree"); } else
if (t == "k") { llSetTimerEvent(0.0); llShout(unichan, "k"); } else
if (t == "h") {vel = 0; vvel = 0; rvel = 0; llSetTimerEvent(0.0); }
}

timer()
{
vector pos = vel*mul*llRot2Fwd(llGetRootRotation()) + vvel*mul*llRot2Up(llGetRootRotation()) + llGetRootPosition();
listener = (listener + 1) % maxl;
if (rvel != 0)
{
azimut += rvel * rspeed;
}

if (curaz != azimut)

{

integer doaz = azimut - curaz;

if (llAbs(doaz) <= rspeed)

{

curaz = azimut;

} else {

if(((doaz > 0) && (doaz < 180)) || (doaz < -180))

{

curaz = (curaz + rspeed) % 360;

} else curaz = (curaz - rspeed) % 360;
if (curaz < 0) curaz += 360;

}

} else {

if ((vel == 0.0) && (vvel == 0.0)) llSetTimerEvent(0.0);

}
llShout(unichan + listener, (string)(pos + llGetRegionCorner()) + "*" + (string)curaz);
}
}

Just change "chan" to the unique channel identifier of your large vehicle.

And this TLML page (along with a server script):
From: someone

T/-99
cMultimove Interface
T|0|T#-98|8201F|<.0,.0,.0>|<.05,.05,.5>|<.25,.25,.6>
T|1|C!128!m1|6321F|586383e8-4d9b-4fba-9196-2b5938e79c2c|1.5707963267949|<1,1,0>|<.05,.05,.05>|<.15,.22,.6>
T|2|C!128!m-1|6321F|586383e8-4d9b-4fba-9196-2b5938e79c2c|-1.5707963267949|<1,1,0>|<.05,.05,.05>|<.15,.08,.6>
T|3|C!128!r1|6321F|586383e8-4d9b-4fba-9196-2b5938e79c2c|3.14159265358979|<1,1,0>|<.05,.05,.05>|<.08,.15,.6>
T|4|C!128!r-1|6221F|586383e8-4d9b-4fba-9196-2b5938e79c2c|<1,1,0>|<.05,.05,.05>|<.22,.15,.6>
T|5|C!128!h|6201F|<1,1,0>|<.05,.05,.05>|<.15,.15,.6>
T|6|C!128!v1|6321F|586383e8-4d9b-4fba-9196-2b5938e79c2c|1.5707963267949|<0,1,0>|<.05,.05,.05>|<.08,.22,.6>
T|7|C!128!v-1|6321F|586383e8-4d9b-4fba-9196-2b5938e79c2c|-1.5707963267949|<0,1,0>|<.05,.05,.05>|<.08,.08,.6>
T|8|C!128!k|6221F|38ce8b3c-fb30-5c59-9926-bd643613f606|<.667,.0,.0>|<.05,.05,.05>|<.22,.22,.6>

Change the colors in there to suit your mood ;)
_____________________
Either Man can enjoy universal freedom, or Man cannot. If it is possible then everyone can act freely if they don't stop anyone else from doing same. If it is not possible, then conflict will arise anyway so punch those that try to stop you. In conclusion the only strategy that wins in all cases is that of doing what you want against all adversity, as long as you respect that right in others.