// Tessel's Workbench: rezzes build roots and loads the builder scripts into them. // Orders come from the owner as chat on channel 7340 (bridge/slbuild.py shouts them), addressed // to one bench by key ("*" for any) so several benches can share a region: // rez||| rez a root seed above the bench, give it a spare seed, // then load the job parts and the node into it // load|||| load the job parts and the node into a root of mine // kill||| delete an object of mine (node with start param -1) // scan|||| list my objects nearby (ids for the CLI) // ping|| report status // Loading uses llRemoteLoadScriptPin, so targets must carry the pin: every prim rezzed from a // Tessel Seed does. Build roots that need more seeds ask on WB_CH with "seed". // Inventory: this script; "Tessel Node" and "Tessel Job", "Tessel Job 2", ... (kept NOT // running here; the CLI overwrites them over the editor RPC before each order); a "Tessel Seed" // object (a drop may name it "Tessel Seed 1"; any name starting "Tessel Seed" works). // Replies go to the owner as "TB||..." lines. // Standard LSL only (no os*), so it runs on Second Life as well as OpenSim. // Written by Claude Opus 5.5, 2026-10-09. integer PIN = 7340881; integer ORDER_CH = 7340; integer SEED_CH = -7340881; integer WB_CH = -7340882; string SEED = "Tessel Seed"; string NODE = "Tessel Node"; string JOB = "Tessel Job"; string gNonce = "0"; integer gParts; key gRoot; string seed() { integer i; for (i = 0; i < llGetInventoryNumber(INVENTORY_OBJECT); ++i) { string n = llGetInventoryName(INVENTORY_OBJECT, i); if (llSubStringIndex(n, SEED) == 0) return n; } return ""; } string part(integer k) { if (k == 1) return JOB; return JOB + " " + (string)k; } say(string s) { llOwnerSay("TB|" + gNonce + "|" + s); } report() { integer slots; while (llGetInventoryType(part(slots + 1)) == INVENTORY_SCRIPT) ++slots; say("ready|" + (string)llGetKey() + "|seed=" + seed() + "|node=" + (string)(llGetInventoryType(NODE) == INVENTORY_SCRIPT) + "|slots=" + (string)slots + "|pos=" + (string)llGetPos()); } dormant() { integer k; if (llGetInventoryType(NODE) == INVENTORY_SCRIPT) llSetScriptState(NODE, FALSE); for (k = 1; llGetInventoryType(part(k)) == INVENTORY_SCRIPT; ++k) llSetScriptState(part(k), FALSE); } loadInto(key target) { integer k; for (k = 1; k <= gParts; ++k) { if (llGetInventoryType(part(k)) != INVENTORY_SCRIPT) { say("error|no slot " + part(k)); return; } llRemoteLoadScriptPin(target, part(k), PIN, TRUE, 0); } llRemoteLoadScriptPin(target, NODE, PIN, TRUE, 0); // last, so the parts are listening say("loaded|" + (string)target); } default { state_entry() { llListen(ORDER_CH, "", llGetOwner(), ""); llListen(SEED_CH, "", NULL_KEY, ""); llListen(WB_CH, "", NULL_KEY, ""); dormant(); report(); } on_rez(integer p) { llResetScript(); } changed(integer c) { if (c & CHANGED_OWNER) llResetScript(); if (c & CHANGED_INVENTORY) dormant(); } touch_start(integer n) { if (llDetectedKey(0) == llGetOwner()) report(); } listen(integer ch, string name, key id, string msg) { if (ch == ORDER_CH) { list a = llParseStringKeepNulls(msg, ["|"], []); string to = llList2String(a, 2); if (to != "*" && to != (string)llGetKey()) return; a = llDeleteSubList(a, 2, 2); string op = llList2String(a, 0); gNonce = llList2String(a, 1); if (op == "ping") report(); else if (op == "scan") llSensor(llUnescapeURL(llList2String(a, 3)), NULL_KEY, ACTIVE | PASSIVE | SCRIPTED, (float)llList2String(a, 2), PI); else if (op == "rez") { if (seed() == "") { say("error|no seed in workbench"); return; } gParts = (integer)llList2String(a, 2); gRoot = NULL_KEY; llRezObject(seed(), llGetPos() + <0, 0, 1.5>, ZERO_VECTOR, ZERO_ROTATION, (integer)gNonce); llSetTimerEvent(20); } else if (op == "load") { gParts = (integer)llList2String(a, 2); loadInto((key)llList2String(a, 3)); } else if (op == "kill") { llRemoteLoadScriptPin((key)llList2String(a, 2), NODE, PIN, TRUE, -1); say("killed|" + llList2String(a, 2)); } return; } if (llGetOwnerKey(id) != llGetOwner()) return; if (ch == SEED_CH) { // Our new root has set its pin. Children of builds announce "-1" and are ignored. if (msg != gNonce || gRoot != NULL_KEY) return; gRoot = id; llSetTimerEvent(0); say("rooted|" + (string)id); llGiveInventory(id, seed()); loadInto(id); } else if (ch == WB_CH && msg == "seed") { llGiveInventory(id, seed()); } } sensor(integer n) { integer i; for (i = 0; i < n; ++i) if (llDetectedOwner(i) == llGetOwner()) say("found|" + (string)llDetectedKey(i) + "|" + llEscapeURL(llDetectedName(i)) + "|" + (string)llDetectedPos(i)); say("scanned|" + (string)n); } no_sensor() { say("scanned|0"); } timer() { llSetTimerEvent(0); if (gRoot == NULL_KEY) say("error|root seed never reported (rez blocked? parcel full?)"); } }