// IF YOU VALUE THIS WORK, PLEASE LEAVE ATTRIBUTION INTACT // // // // _|_| // // _| _| _|_|_|_| _|_| _|_|_| _|_| // // _| _| _| _| _| _| _| _|_|_|_| // // _| _| _| _| _| _| _| _| // // _|_| _|_|_|_| _|_| _| _| _|_|_| // // MiniVerse // //............................................................// //.....................▒▓▓▒▒▒▒▓▓▓▓▓▓▒▒▒▓▓░....................// //.................._▒▓▒.Ozone.MiniVerse.▒▓▒_.................// //................_▒▓▓▓▒____PRESENTS____▒▓▓▓▓▓▒_..............// //..............░▓▓▓▓▓▓▒___I.M.A.G.E.___▒▓▓▓▓▓▓▓░.............// //.............▒▓▓▓▓▓▓▓▒__IMAGE_MATRIX__▒▓▓▓▓▓▓▓▓▒............// //............▒▓▒▓▓▓▓▒_ACTION_GAME_ENGINE_▒▓▓▓▓▒▓▓▒...........// //..........▒▓▓▓▓░...▒▓▓▒_By_Spax_Orion_▒▓▓▒...░▓▓▓▓▒.........// //.........▒▓▓▓▓░......▒▓_& Dirty Helga_▓▓▒░.....░▒▓▓▓........// //.........▓▓▓▓▒░.._......░▒▓▓▓▓▓▓▓▓▓▓▓▒░......_..░▒▓▓▓.......// //.........▓▓▓▓░.._▓░▓░_....▓▓▓▓▓▓▓▓▓▓...._▒▓▒▓_..░▓▓▓▓.......// //.........▓▓▓▓.._▓█░▒█▒.....▒▓▓▓▓▓▓▒.....▒█▒░█▒_..▓▓▓▓.......// //........▒▓▓▓▓▓▒_..▒._░_▒▓▓▒░.▒▒.░▒▓▓▒_░_.▒.._▒▓▓▓▓▓▓▒.......// //.......▒▓▓▓▓▓▓▓▓▓_...▒▓▓▓▓▓░..▒▒..░▓▓▓▓▓▒..._▓▓▓▓▓▓▓▓▒......// //......▓▓▓▓▓▓▓▓▓▓▓_.▓▓▓▓▓▒.._▓▓▓█_..▒▓▓▓▓▓._▓▓▓▓▓▓▓▓▓▓▓......// //......▓▓▓▓▓▒▒▓▓▓▒▓▓▓▓▓▓▒░.▒▓▓▓▓▓█▒.░▒▓▓▓▓▓▒▒▓▓▓▒▓▓▓▓▓▓......// //......▓▓▓▓▓░.▓▓▓.▒▓▓▓▓▓░..▒▓▓▓▓▓▓▒..░▓▓▓▓▓░.▓▓▓.░▓▓▓▓▓......// //........▒▓▓█░.▓▓▓.▒▓▓▓▓▓__▓_..▒▒.._▓__▓▓▓▓▓░.▓▓▓.▒▓▓▓▒......// //.........░▒░░▒▓▒░▒▓▓▓▓▓▓▒▒▓▒▒▒▓▓▒▒▒▓▒▒▓▓▓▓▓▒░▒▓▒░░▒░░.......// //.........░▒.░▓▓░.▓▓▓▓█▒▒▒█▒▒▒█▒▒█▒▒▒█▒▒▒█▓▓▓▓.░▓▓░.▒░.......// //.............░▓▓░_▓▓▓▓█................█▓▓▓▓_░▓▓░...........// //..............░▒▓▓▓█▓._▒_.█░....█░._█_.▓█▓▓▓▒░..............// //.................░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░................// //.................░▒▓▓▓▒▒▓▒▒▓▓▓▓▒▒▓▓▓▓▒▒▓▓▒░.................// //..................▒▓▓░░▓░░▓▓▓▓░░▓▓▓▓░░▓▓▒...................// //.................▒▓░..█__▓▓▓▓__▓▓▓▓__█..░▓▒.................// //...................░...░..░▓▓░..░▓▓░..░...░.................// //.................... ..░...░▒░....░▒░...░...................// //............................................................// // Common sense not included and must be supplied by end user // // Read ENTIRE script first and LEARN: KNOW what you're using // // CC BY-NC https://creativecommons.org/licenses/by-nc/4.0/ // // ===========================================================// // SlugShooter (rezzed follower gun; invisible after arming) // // v1.1.0 - Costume change + Arena boundary dissolution // // OpenSim 0.9.2.1 Yeti-friendly // // Receives INIT from rezzer on a private channel: // // INIT|token|avatarUUID // // Requests permissions from that avatar, then: // // - follows them (chin-ish offset) // // - takes CONTROL_ML_LBUTTON // // - uses llGetCameraRot for aim // // - raycasts and applies osCauseDamage // // - rezzes "slug" as visible tracer // // - dissolves if player leaves arena bounds // // ===========================================================// // part of the slugshooter prim // ============================================================ // SECTION: CONFIG // ============================================================ string AMMO_OBJECT = "slug"; // must be in SlugShooter inventory // IASR integration (weapon tells local relay to play SFX) integer USE_IASR = TRUE; integer CH_IASR_CMD = -733102; // MUST match IASR / controller string SOUND_FIRE = "GunFire"; // put a real sound in weapon/IASR inventory float SOUND_FIRE_VOL = 1.0; // Fire feel float BULLET_VELOCITY = 60.0; float REPEAT_DELAY = 0.35; integer DAMAGE_DICE = 3; // Ray forgiveness float RAY_START_FWD = 1.0; float RAY_RANGE = 80.0; integer USE_CONE = TRUE; float CONE_SPREAD = 0.012; float INIT_MAX_DIST = 64.0; // 0.0 disables distance check // Follow float FOLLOW_TICK = 0.15; // 0.10..0.20 typical vector FOLLOW_OFFSET_LOCAL = <0.35, 0.0, 1.45>; // chin/front-ish, in avatar local space // Follow robustness (teleport can cause brief "not found") integer FOLLOW_MISS_LIMIT = 20; // ticks before giving up (20 * 0.15 = 3s) // Drop-in / arena insertion integer USE_DROPIN = TRUE; // Preferred anchor: phantom object named exactly this string ARENA_DROP_NAME = "ArenaDROP"; float ARENA_SENSOR_RANGE = 96.0; // sensor search radius float DROP_RADIUS = 10.0; // within 10m of anchor float DROP_HEIGHT = 3.0; // extra Z so they "drop in" with impact // Fallback point (if ArenaDROP not found) vector FALLBACK_DROP_POS = <0,0,0>; //Set this // Framework ping: rezzer/framework may ignore this if it doesn't care integer AUTO_REQUEST_MENU = TRUE; // Lore message (sent once after permissions granted) string LORE = "Grugs come from a place where screams are their favorite seasoning. They were conscripted by the EVIL Dr. Fester to STEAL your PRICVACY!\n \nSHOOT these monsters in Mouselook Mode with the Left Mouse Button and SCORE!\n \nPRESS START GAME if there are no Grugs in the Arena. It is FEAST or FAMINE! Fight GRUGS or be FOOD!"; // ============================================================ // SECTION: COSTUME CHANGE CONFIG // Visible appearance while awaiting permission grant. // After grant the prim reverts to invisible tiny form. // ============================================================ vector COSTUME_COLOR = <0.0, 0.5, 1.0>; // blue tint float COSTUME_GLOW = 0.1; float COSTUME_ALPHA = 1.0; vector COSTUME_SCALE = <0.3, 0.3, 0.3>; // enough to see string COSTUME_HOVERTEXT = "⚡ ACCEPT PERMISSIONS TO ARM ⚡"; vector COSTUME_TEXT_COLOR = <0.0, 0.5, 1.0>; float COSTUME_TEXT_ALPHA = 1.0; // ============================================================ // SECTION: ARENA BOUNDARY CONFIG // 3D axis-aligned bounding box defined by two corner prims. // Sensor scans for live positions; falls back to hardcoded. // ============================================================ string ARENA_NE_NAME = "*ArenaNE"; string ARENA_SW_NAME = "*ArenaSW"; float ARENA_CORNER_RANGE = 256.0; // sensor range for corner prims // Hardcoded fallbacks (if sensor misses the prims) LOOK HERE vector FALLBACK_ARENA_NE = <0,0,0>; vector FALLBACK_ARENA_SW = <0,0,0>; // Boundary check runs every Nth follow tick (5 * 0.15s = ~0.75s) integer BOUND_CHECK_EVERY = 5; // Ticks out of bounds before dissolution (4 * 0.75s = ~3 seconds grace) integer OOB_TICK_LIMIT = 4; string OOB_MSG = "You have left the arena! Your weapon has dissolved. Return to the arena entrance and touch the weapon station to request a new SlugShooter."; // ============================================================ // SECTION: RUNTIME STATE // ============================================================ key gRezzer = NULL_KEY; // locked INIT sender integer gChan = 0; string gToken = ""; key gController = NULL_KEY; integer gListenHandle = 0; integer gHasPerms = FALSE; integer gPermPending = FALSE; integer gFireLock = FALSE; // Drop-in integer gDidDrop = FALSE; // Follow robustness integer gMissCount = 0; // Costume change: stash original prim size to restore later vector gOriginalScale = <0.01, 0.01, 0.01>; // Arena boundary vector gArenaHigh = ZERO_VECTOR; // NE corner (max x, max y, max z) vector gArenaLow = ZERO_VECTOR; // SW corner (min x, min y, min z) integer gBoundsReady = FALSE; // TRUE once corners are resolved integer gFollowTickCount = 0; // counts follow ticks for Nth check integer gOOBCount = 0; // consecutive out-of-bounds ticks integer gBoundScanPhase = 0; // 0=not started, 1=scanning NE, 2=scanning SW, 3=done // Temp storage for corner scanning vector gScannedNE = ZERO_VECTOR; vector gScannedSW = ZERO_VECTOR; integer gFoundNE = FALSE; integer gFoundSW = FALSE; // ============================================================ // SECTION: HELPERS — COSTUME // ============================================================ costumeOn() { // Stash current scale for restoration gOriginalScale = llGetScale(); llSetPrimitiveParams([ PRIM_SIZE, COSTUME_SCALE, PRIM_COLOR, ALL_SIDES, COSTUME_COLOR, COSTUME_ALPHA, PRIM_GLOW, ALL_SIDES, COSTUME_GLOW, PRIM_FULLBRIGHT, ALL_SIDES, TRUE ]); llSetText(COSTUME_HOVERTEXT, COSTUME_TEXT_COLOR, COSTUME_TEXT_ALPHA); } costumeOff() { llSetPrimitiveParams([ PRIM_SIZE, gOriginalScale, PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 0.0, PRIM_GLOW, ALL_SIDES, 0.0, PRIM_FULLBRIGHT, ALL_SIDES, FALSE ]); llSetAlpha(0.0, ALL_SIDES); llSetText("", ZERO_VECTOR, 0.0); } // ============================================================ // SECTION: HELPERS — ARENA BOUNDARY // ============================================================ // Start scanning for corner prims. Phase 1: look for NE. startCornerScan() { gFoundNE = FALSE; gFoundSW = FALSE; gScannedNE = ZERO_VECTOR; gScannedSW = ZERO_VECTOR; gBoundScanPhase = 1; llSensor(ARENA_NE_NAME, NULL_KEY, ACTIVE | PASSIVE, ARENA_CORNER_RANGE, PI); } // Called when both scans complete (or fallback used) finalizeCorners() { if (!gFoundNE) { gScannedNE = FALLBACK_ARENA_NE; } if (!gFoundSW) { gScannedSW = FALLBACK_ARENA_SW; } // Build the AABB: ensure high/low are correct regardless of naming gArenaHigh.x = llListStatistics(LIST_STAT_MAX, [gScannedNE.x, gScannedSW.x]); gArenaHigh.y = llListStatistics(LIST_STAT_MAX, [gScannedNE.y, gScannedSW.y]); gArenaHigh.z = llListStatistics(LIST_STAT_MAX, [gScannedNE.z, gScannedSW.z]); gArenaLow.x = llListStatistics(LIST_STAT_MIN, [gScannedNE.x, gScannedSW.x]); gArenaLow.y = llListStatistics(LIST_STAT_MIN, [gScannedNE.y, gScannedSW.y]); gArenaLow.z = llListStatistics(LIST_STAT_MIN, [gScannedNE.z, gScannedSW.z]); gBoundsReady = TRUE; gBoundScanPhase = 3; } // Returns TRUE if position is inside the arena AABB integer isInArena(vector pos) { if (!gBoundsReady) return TRUE; // fail-open if bounds not yet resolved if (pos.x < gArenaLow.x) return FALSE; if (pos.x > gArenaHigh.x) return FALSE; if (pos.y < gArenaLow.y) return FALSE; if (pos.y > gArenaHigh.y) return FALSE; if (pos.z < gArenaLow.z) return FALSE; if (pos.z > gArenaHigh.z) return FALSE; return TRUE; } // Called from follow tick every Nth cycle boundaryCheck() { if (!gBoundsReady) return; if (gController == NULL_KEY) return; list d = llGetObjectDetails(gController, [OBJECT_POS]); if (llGetListLength(d) < 1) return; // can't find them, follow miss handles this vector avPos = llList2Vector(d, 0); if (isInArena(avPos)) { // Inside — reset OOB counter gOOBCount = 0; return; } // Outside arena gOOBCount++; if (gOOBCount >= OOB_TICK_LIMIT) { // Grace period expired — dissolve if (gController != NULL_KEY) { llInstantMessage(gController, OOB_MSG); } shutdown(); } } // ============================================================ // SECTION: HELPERS — RAYCAST // ============================================================ key castOneRay(vector cpos, rotation crot, vector lateral) { vector fwd = llRot2Fwd(crot); vector left = llRot2Left(crot); vector up = llRot2Up(crot); vector dir = llVecNorm(fwd + (left * lateral.x) + (up * lateral.y)); vector start = cpos + (fwd * RAY_START_FWD); vector end = start + (dir * RAY_RANGE); list results = llCastRay( start, end, [ RC_DETECT_PHANTOM, FALSE, RC_DATA_FLAGS, RC_GET_ROOT_KEY, RC_MAX_HITS, 1 ] ); return llList2Key(results, 0); } key doRaycastHit() { vector cpos = llGetCameraPos(); rotation crot = llGetCameraRot(); if (!USE_CONE) return castOneRay(cpos, crot, <0.0, 0.0, 0.0>); key k; k = castOneRay(cpos, crot, <0.0, 0.0, 0.0>); if (k) return k; k = castOneRay(cpos, crot, ); if (k) return k; k = castOneRay(cpos, crot, <-CONE_SPREAD, 0.0, 0.0>); if (k) return k; return NULL_KEY; } // ============================================================ // SECTION: HELPERS — IASR // ============================================================ iasrTrig(string snd, float vol) { if (!USE_IASR) return; if (snd == "") return; if (vol < 0.0) vol = 0.0; if (vol > 1.0) vol = 1.0; llRegionSay(CH_IASR_CMD, "TRIG|" + snd + "|" + (string)vol); } // ============================================================ // SECTION: HELPERS — FIRE // ============================================================ fire() { if (!gHasPerms) return; if (gFireLock) return; gFireLock = TRUE; rotation rot = llGetCameraRot(); vector vel = llRot2Fwd(rot) * BULLET_VELOCITY; // Visual tracer slug vector pos = llGetPos() + (llRot2Fwd(rot) * 0.3); rotation rezRot = rot * llEuler2Rot(<0, PI_BY_TWO, 0>); if (llGetInventoryType(AMMO_OBJECT) == INVENTORY_OBJECT) llRezObject(AMMO_OBJECT, pos, vel, rezRot, DAMAGE_DICE); // Hitscan damage key target = doRaycastHit(); if (target != NULL_KEY) osCauseDamage(target, DAMAGE_DICE); // Weapon SFX via IASR (IASR will auto-resume any active loop after TRIG) iasrTrig(SOUND_FIRE, SOUND_FIRE_VOL); // Temporarily switch timer to ROF cooldown; timer() will restore FOLLOW_TICK llSetTimerEvent(REPEAT_DELAY); } // ============================================================ // SECTION: HELPERS — FOLLOW // ============================================================ followTick() { if (gController == NULL_KEY) return; list d = llGetObjectDetails(gController, [OBJECT_POS, OBJECT_ROT]); if (llGetListLength(d) < 2) { // Controller briefly not found (teleport etc). Don't instantly suicide. gMissCount++; if (gMissCount >= FOLLOW_MISS_LIMIT) llDie(); return; } // Found again gMissCount = 0; vector avPos = llList2Vector(d, 0); rotation avRot = llList2Rot(d, 1); vector desired = avPos + (FOLLOW_OFFSET_LOCAL * avRot); // Yeti-safe movement: non-physical follower using llSetPos() llSetPos(desired); llRegionSay(gChan, "AUD|" + gToken + "|" + (string)llGetCameraPos()); // Boundary check every Nth follow tick (only after perms granted) if (gHasPerms && gBoundsReady) { gFollowTickCount++; if (gFollowTickCount >= BOUND_CHECK_EVERY) { gFollowTickCount = 0; boundaryCheck(); } } } // ============================================================ // SECTION: HELPERS — DROP-IN // ============================================================ vector pickDropPos(vector anchorPos) { float a = llFrand(TWO_PI); float r = llSqrt(llFrand(1.0)) * DROP_RADIUS; vector offset = ; return anchorPos + offset; } doDropIn(vector anchorPos) { if (gDidDrop) return; gDidDrop = TRUE; vector dest = pickDropPos(anchorPos); vector lookAt = llRot2Fwd(llGetCameraRot()); if (lookAt == ZERO_VECTOR) lookAt = <1,0,0>; osTeleportAgent(gController, dest, lookAt); if (AUTO_REQUEST_MENU) llRegionSay(gChan, "REQMENU|" + gToken + "|" + (string)gController); } requestDropIn() { if (!USE_DROPIN) return; if (gDidDrop) return; if (gController == NULL_KEY) return; // We need to scan for ArenaDROP. But we might still be scanning // for arena corners. The drop-in sensor uses ARENA_DROP_NAME which // is different from the corner names, so we handle it in the // run_time_permissions flow after corner scanning completes. // For now, stash intent and let the scan chain handle it. llSensor(ARENA_DROP_NAME, NULL_KEY, ACTIVE | PASSIVE, ARENA_SENSOR_RANGE, PI); } fallbackDropIn() { if (gDidDrop) return; doDropIn(FALLBACK_DROP_POS); } // ============================================================ // SECTION: HELPERS — SHUTDOWN // ============================================================ shutdown() { llReleaseControls(); llDie(); } // ============================================================ // SECTION: HELPERS — PRE-PERMISSION NUDGE // Move to avatar's position before requesting permissions // so the viewer sees us as a nearby object. // ============================================================ nudgeToController() { if (gController == NULL_KEY) return; list d = llGetObjectDetails(gController, [OBJECT_POS]); if (llGetListLength(d) < 1) return; vector avPos = llList2Vector(d, 0); // Place ourselves slightly in front and below chin vector nudgePos = avPos + <0.5, 0.0, 0.3>; osSetPrimitiveParams(llGetKey(), [PRIM_POSITION, nudgePos]); } // ============================================================ // SECTION: EVENTS // ============================================================ default { state_entry() { // Ensure non-physical so llSetPos works reliably llSetStatus(STATUS_PHYSICS, FALSE); // Start invisible — costume goes ON in INIT handler before perm request llSetAlpha(0.0, ALL_SIDES); // No timer until we have permissions llSetTimerEvent(0.0); // Reset gDidDrop = FALSE; gMissCount = 0; gFollowTickCount = 0; gOOBCount = 0; gBoundsReady = FALSE; gBoundScanPhase = 0; gOriginalScale = llGetScale(); } on_rez(integer start_param) { // Channel comes from rezzer as start_param gChan = start_param; gRezzer = NULL_KEY; // Reset gDidDrop = FALSE; gMissCount = 0; gFollowTickCount = 0; gOOBCount = 0; gBoundsReady = FALSE; gBoundScanPhase = 0; gOriginalScale = llGetScale(); // Listen immediately so we don't miss INIT if (gListenHandle) llListenRemove(gListenHandle); gListenHandle = llListen(gChan, "", NULL_KEY, ""); llSetTimerEvent(0.0); } listen(integer channel, string name, key id, string msg) { if (channel != gChan) return; // Accept sender if same owner OR same group (supports deeded rezzer). if (!llSameGroup(id)) { if (llGetOwnerKey(id) != llGetOwner()) return; } // Lock to first sender and tighten listener to it. if (gRezzer == NULL_KEY) { gRezzer = id; if (gListenHandle) llListenRemove(gListenHandle); gListenHandle = llListen(gChan, "", gRezzer, ""); } else { if (id != gRezzer) return; } list p = llParseString2List(msg, ["|"], []); string cmd = llList2String(p, 0); if (cmd == "INIT") { // INIT|token|avatarUUID string tok = llList2String(p, 1); key cand = (key)llList2String(p, 2); // basic sanity if (tok == "") return; if (cand == NULL_KEY) return; // ignore cross- once token is set if (gToken != "" && tok != gToken) return; // don't spam permission prompts if already granted if (gHasPerms) return; // must be an agent actually present if (llGetAgentSize(cand) == ZERO_VECTOR) return; // optional: require avatar near rezzer if (INIT_MAX_DIST > 0.0) { list rd = llGetObjectDetails(gRezzer, [OBJECT_POS]); list ad = llGetObjectDetails(cand, [OBJECT_POS]); if (llGetListLength(rd) == 1 && llGetListLength(ad) == 1) { vector rp = llList2Vector(rd, 0); vector ap = llList2Vector(ad, 0); if (llVecDist(rp, ap) > INIT_MAX_DIST) return; } } // commit gToken = tok; gController = cand; if (gPermPending) return; // --- PRE-PERMISSION: nudge to player, then costume on --- nudgeToController(); costumeOn(); // --- Now request permissions (viewer sees a nearby, visible object) --- gPermPending = TRUE; llRequestPermissions(gController, PERMISSION_TAKE_CONTROLS | PERMISSION_TRACK_CAMERA); return; } if (cmd == "OFF") { // OFF|token string tok = llList2String(p, 1); if (tok == gToken) shutdown(); return; } } run_time_permissions(integer perm) { gPermPending = FALSE; if (gController == NULL_KEY) return; // If denied, OpenSim may send 0; handle gracefully. if (!(perm & PERMISSION_TAKE_CONTROLS)) { llInstantMessage(gController, "SlugShooter could not take controls (permissions denied). Touch the rezzer prim again to try."); costumeOff(); shutdown(); return; } gHasPerms = TRUE; // --- COSTUME OFF: revert to invisible operational form --- costumeOff(); // Take LMB in mouselook llTakeControls(CONTROL_ML_LBUTTON, TRUE, FALSE); // Lore llInstantMessage(gController, LORE); // Begin arena corner scan chain (NE first, then SW, then finalize) startCornerScan(); // Drop them into the arena after lore (one-shot) // NOTE: requestDropIn fires its own sensor for ArenaDROP. // Corner scans may still be in progress; drop-in sensor uses // a different name so there's no collision — but LSL sensor // is single-fire. We chain: corners first, then drop-in last. // The sensor/no_sensor events handle the chain. // Start follow updates llSetTimerEvent(FOLLOW_TICK); } control(key id, integer level, integer edge) { id = NULL_KEY; // lint integer pressed = level & edge; if (pressed & CONTROL_ML_LBUTTON) fire(); } timer() { // If we're in fire cooldown, release lock and resume follow tick. if (gFireLock) { gFireLock = FALSE; llSetTimerEvent(FOLLOW_TICK); return; } // Normal follow update followTick(); } sensor(integer n) { integer i; // single declaration — LSL flat scope // ---- CORNER SCAN PHASE 1: looking for *ArenaNE ---- if (gBoundScanPhase == 1) { for (i = 0; i < n; ++i) { if (llDetectedName(i) == ARENA_NE_NAME) { gScannedNE = llDetectedPos(i); gFoundNE = TRUE; break; } } // Chain to phase 2: scan for SW gBoundScanPhase = 2; llSensor(ARENA_SW_NAME, NULL_KEY, ACTIVE | PASSIVE, ARENA_CORNER_RANGE, PI); return; } // ---- CORNER SCAN PHASE 2: looking for *ArenaSW ---- if (gBoundScanPhase == 2) { for (i = 0; i < n; ++i) { if (llDetectedName(i) == ARENA_SW_NAME) { gScannedSW = llDetectedPos(i); gFoundSW = TRUE; break; } } // Finalize corners and start drop-in finalizeCorners(); requestDropIn(); return; } // ---- DROP-IN PHASE: looking for ArenaDROP ---- if (gDidDrop) return; for (i = 0; i < n; ++i) { if (llDetectedName(i) == ARENA_DROP_NAME) { vector anchorPos = llDetectedPos(i); doDropIn(anchorPos); return; } } // Unexpected: sensor returned hits but none matched ArenaDROP fallbackDropIn(); } no_sensor() { // ---- CORNER SCAN PHASE 1 MISS: NE not found ---- if (gBoundScanPhase == 1) { // NE not found, will use fallback. Chain to SW scan. gBoundScanPhase = 2; llSensor(ARENA_SW_NAME, NULL_KEY, ACTIVE | PASSIVE, ARENA_CORNER_RANGE, PI); return; } // ---- CORNER SCAN PHASE 2 MISS: SW not found ---- if (gBoundScanPhase == 2) { // SW not found, will use fallback. Finalize and proceed to drop-in. finalizeCorners(); requestDropIn(); return; } // ---- DROP-IN MISS: ArenaDROP not found ---- fallbackDropIn(); } // If someone manages to touch the invisible object (rare), remove it. touch_start(integer n) { n = 0; if (gController != NULL_KEY) llInstantMessage(gController, "SlugShooter removed."); shutdown(); } }