// Wolf Territories example - ANALOGUE CAR. A simple car that drives from a real steering wheel // and pedals, or a gamepad, with proportional throttle and steering, and drives the driver's Wolf // dashboard (speedometer, rev counter, gears, fuel, lamps). // // wolfDriveInput(driver) -> [steer, throttle, brake, clutch, gear, buttons, age] // steer -1..1 (right +), pedals 0..1, gear WOLF_DRIVE_GEAR_* or 1..18, // buttons WOLF_DRIVE_BTN_* bits, age = seconds since the viewer sent it. // An empty list from any other viewer (or Firestorm): this script then // drives from the keys, through control(), as every car does. // wolfDashboard(driver, [...]) -> the dashboard's rev counter, gear, fuel and lamps. The speed is // the car's own; the viewer measures it. // // Both work only for a script in the linkset the driver is sitting on, and only on Wolf Territories // regions (WolfSim). Put this in the root prim of a car-shaped linkset and sit in it. A sound called // "horn" in the same prim plays on the horn button. float MAX_SPEED = 30.0; // m/s at full throttle in D float REVERSE_SPEED = 6.0; // m/s at full throttle in R float MAX_TURN = 2.0; // rad/s at full lock float TICK = 0.1; // seconds between control updates integer DASH_EVERY = 3; // ticks between dashboard updates (the region allows 5 a second) float LIVE_AGE = 2.0; // analogue values older than this mean the viewer has gone // This car's own six-speed automatic, for its rev counter: the speeds (m/s) where it changes up. list SHIFT_UP = [7.0, 12.0, 17.0, 22.0, 27.0]; float IDLE_RPM = 800.0; float SHIFT_RPM = 6200.0; key gDriver; integer gKeys; // the held keys (control() levels), for viewers that send no analogue values integer gTick; integer gPrevButtons; integer gLights; // 0 off, 1 dipped, 2 main beam integer gIndicators; // 0 off, 1 left, 2 right integer gHandbrake; float gFuel = 100.0; // percent setupVehicle() { llSetVehicleType(VEHICLE_TYPE_CAR); llSetVehicleFloatParam(VEHICLE_LINEAR_MOTOR_TIMESCALE, 1.0); llSetVehicleFloatParam(VEHICLE_LINEAR_MOTOR_DECAY_TIMESCALE, 0.2); llSetVehicleFloatParam(VEHICLE_ANGULAR_MOTOR_TIMESCALE, 0.2); llSetVehicleFloatParam(VEHICLE_ANGULAR_MOTOR_DECAY_TIMESCALE, 0.2); llSetVehicleVectorParam(VEHICLE_LINEAR_FRICTION_TIMESCALE, <100.0, 1.0, 1000.0>); llSetVehicleVectorParam(VEHICLE_ANGULAR_FRICTION_TIMESCALE, <10.0, 10.0, 1000.0>); llSetVehicleFloatParam(VEHICLE_VERTICAL_ATTRACTION_EFFICIENCY, 1.0); llSetVehicleFloatParam(VEHICLE_VERTICAL_ATTRACTION_TIMESCALE, 1.0); } // This car's gear for a speed (1..6), and its engine speed in that gear. integer gearFor(float speed) { integer g = 1; integer i; for (i = 0; i < llGetListLength(SHIFT_UP); ++i) { if (speed > llList2Float(SHIFT_UP, i)) g = i + 2; } return g; } float rpmFor(float speed, integer g, float throttle) { float lo = 0.0; float hi = llList2Float(SHIFT_UP, 0); if (g > 1) lo = llList2Float(SHIFT_UP, g - 2); if (g <= llGetListLength(SHIFT_UP)) hi = llList2Float(SHIFT_UP, g - 1); else hi = MAX_SPEED; float frac = (speed - lo) / (hi - lo); if (frac < 0.0) frac = 0.0; if (frac > 1.0) frac = 1.0; float rpm = 2200.0 + frac * (SHIFT_RPM - 2200.0); float revving = IDLE_RPM + throttle * 2000.0; // pulling away, the clutch slips if (rpm < revving) rpm = revving; return rpm; } // The lamp buttons work on the press, not while held. pressButtons(integer buttons) { integer pressed = buttons & ~gPrevButtons; gPrevButtons = buttons; if (pressed & WOLF_DRIVE_BTN_LIGHTS) gLights = (gLights + 1) % 3; if (pressed & WOLF_DRIVE_BTN_INDICATOR_LEFT) { if (gIndicators == 1) gIndicators = 0; else gIndicators = 1; } if (pressed & WOLF_DRIVE_BTN_INDICATOR_RIGHT) { if (gIndicators == 2) gIndicators = 0; else gIndicators = 2; } if (pressed & WOLF_DRIVE_BTN_HANDBRAKE) gHandbrake = !gHandbrake; if ((pressed & WOLF_DRIVE_BTN_HORN) && llGetInventoryType("horn") == INVENTORY_SOUND) llTriggerSound("horn", 1.0); } drive() { float steer; float throttle; float brake; integer gear = WOLF_DRIVE_GEAR_D; list wheel = wolfDriveInput(gDriver); if (llGetListLength(wheel) == 7 && llList2Float(wheel, 6) < LIVE_AGE) { // A Wolf viewer: exactly how far the wheel is turned and the pedals are pressed. steer = llList2Float(wheel, 0); throttle = llList2Float(wheel, 1); brake = llList2Float(wheel, 2); gear = llList2Integer(wheel, 4); pressButtons(llList2Integer(wheel, 5)); } else { // Any other viewer: the keys, all or nothing. Back is reverse, as on most SL cars. if (gKeys & CONTROL_FWD) throttle = 1.0; if (gKeys & CONTROL_BACK) { throttle = 1.0; gear = WOLF_DRIVE_GEAR_R; } if (gKeys & (CONTROL_ROT_RIGHT | CONTROL_RIGHT)) steer += 1.0; if (gKeys & (CONTROL_ROT_LEFT | CONTROL_LEFT)) steer -= 1.0; } float target; if (gHandbrake || gear == WOLF_DRIVE_GEAR_P || gear == WOLF_DRIVE_GEAR_N) target = 0.0; else if (gear == WOLF_DRIVE_GEAR_R) target = -REVERSE_SPEED * throttle; else target = MAX_SPEED * throttle; target = target * (1.0 - brake); llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, ); // a right turn (steer +) is clockwise seen from above: negative about z llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION, <0.0, 0.0, -steer * MAX_TURN>); vector vel = llGetVel(); float speed = llVecMag(); gFuel -= speed * TICK / 1000.0 / 5.0; // 5 km per percent: this car's own figure if (gFuel < 0.0) gFuel = 0.0; if (++gTick % DASH_EVERY == 0) { integer g = gearFor(speed); string label = (string)g; if (gear == WOLF_DRIVE_GEAR_R) label = "R"; else if (gear == WOLF_DRIVE_GEAR_N) label = "N"; else if (gear == WOLF_DRIVE_GEAR_P) label = "P"; else if (gear >= 1 && gear <= 18) label = (string)gear; // the driver's own gear (paddles) float rpm = IDLE_RPM + throttle * 2500.0; if (label != "R" && label != "N" && label != "P") rpm = rpmFor(speed, g, throttle); string warnings = ""; if (gFuel < 10.0) warnings = "fuel"; wolfDashboard(gDriver, [ "type", "car", "rpm", (integer)rpm, "gear", label, "fuel", gFuel, "temp", 90.0, "lights", gLights, "indicators", gIndicators, "handbrake", gHandbrake, "warnings", warnings]); } } default { state_entry() { llSitTarget(<0.2, 0.0, 0.45>, ZERO_ROTATION); llSetSitText("Drive"); setupVehicle(); llSetStatus(STATUS_PHYSICS, FALSE); } changed(integer change) { if (!(change & CHANGED_LINK)) return; key av = llAvatarOnSitTarget(); if (av != NULL_KEY && gDriver == NULL_KEY) { gDriver = av; llRequestPermissions(av, PERMISSION_TAKE_CONTROLS); } else if (av == NULL_KEY && gDriver != NULL_KEY) { gDriver = NULL_KEY; gKeys = 0; gPrevButtons = 0; llSetTimerEvent(0.0); llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, ZERO_VECTOR); llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION, ZERO_VECTOR); llSetStatus(STATUS_PHYSICS, FALSE); llReleaseControls(); } } run_time_permissions(integer perm) { if (!(perm & PERMISSION_TAKE_CONTROLS)) return; llTakeControls(CONTROL_FWD | CONTROL_BACK | CONTROL_LEFT | CONTROL_RIGHT | CONTROL_ROT_LEFT | CONTROL_ROT_RIGHT | CONTROL_UP | CONTROL_DOWN, TRUE, FALSE); llSetStatus(STATUS_PHYSICS, TRUE); gTick = 0; llSetTimerEvent(TICK); } control(key id, integer levels, integer edges) { gKeys = levels; } timer() { if (gDriver != NULL_KEY) drive(); } }