string VERSION = "0.0.4"; string GuideRailName = "Guide"; /** * MIT Licensed 2017 * Created by Chris Strachan aka VenKellie Resident in SecondLife * For railfans in SecondLife and Opensim * Free to modify and use in your trains * Just please give credit to me and not say you wrote this * * UPDATE 2024! Switched llSetLinkPrimitiveParamsFast to just llSetLinkPrimitiveParams **/ integer APICHAN = -782274; // RTCAPI integer VRCCHAN = -872274; integer LINC = 98; integer LOUT = 99; rotation ROTATION_FLIP = <0,0,0,0>; integer RerailRequest = TRUE; integer FlipRequest = FALSE; float SENSOR_SEEK_ARC_DEFAULT = 25.00; float SensorScanRange = 20.00; float SensorScanArc = 25.00; vector GuideOffset = <0,0,0.5>; float SpeedPrior = 0.00; float SpeedCurrent = 0.00; float SpeedMinimum = 0.05; float SpeedMaximum = 1.10; float SpeedIncrement = 0.10; string RegionCurrentName; vector RSize = <255.0,255.0,0.0>; vector PriorWaypoint = <0,0,0>; key owner = NULL_KEY; integer TrainStatus = 0; integer ENGINE_STATUS_OFF = 0; integer ENGINE_STATUS_IDLING = 1; integer ENGINE_STATUS_ROLLING = 2; integer NoHitsCount = 0; integer TravelDirection = 0; key HUDKEY = NULL_KEY; integer pcount = 0; string strReplace(string source, string pattern, string replace) { while (llSubStringIndex(source, pattern) > -1) { integer len = llStringLength(pattern); integer pos = llSubStringIndex(source, pattern); if (llStringLength(source) == len) { source = replace; } else if (pos == 0) { source = replace+llGetSubString(source, pos+len, -1); } else if (pos == llStringLength(source)-len) { source = llGetSubString(source, 0, pos-1)+replace; } else { source = llGetSubString(source, 0, pos-1)+replace+llGetSubString(source, pos+len, -1); } } return source; } string listtojson(list l) { string jit = llList2Json(JSON_OBJECT, l); return strReplace(jit, "}{", ","); } LMOUT(list l) { if (l != []) { string json = listtojson(l); llMessageLinked(LINK_SET,LOUT,json,"*"); if (HUDKEY != NULL_KEY) { llRegionSayTo(HUDKEY,APICHAN,json); } } } TrainSpeedUpdate(float fSpeedDelta) { if (fSpeedDelta > 0.00) { if ((SpeedCurrent + fSpeedDelta) < SpeedMaximum) { SpeedCurrent += fSpeedDelta; } }else{ if ((SpeedCurrent + fSpeedDelta) > SpeedMinimum) { SpeedCurrent += fSpeedDelta; } } LMOUT(["com", "speedupdate", "speed", SpeedCurrent]); } TrainText(string msg) { llSetText(msg,<0,1,0>,1); } TrainMoveTo(vector waypoint, rotation rotpoint) { if (llGetStatus(STATUS_PHYSICS)) { llRotLookAt(rotpoint, 1 / llVecMag(llGetVel()), 1); llMoveToTarget(waypoint, 1.0); }else{ llSetLinkPrimitiveParams(LINK_ROOT, [ PRIM_POSITION, waypoint, PRIM_ROTATION, rotpoint]); } TrainText((string)SpeedCurrent+" Speed"); } TrainStop() { TrainStatus = ENGINE_STATUS_OFF; LMOUT(["com", "stop"]); SpeedPrior = SpeedCurrent; SpeedCurrent = 0.00; llSensorRemove(); TrainText((string)SpeedCurrent+" Speed"); } TrainIdle() { LMOUT(["com", "idle"]); llSetPos(llGetPos() + <-0.01, 0.00, 0.00>); TrainStatus = ENGINE_STATUS_IDLING; SpeedPrior = SpeedCurrent; SpeedCurrent = 0.00; TrainText((string)SpeedCurrent+" Speed"); } TrainRun(float SpeedStartup) { llSetPos(llGetPos() + <0.01, 0.00, 0.00>); llSensor(GuideRailName, "", ACTIVE | PASSIVE, SensorScanRange, SensorScanArc); TrainStatus = ENGINE_STATUS_ROLLING; SpeedCurrent = 0.10; LMOUT(["com", "run", "speed", SpeedCurrent]); } TrainFlip() { TrainText((string)SpeedCurrent+" Speed"); LMOUT(["com", "flip"]); if (llGetStatus(STATUS_PHYSICS)) { llSetStatus(STATUS_PHYSICS,FALSE); } FlipRequest = FALSE; llSetRot(ROTATION_FLIP * llGetRot()); TravelDirection = (TravelDirection + 1) % 2; if (TrainStatus == ENGINE_STATUS_ROLLING) { llSleep(0.5); llSensor(GuideRailName, "", ACTIVE | PASSIVE, SensorScanRange, SensorScanArc); } TrainIdle(); } TrainSpeedUp() { if (TrainStatus == ENGINE_STATUS_OFF) { TrainIdle(); }else if (TrainStatus == ENGINE_STATUS_IDLING) { TrainRun(SpeedMinimum); }else if (TrainStatus == ENGINE_STATUS_ROLLING) { TrainSpeedUpdate(SpeedIncrement); } TrainText((string)SpeedCurrent+" Speed"); } TrainSlowDown() { if (TrainStatus == ENGINE_STATUS_ROLLING) { if (SpeedCurrent >= SpeedMinimum) { TrainSpeedUpdate(-SpeedIncrement); }else{ TrainIdle(); } }else if (TrainStatus == ENGINE_STATUS_IDLING) { TrainStop(); } TrainText((string)SpeedCurrent+" Speed"); } integer isOwnerOnboard = FALSE; integer GetAgentLinkNumber(key avatar) { integer lnp = llGetNumberOfPrims(); while (lnp > 1) { if (llGetLinkKey(lnp) == avatar) { return TRUE; } lnp--; } return FALSE; } ScriptInitialize() { llSetText("",<0,0,0>,0); pcount = llGetNumberOfPrims(); SensorScanArc = SENSOR_SEEK_ARC_DEFAULT * DEG_TO_RAD; ROTATION_FLIP = llEuler2Rot(<0.00, 0.00, 180.00> * DEG_TO_RAD); owner = llGetOwner(); llStopMoveToTarget(); llStopLookAt(); llSetBuoyancy(1.0); llVolumeDetect(FALSE); llSetStatus(STATUS_PHANTOM, FALSE); // uncomment this for ubODE // RSize = osGetRegionSize(); } default { state_entry() { if (RerailRequest) { state Rerail; llOwnerSay("RERAIL!"); }else{ if (owner == NULL_KEY) { ScriptInitialize(); } } LMOUT(["com", "reset"]); llListen(APICHAN, "", "", ""); llListen(VRCCHAN, "", "", ""); } changed(integer iChanged) { if (iChanged & CHANGED_REGION) { RegionCurrentName = llGetRegionName(); // uncomment this for ubODE // RSize = osGetRegionSize(); } if (iChanged & CHANGED_LINK) { if (GetAgentLinkNumber(owner) && isOwnerOnboard == FALSE) { isOwnerOnboard = TRUE; llRequestPermissions(owner, PERMISSION_TAKE_CONTROLS); }else if (GetAgentLinkNumber(owner) == FALSE && isOwnerOnboard == TRUE) { isOwnerOnboard = FALSE; integer perms = llGetPermissions(); if (perms & PERMISSION_TAKE_CONTROLS) { llReleaseControls(); } TrainStop(); } } } run_time_permissions(integer iPermissions) { if (iPermissions & PERMISSION_TAKE_CONTROLS) { llTakeControls(CONTROL_UP | CONTROL_DOWN | CONTROL_LEFT | CONTROL_RIGHT, TRUE, FALSE); } } control(key kID, integer level, integer edge) { if (edge & level & CONTROL_UP) { TrainSpeedUp(); } if (edge & level & CONTROL_DOWN) { TrainSlowDown(); } if (edge & level & CONTROL_LEFT) { if (TrainStatus == ENGINE_STATUS_ROLLING) { FlipRequest=TRUE; }else{ TrainFlip(); } } if (edge & level & CONTROL_RIGHT) { if (TrainStatus == ENGINE_STATUS_ROLLING) { TrainIdle(); }else if (TrainStatus == ENGINE_STATUS_IDLING || TrainStatus == ENGINE_STATUS_OFF) { TrainRun(SpeedPrior); } } } sensor(integer iSensed) { if (TrainStatus == ENGINE_STATUS_ROLLING) { integer index = 0; NoHitsCount = 0; if (FlipRequest) { TrainFlip(); return; } if (llGetPos() == llDetectedPos(0) + GuideOffset) { if (iSensed > 1) { index++; }else{ vector next = llGetPos() + <15.00, 0.00, 0.00> * llGetRot(); float of = 1.00; if (next.x < of || next.x > RSize.x || next.y < of || next.y > RSize.y) { TrainMoveTo(llGetPos() + <2.00, 0.00, 0.00> * llGetRot(), llGetRot()); }else{ TrainFlip(); return; } llSensor(GuideRailName, "", ACTIVE | PASSIVE, SensorScanRange, SensorScanArc); return; } } rotation rHeadingNext = llDetectedRot(0); if (TravelDirection) { rHeadingNext = ROTATION_FLIP * rHeadingNext; } if (llFabs(llAngleBetween(rHeadingNext, llGetRot())) > PI_BY_TWO) { rHeadingNext = ROTATION_FLIP * rHeadingNext; } vector vPositionCurrent = llGetPos(); vector vWaypointNext = llDetectedPos(index) + GuideOffset; if (llVecDist(vPositionCurrent, PriorWaypoint) < llVecDist(vPositionCurrent, vWaypointNext)) { rHeadingNext = llGetRot(); }else{ PriorWaypoint = vWaypointNext; } vector vPositionNext = vPositionCurrent + (llVecNorm(vWaypointNext - vPositionCurrent) * SpeedCurrent); TrainMoveTo(vPositionNext, rHeadingNext); llSensor(GuideRailName, "", ACTIVE | PASSIVE, SensorScanRange, SensorScanArc); } } no_sensor() { if (++NoHitsCount > 4) { llResetScript(); }else{ // uncomment this for ubODE // RSize = osGetRegionSize(); RegionCurrentName = llGetRegionName(); vector vPN = llGetPos() + <15.00, 0.00, 0.00> * llGetRot(); float of = 1.00; if (vPN.x < of || vPN.x > RSize.x || vPN.y < of || vPN.y > RSize.y) { TrainMoveTo(llGetPos() + <5.00, 0.00, 0.00> * llGetRot(), llGetRot()); }else{ TrainFlip(); return; } llSleep(1.0); llSensor(GuideRailName, "", ACTIVE | PASSIVE, SensorScanRange, SensorScanArc); } } listen(integer chan, string name, key id, string msg) { if (chan == APICHAN) { string com = llJsonGetValue(msg,["com"]); if (llGetOwnerKey(id) == owner) { if (~llSubStringIndex(llToLower(name), "hud") && HUDKEY == NULL_KEY) { HUDKEY = id; } if (HUDKEY != NULL_KEY) { if (com == "startstop") { if (TrainStatus == ENGINE_STATUS_ROLLING) { TrainIdle(); }else if (TrainStatus == ENGINE_STATUS_IDLING || TrainStatus == ENGINE_STATUS_OFF) { TrainRun(SpeedPrior); } } if (com == "flip") { if (TrainStatus == ENGINE_STATUS_ROLLING) { FlipRequest=TRUE; }else{ TrainFlip(); } } if (com == "faster") { TrainSpeedUp(); } if (com == "slower") { TrainSlowDown(); } if (com == "rerail") { RerailRequest = TRUE; state Rerail; } } }else{ if (com == "rerail" && RerailRequest == FALSE) { RerailRequest = TRUE; state Rerail; } } } if (chan == VRCCHAN) { if (msg == "rerail" && RerailRequest == FALSE) { RerailRequest = TRUE; state Rerail; } if (msg == "startstop") { if (TrainStatus == ENGINE_STATUS_ROLLING) { TrainIdle(); }else if (TrainStatus == ENGINE_STATUS_IDLING || TrainStatus == ENGINE_STATUS_OFF) { TrainRun(SpeedPrior); } } } } link_message(integer sn, integer num, string msg, key id) { if (num == LINC) { string com = llJsonGetValue(msg,["com"]); if (com == "startstop") { if (TrainStatus == ENGINE_STATUS_ROLLING) { TrainIdle(); }else if (TrainStatus == ENGINE_STATUS_IDLING || TrainStatus == ENGINE_STATUS_OFF) { TrainRun(SpeedPrior); } } if (com == "flip") { if (TrainStatus == ENGINE_STATUS_ROLLING) { FlipRequest=TRUE; }else{ TrainFlip(); } } if (com == "faster") { TrainSpeedUp(); } if (com == "slower") { TrainSlowDown(); } if (com == "rerail") { RerailRequest = TRUE; state Rerail; } } } on_rez(integer iRezParameter) { llResetScript(); } } state Rerail { state_entry() { RerailRequest = FALSE; llSensor(GuideRailName, "", ACTIVE | PASSIVE, SensorScanRange, PI); } sensor(integer scnd) { TrainMoveTo(llDetectedPos(0) + GuideOffset, llDetectedRot(0)); state default; } no_sensor() { TrainStop(); llOwnerSay("Scan target "+GuideRailName+" not within "+(string)llRound(SensorScanRange)+" meters"); state default; } }