I've hacked up a script of mine which does pretty much what you're asking (unless I've misread the request):
float REFRESH_TIME = 7200.0; // How frequently to clear recent collisions
list groupNames = [];
list groupMemberKeys = [];
list groupMemberKeysMap = [];
list groupMemberNames = [];
list groupMemberNamesMap = [];
list recentCollisions = [];
key dataKey = NULL_KEY;
key notecardKey = NULL_KEY;
integer notecardLine = 0;
default {
state_entry() {
llSetStatus(STATUS_PHANTOM, FALSE);
llVolumeDetect(TRUE);
string name = llGetInventoryName(INVENTORY_NOTECARD, 0);
if (name == "") llOwnerSay("No notecard found!");
else {
llOwnerSay("Loading " + name + "…");
notecardKey = llGetInventoryKey(name);
dataKey = llGetNotecardLine(notecardKey, (notecardLine = 0));
}
} changed(integer x) { if (x & CHANGED_INVENTORY) llResetScript(); }
dataserver(key id, string data) {
if (id != dataKey) dataKey = llGetNotecardLine(notecardKey, notecardLine);
if (data != EOF) {
list parts = llParseString2List((data = "") + data, [":", ","], []);
string groupName = llStringTrim(llList2String(parts, 0), STRING_TRIM);
integer names = 0; integer x = parts != []; list indices = [];
integer index = llListFindList(groupNames, [groupName]);
if (index < 0) {
index = groupNames != [];
if (index >= 32) {
llOwnerSay("Can't add " + groupName + ", group-limit reached");
jump skip;
}
}
while ((--x) >= 1) {
string name = llToLower(
llStringTrim(llList2String(parts, x), STRING_TRIM)
);
integer i = llListFindList(groupMemberNames, [name]);
integer groups = 0;
if (i >= 0) groups = llList2Integer(groupMemberNamesMap, i);
groups = groups | (1 << index);
if (i >= 0)
groupMemberNamesMap = llListReplaceList(
groupMemberNamesMap, [groups], i, i
);
else {
groupMemberNames += [name];
groupMemberNamesMap += [groups];
}
++names;
}
if (names > 0) {
if (index == (groupNames != []))
groupNames += (list)groupName;
} else llOwnerSay("Group " + groupName + " has no members!");
@skip;
dataKey = llGetNotecardLine(notecardKey, ++notecardLine);
} else {
llOwnerSay((string)(groupNames != []) + " groups loaded.");
state ready;
}
}
}
state ready {
state_entry() { llSetTimerEvent(REFRESH_TIME); }
timer() { recentCollisions = []; }
changed(integer x) { if (x & CHANGED_INVENTORY) llResetScript(); }
collision_start(integer x) {
// Check every collider
while ((--x) >= 0) {
// Check only agents
if (llDetectedType(x) & AGENT) {
key id = llDetectedKey(x);
list l = (list)id;
// Ignore recent-colliders
if (llListFindList(recentCollisions, l) < 0) {
string name = llDetectedName(x);
integer i = llListFindList(groupMemberKeys, l);
if (i >= 0)
i = llList2Integer(groupMemberKeysMap, i);
else if (
(i = llListFindList(
groupMemberNames,
[llToLower(name)]
)) >= 0) {
integer j = llList2Integer(groupMemberNamesMap, i);
groupMemberNames = llDeleteSubList(
groupMemberNames,
i,
i
);
groupMemberNamesMap = llDeleteSubList(
groupMemberNamesMap,
i,
i
);
groupMemberKeys += l;
groupMemberKeysMap += (list)(i = j);
} else i = 0;
if (i != 0) {
list groups = [];
integer j = 0; integer x = groupNames != [];
for (; j < x; ++j) {
if ((i >> j) & 1)
groups += llList2String(groupNames, j);
}
llSay(
0,
"Please welcome " + name +
", member of " + llList2CSV(groups)
);
}
recentCollisions += l;
}
}
}
}
}
To get the nicely formatted version, you should click the "Quote" button on my post and copy/paste from there.
This script takes groups in the following format:
<group_name>: <name_1>, <name_2>, ... <name_n>
i.e - the whole request is on a single-line. If you have multiple-lines with the same <group_name> then they will all be added to the same group, this is useful if you need to enter quite a few names (as scripts can only read 255 characters-per-line).
Here's an example:
Scripters: Haravikk Mistral, Viny Bailey
People: Joe Bloggs, John Smith
Scripters: Someone Else
This will result in a group called "Scripters" with members; Haravikk Mistral, Viny Bailey, and Someone Else. And a group called "People", with members; Joe Bloggs, and John Smith.
NOTE: This script allow avatars to belong to multiple groups (and will list all of them in the greeting), it keeps this speedy by using a 32-bit field, but this means you can only have 32 unique group-names in total.