Files
DM67-Developer 4a850d3a24 Add files via upload
Add initial NPC Framework files
2025-12-08 13:43:33 -06:00

848 lines
28 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Senses.lsl — ENHANCED with Universal Seating + Occupancy Detection Integration
// Now provides avatar position data for occupancy checking
// ---- Opcodes align with existing project
integer LM_INIT = 1;
integer LM_CONFIG = 300;
integer LM_FOLLOW = 210;
integer LM_UNFOLLOW = 211;
integer LM_SIT = 212;
integer LM_STAND = 214;
integer LM_TIMER_TICK = 250; // Master timer tick broadcast
integer LM_TIMER_CONFIG = 251; // Timer configuration update
integer LM_SENSE = 400; // Senses -> others
integer LM_SENSES_UPDATE = 401; // Send object data to Actions.lsl
integer LM_SEATING_UPDATE = 402; // NEW: Receive seating awareness from Actions.lsl
integer LM_AVATAR_UPDATE = 403; // NEW: Send avatar position data for occupancy detection
// ---- Notecard references
string CONFIG_CARD = "general.cfg";
// ---- Config defaults (overridden by config)
integer SENSES_ENABLED = TRUE;
float SENSES_TIMER_INTERVAL = 1.5; // seconds
float SENSES_RADIUS = 20.0; // meters
float SENSES_ARC_DEG = 90.0; // degrees
integer SENSES_TOPK_AVATARS = 3;
// Enhanced object detection configuration
integer SENSES_MAX_OBJECTS = 5; // Top objects to report
integer SENSES_INCLUDE_ATTACHMENTS = FALSE; // Skip worn items
integer SENSES_OBJECT_DETAILS = TRUE; // Enable detailed object info
integer SENSES_DEBUG = FALSE; // Debug mode for testing
// ---- Runtime
key NPC = NULL_KEY;
integer following = FALSE;
key followtarget = NULL_KEY;
integer sitting = FALSE;
// ---- Master Timer
float MASTER_TIMER_INTERVAL = 1.5; // Will be overridden by config
integer SENSES_CYCLE_OFFSET = 1; // Act on cycle 1
// NEW: Universal Seating Awareness variables
string current_seating_info = ""; // What the NPC is sitting on
integer seating_awareness_active = FALSE;
// FOV running tallies from last sensor event
integer fovscripted = 0;
integer fovpassive = 0;
// Enhanced object detail storage
list detected_objects = []; // Format: [name, desc, pos, distance, key, type]
// NEW: Avatar position storage for occupancy detection
list detected_avatars = []; // Format: [key, name, pos]
// ---- Helpers
list split_pipe(string s)
{
return llParseString2List(s, ["|"], []);
}
string join_pipe(list L)
{
return llDumpList2String(L, "|");
}
string getcard(string name)
{
if (llGetInventoryType(name) != INVENTORY_NOTECARD) return "";
return osGetNotecard(name);
}
vector getSelfPos()
{
if (NPC != NULL_KEY) return osNpcGetPos(NPC);
return llGetPos();
}
vector getAnchorPos()
{
return llGetRootPosition(); // anchor for FOV/range
}
rotation getAnchorRot()
{
// If we have an NPC, use their actual rotation
// Otherwise fall back to controller rotation
if (NPC != NULL_KEY)
{
rotation npc_rot = osNpcGetRot(NPC);
if (npc_rot != ZERO_ROTATION)
return npc_rot;
}
// Fallback to controller rotation if NPC not available
return llGetRootRotation();
}
string fmtFloat(float v, integer places)
{
float m = llPow(10.0, (float)places);
integer rounded = llRound(v * m);
if (places == 0)
return (string)rounded;
// Build string manually to avoid LSL's 6-decimal expansion
integer whole = rounded / llRound(m);
integer frac = rounded % llRound(m);
// Pad fractional part with leading zeros if needed
string frac_str = (string)frac;
while (llStringLength(frac_str) < places)
frac_str = "0" + frac_str;
return (string)whole + "." + frac_str;
}
string getRelativeDirection(vector npc_pos, rotation npc_rot, vector obj_pos)
{
// Get vector from NPC to object
vector to_obj = obj_pos - npc_pos;
// Transform to NPC's local coordinate system
vector local = to_obj / npc_rot;
// Calculate angle in horizontal plane
// After rotation division in LSL:
// local.x = left/right (positive = left, negative = right)
// local.y = forward/backward (positive = forward, negative = backward)
// For llAtan2(y, x): use forward as X-axis, left as Y-axis
float angle = llAtan2(local.y, local.x) * RAD_TO_DEG;
// TEMPORARY DEBUG to see where exactly the npc is seeing
if (SENSES_DEBUG)
{
llOwnerSay("DEBUG Direction: local.x=" + (string)local.x +
", local.y=" + (string)local.y +
", angle=" + (string)angle + " degrees");
}
// 8-direction classification (45-degree segments)
// Front: -22.5 to 22.5 degrees
// Front-Left: 22.5 to 67.5 degrees
// Left: 67.5 to 112.5 degrees
// Behind-Left: 112.5 to 157.5 degrees
// Behind: 157.5 to 180 and -180 to -157.5 degrees
// Behind-Right: -157.5 to -112.5 degrees
// Right: -112.5 to -67.5 degrees
// Front-Right: -67.5 to -22.5 degrees
if (angle >= -22.5 && angle < 22.5)
return "in front of me";
else if (angle >= 22.5 && angle < 67.5)
return "in front of me to my left";
else if (angle >= 67.5 && angle < 112.5)
return "to my left";
else if (angle >= 112.5 && angle < 157.5)
return "behind me to my left";
else if (angle >= 157.5 || angle < -157.5)
return "behind me ";
else if (angle >= -157.5 && angle < -112.5)
return "behind me to my right";
else if (angle >= -112.5 && angle < -67.5)
return "to my right";
else // angle >= -67.5 && angle < -22.5
return "front-right";
}
// Same as getRelativeDirection but with 180° flip for avatars
string getAvatarDirection(vector npc_pos, rotation npc_rot, vector obj_pos)
{
// 1) Vector from NPC to object in world coords
vector to_obj = obj_pos - npc_pos;
// 2) Rotate into NPClocal space (no extra flips needed)
vector local = to_obj / npc_rot;
// 3) Compute the local heading in degrees
float angle = llAtan2(local.y, local.x) * RAD_TO_DEG;
// 4) Normalize to [0,360)
if (angle < 0.0) angle += 360.0;
// 5) Bucket into eight 45° sectors
if (angle < 22.5 || angle >= 337.5) return "in front of me";
else if (angle < 67.5) return "in front of me to my left";
else if (angle < 112.5) return "to my left";
else if (angle < 157.5) return "behind me to my left";
else if (angle < 202.5) return "behind me";
else if (angle < 247.5) return "behind me to my right";
else if (angle < 292.5) return "to my right";
else return "in front of me to my right";
}
// Calculate relative height description
string getRelativeHeight(float npc_z, float obj_z)
{
float diff = obj_z - npc_z;
if (diff > 1.0)
return "above";
else if (diff < -1.0)
return "below";
else
return "level";
}
// FOV test: within radius and inside half-arc cone around +X forward, anchored to the prim
integer withinFOV(vector anchorpos, rotation anchorrot, vector targetpos)
{
float d = llVecDist(anchorpos, targetpos);
if (d > SENSES_RADIUS) return FALSE;
vector fwd = llRot2Fwd(anchorrot); // world-space forward (+X)
vector dir = llVecNorm(targetpos - anchorpos);
float dot = fwd.x*dir.x + fwd.y*dir.y + fwd.z*dir.z;
float halfArc = SENSES_ARC_DEG * DEG_TO_RAD * 0.5;
float cosLimit = llCos(halfArc);
if (dot >= cosLimit) return TRUE;
return FALSE;
}
// Sort and limit objects by distance
list sortObjectsByDistance(list objects)
{
integer count = llGetListLength(objects) / 9; // Changed from 6 to 9
if (count <= 1) return objects;
// Simple bubble sort by distance (index 3)
integer i;
integer j;
for (i = 0; i < count - 1; i++)
{
for (j = 0; j < count - i - 1; j++)
{
integer idx1 = j * 9 + 3; // distance index (changed from 6 to 9)
integer idx2 = (j + 1) * 9 + 3; // changed from 6 to 9
if (llList2Float(objects, idx1) > llList2Float(objects, idx2))
{
// Swap entire object records (9 elements each, changed from 6)
list temp = llList2List(objects, j * 9, j * 9 + 8); // Changed from +5 to +8
objects = llListReplaceList(objects,
llList2List(objects, (j + 1) * 9, (j + 1) * 9 + 8),
j * 9, j * 9 + 8);
objects = llListReplaceList(objects, temp,
(j + 1) * 9, (j + 1) * 9 + 8);
}
}
}
// Limit to max objects
if (count > SENSES_MAX_OBJECTS)
{
integer keep = SENSES_MAX_OBJECTS * 9; // Changed from 6 to 9
objects = llList2List(objects, 0, keep - 1);
}
return objects;
}
// Determine object type description
string getObjectType(integer objtype)
{
if (objtype & SCRIPTED) return "scripted";
if (objtype & PASSIVE) return "furniture";
if (objtype & AGENT) return "attachment";
return "object";
}
// Send detected objects to Actions.lsl with proper data format
sendObjectsToActions() {
integer count = llGetListLength(detected_objects) / 6;
if (count == 0) {
llMessageLinked(LINK_SET, LM_SENSES_UPDATE, "", NULL_KEY);
return;
}
string objectData = "";
integer i;
for (i = 0; i < count; i++) {
integer idx = i * 6;
string name = llList2String(detected_objects, idx);
string desc = llList2String(detected_objects, idx + 1);
vector pos = llList2Vector(detected_objects, idx + 2);
float dist = llList2Float(detected_objects, idx + 3);
key objKey = llList2Key(detected_objects, idx + 4);
string objType = llList2String(detected_objects, idx + 5);
// Use ~ separator and x:y:z format to avoid comma parsing issues
string posStr = (string)pos.x + ":" + (string)pos.y + ":" + (string)pos.z;
string objString = name + "~" + desc + "~" + posStr + "~" +
(string)dist + "~" + (string)objKey + "~" + objType;
if (i > 0) objectData += "|";
objectData += objString;
}
llMessageLinked(LINK_SET, LM_SENSES_UPDATE, objectData, NULL_KEY);
}
// NEW: Send avatar position data to Actions.lsl for occupancy detection
sendAvatarsToActions() {
integer count = llGetListLength(detected_avatars) / 3;
if (count == 0) {
llMessageLinked(LINK_SET, LM_AVATAR_UPDATE, "", NULL_KEY);
return;
}
string avatarData = "";
integer i;
for (i = 0; i < count; i++) {
integer idx = i * 3;
key avatarKey = llList2Key(detected_avatars, idx);
string avatarName = llList2String(detected_avatars, idx + 1);
vector avatarPos = llList2Vector(detected_avatars, idx + 2);
// Use ~ separator and x:y:z format
string posStr = (string)avatarPos.x + ":" + (string)avatarPos.y + ":" + (string)avatarPos.z;
string avatarString = (string)avatarKey + "~" + avatarName + "~" + posStr;
if (i > 0) avatarData += "|";
avatarData += avatarString;
}
llMessageLinked(LINK_SET, LM_AVATAR_UPDATE, avatarData, NULL_KEY);
}
// Build detailed objects field
string buildObjectsFieldDetailed()
{
if (llGetListLength(detected_objects) == 0)
{
return "OBJECTS=none_detected";
}
string result = "OBJECTS=";
integer count = llGetListLength(detected_objects) / 9; // Changed from 6 to 9
integer i;
vector npc_pos = getSelfPos();
rotation npc_rot = getAnchorRot();
for (i = 0; i < count; i++)
{
integer idx = i * 9; // Changed from 6 to 9
string name = llList2String(detected_objects, idx);
string desc = llList2String(detected_objects, idx + 1);
vector objpos = llList2Vector(detected_objects, idx + 2);
float dist = llList2Float(detected_objects, idx + 3);
key objkey = llList2Key(detected_objects, idx + 4);
string objtype = llList2String(detected_objects, idx + 5);
vector bbox_min = llList2Vector(detected_objects, idx + 6);
vector bbox_max = llList2Vector(detected_objects, idx + 7);
string objinfo = name;
if (desc != "" && desc != name)
{
objinfo += " (" + desc + ")";
}
// Calculate spatial information
string direction = getRelativeDirection(npc_pos, npc_rot, objpos);
string height_rel = getRelativeHeight(npc_pos.z, objpos.z);
// Calculate object size from bounding box
string size_str = "";
if (bbox_min != ZERO_VECTOR || bbox_max != ZERO_VECTOR)
{
vector size = bbox_max - bbox_min;
if (size.x >= 0.1 || size.y >= 0.1 || size.z >= 0.1)
{
string w = fmtFloat(size.x, 1);
string h = fmtFloat(size.z, 1); // Z is height
string d = fmtFloat(size.y, 1);
size_str = w + "×" + h + "×" + d + "m";
}
}
// Build the info string with enhanced spatial data
objinfo += " [" + objtype + ", " + direction;
if (height_rel != "level")
objinfo += ", " + height_rel;
objinfo += ", " + fmtFloat(dist, 1) + "m";
if (size_str != "")
objinfo += ", size:" + size_str;
objinfo += "]";
if (i > 0) result += ", ";
result += objinfo;
}
return result;
}
// Build top-K nearby human avatars using osGetAvatarList (stride: key, pos, name), anchored to the prim
// Returns up to k closest avatars from detected_avatars
// Returns up to k closest avatars with avatarflipped directions
list topKNearbyAvatars(integer k)
{
list out = [];
integer total = llGetListLength(detected_avatars) / 3;
if (total == 0) return out;
vector selfPos = llGetPos();
rotation selfRot = llGetRot();
list entries = [];
// Build distanceindexed entries with direction and height
integer idx;
for (idx = 0; idx < total; ++idx)
{
integer base = idx * 3;
key akey = llList2Key(detected_avatars, base);
string aname = llList2String(detected_avatars, base + 1);
vector apos = llList2Vector(detected_avatars, base + 2);
float dist = llVecDist(selfPos, apos);
// Avatarflipped direction
string dir = getAvatarDirection(selfPos, selfRot, apos);
// Relative height
string hgt = getRelativeHeight(selfPos.z, apos.z);
// Each entry: distance, key, name, pos, dir, hgt
entries += [
dist,
(string)akey,
aname,
llDumpList2String([apos], ","),
dir,
hgt
];
}
// Sort by distance (stride=6, index=0)
entries = llListSort(entries, 6, TRUE);
// Determine count
integer totalEntries = llGetListLength(entries) / 6;
integer count = (totalEntries < k) ? totalEntries : k;
// Build output list
integer e;
for (e = 0; e < count; ++e)
{
integer off = e * 6;
float d = llList2Float(entries, off + 0);
key key_s = (key)llList2String(entries, off + 1);
string nm = llList2String(entries, off + 2);
vector pos = (vector)llList2Vector(
llParseString2List(
llList2String(entries, off + 3),
[","], []),
0);
string dir = llList2String(entries, off + 4);
string hgt = llList2String(entries, off + 5);
// Build CSV info: key,name,distance,direction,height
string info = (string)key_s + "," +
nm + "," +
(string)d + "m," +
dir + "," +
hgt;
out += [ info ];
}
return out;
}
// NEW: Enhanced buildSelfField with universal seating awareness
string buildSelfField()
{
vector p = getSelfPos();
string pos = fmtFloat(p.x,3) + "," + fmtFloat(p.y,3) + "," + fmtFloat(p.z,3);
string selfField = "SELF=pos:" + pos +
",sit:" + (sitting ? "1" : "0") +
",follow:" + (following ? "1" : "0") +
",followtarget:" + (string)followtarget;
// NEW: Add universal seating awareness information
if (seating_awareness_active && current_seating_info != "") {
selfField += ",sitting_on:" + current_seating_info;
}
return selfField;
}
string buildAvatarsFieldFromList(list K)
{
string s = "AVATARS=" + (string)llGetListLength(K);
key owner = llGetOwner();
integer i;
for (i = 0; i < llGetListLength(K); i++)
{
string avatarData = llList2String(K, i);
// Parse to get the avatar key (first field)
list fields = llParseString2List(avatarData, [","], []);
if (llGetListLength(fields) > 0)
{
key avatarKey = (key)llList2String(fields, 0);
string avatarName = llList2String(fields, 1);
// Label self and owner
if (avatarKey == NPC)
{
avatarName = "self:" + avatarName;
}
else if (avatarKey == owner)
{
avatarName = "owner:" + avatarName;
}
// Rebuild the data with labeled name
fields = llListReplaceList(fields, [avatarName], 1, 1);
avatarData = llDumpList2String(fields, ",");
}
s += "," + avatarData;
}
return s;
}
// Owner summary fragment using same filters as avatar FOV
string buildOwnerFragment()
{
vector Apos = getAnchorPos();
rotation Arot = getAnchorRot();
key owner = llGetOwner();
if (owner == NULL_KEY || osIsNpc(owner)) return "";
vector op = llList2Vector(llGetObjectDetails(owner, [OBJECT_POS]), 0);
if (op == ZERO_VECTOR) return "";
if (!withinFOV(Apos, Arot, op)) return "";
float od = llVecDist(Apos, op);
string on = llKey2Name(owner);
if (on == "") on = (string)owner;
return "owner:" + on + "," + fmtFloat(od,1) + "m";
}
string buildObjectsField()
{
if (SENSES_OBJECT_DETAILS)
{
return buildObjectsFieldDetailed();
}
else
{
// Original simple format for backward compatibility
return "OBJECTS=scripted:" + (string)fovscripted + ",passive:" + (string)fovpassive;
}
}
// buildsummaryFromK with universal seating awareness
string buildsummaryFromK(list K)
{
string objectInfo = buildObjectsField();
string summary = "SUMMARY=" +
(following ? "following" : "idle") +
(sitting ? ",sitting" : "") +
",avatars=" + (string)llGetListLength(K) +
"," + objectInfo;
// NEW: Add universal seating awareness to summary
if (seating_awareness_active && current_seating_info != "") {
summary += ",currently_sitting_on=" + current_seating_info;
}
return summary;
}
integer loadconfig()
{
string txt = getcard(CONFIG_CARD);
if (txt == "") return FALSE;
list lines = llParseString2List(txt, ["\n"], []);
integer i;
for (i = 0; i < llGetListLength(lines); i++)
{
string line = llStringTrim(llList2String(lines, i), STRING_TRIM);
if (line == "") jump cont;
if (llGetSubString(line,0,0) == "#") jump cont;
if (llGetSubString(line,0,1) == "//") jump cont;
integer eq = llSubStringIndex(line, "=");
if (eq <= 0) jump cont;
string k = llStringTrim(llGetSubString(line, 0, eq-1), STRING_TRIM);
string v = llStringTrim(llGetSubString(line, eq+1, -1), STRING_TRIM);
if (k == "CONFIG_CARD") CONFIG_CARD = v;
else if (k == "SENSES_ENABLED") SENSES_ENABLED = (integer)v;
else if (k == "SENSES_TIMER_INTERVAL") SENSES_TIMER_INTERVAL = (float)v;
else if (k == "SENSES_RADIUS") SENSES_RADIUS = (float)v;
else if (k == "SENSES_ARC_DEG") SENSES_ARC_DEG = (float)v;
else if (k == "SENSES_TOPK_AVATARS") SENSES_TOPK_AVATARS = (integer)v;
// Enhanced object detection config
else if (k == "SENSES_MAX_OBJECTS") SENSES_MAX_OBJECTS = (integer)v;
else if (k == "SENSES_INCLUDE_ATTACHMENTS") SENSES_INCLUDE_ATTACHMENTS = (integer)v;
else if (k == "SENSES_OBJECT_DETAILS") SENSES_OBJECT_DETAILS = (integer)v;
else if (k == "SENSES_DEBUG") SENSES_DEBUG = (integer)v;
@cont;
}
return TRUE;
}
// ---- DEFAULT
default
{
state_entry()
{
loadconfig();
if (SENSES_ENABLED) {
float arc = SENSES_ARC_DEG * DEG_TO_RAD;
llSensorRepeat(
"", // name filter
NULL_KEY, // key filter
AGENT | ACTIVE | PASSIVE, // detect avatars + objects
SENSES_RADIUS,
SENSES_ARC_DEG * DEG_TO_RAD,
SENSES_TIMER_INTERVAL
);
llSetTimerEvent(SENSES_TIMER_INTERVAL); // Keep for coordination
}
llOwnerSay("Senses: Ready with Master Timer Coordination");
}
sensor(integer n)
{
detected_objects = []; // Clear previous scan
detected_avatars = []; // Clear previous scan
integer sc = 0;
integer pc = 0;
vector mypos = getSelfPos();
rotation myrot = getAnchorRot();
integer i;
for (i = 0; i < n; i++)
{
key objkey = llDetectedKey(i);
string name = llDetectedName(i);
vector objpos = llDetectedPos(i);
integer objtype = llDetectedType(i);
if (objtype & AGENT)
{
key akey = llDetectedKey(i);
string aname = llDetectedName(i);
vector apos = llDetectedPos(i);
// Skip the NPC's own key - NPCs don't see themselves
if (akey == NPC) jump skip_self;
detected_avatars += [ akey, aname, apos ];
@skip_self;
}
// Keep original counting for backward compatibility
if (objtype & SCRIPTED) sc += 1;
else if (objtype & PASSIVE) pc += 1;
// Enhanced detection only if enabled
if (!SENSES_OBJECT_DETAILS) jump nextobj;
if (!withinFOV(mypos, myrot, objpos)) jump nextobj;
// Skip attachments if configured
if (!SENSES_INCLUDE_ATTACHMENTS && (objtype & AGENT)) jump nextobj;
// Get additional details via llGetObjectDetails
list details = llGetObjectDetails(objkey, [OBJECT_DESC]);
string desc = "";
if (llGetListLength(details) > 0)
{
desc = llList2String(details, 0);
}
// Get bounding box for size calculations
vector bbox_min = ZERO_VECTOR;
vector bbox_max = ZERO_VECTOR;
list bbox = llGetBoundingBox(objkey);
if (llGetListLength(bbox) == 2)
{
bbox_min = llList2Vector(bbox, 0);
bbox_max = llList2Vector(bbox, 1);
}
// Calculate distance for sorting
float dist = llVecDist(mypos, objpos);
// Get object type description
string typeDesc = getObjectType(objtype);
// Store: name, desc, pos, distance, key, type
detected_objects += [name, desc, objpos, dist, objkey, typeDesc, bbox_min, bbox_max, objpos];
@nextobj;
}
// Sort objects by distance and keep top objects
detected_objects = sortObjectsByDistance(detected_objects);
// Keep original counters
fovscripted = sc;
fovpassive = pc;
}
no_sensor()
{
fovscripted = 0;
fovpassive = 0;
detected_objects = [];
}
link_message(integer sender, integer num, string str, key id)
{
if (num == LM_CONFIG)
{
NPC = id;
}
else if (num == LM_FOLLOW)
{
following = (id != NULL_KEY);
followtarget = id;
sitting = FALSE;
// Clear seating awareness when starting to follow
seating_awareness_active = FALSE;
current_seating_info = "";
}
else if (num == LM_UNFOLLOW)
{
following = FALSE;
followtarget = NULL_KEY;
}
else if (num == LM_SIT)
{
sitting = TRUE;
following = FALSE;
}
else if (num == LM_STAND)
{
sitting = FALSE;
// Clear seating awareness when standing
seating_awareness_active = FALSE;
current_seating_info = "";
}
// NEW: Handle universal seating awareness updates from Actions.lsl
else if (num == LM_SEATING_UPDATE)
{
list parts = llParseString2List(str, ["|"], []);
if (llGetListLength(parts) >= 2)
{
string action = llList2String(parts, 0);
string seatingInfo = llList2String(parts, 1);
if (action == "sitting_on")
{
seating_awareness_active = TRUE;
current_seating_info = seatingInfo;
}
else if (action == "standing")
{
seating_awareness_active = FALSE;
current_seating_info = "";
}
}
}
}
timer()
{
if (!SENSES_ENABLED) return;
integer current_cycle = (integer)(llGetTime() / MASTER_TIMER_INTERVAL) % 4;
// Only process and send data on our designated cycle
if (current_cycle == SENSES_CYCLE_OFFSET) {
// Compute avatars once and reuse for both fields and summary
list nearby = topKNearbyAvatars(SENSES_TOPK_AVATARS);
if (SENSES_DEBUG)
{
llOwnerSay("DEBUG: Timer cycle " + (string)current_cycle +
" - Processing " + (string)llGetListLength(nearby) +
" top avatars");
// Show what we're sending
integer k;
for (k = 0; k < llGetListLength(nearby); k++)
{
llOwnerSay(" -> " + llList2String(nearby, k));
}
}
string payload = join_pipe([
buildSelfField(),
buildAvatarsFieldFromList(nearby),
buildObjectsField(),
buildsummaryFromK(nearby)
]);
llMessageLinked(LINK_SET, LM_SENSE, payload, NPC);
// Send object data to Actions.lsl for seating detection
sendObjectsToActions();
// Send avatar position data to Actions.lsl for occupancy detection
sendAvatarsToActions();
}
}
on_rez(integer p)
{
llResetScript();
}
changed(integer c)
{
if (c & CHANGED_OWNER)
{
llResetScript();
}
}
}