Welcome to the Second Life Forums Archive

These forums are CLOSED. Please visit the new forums HERE

Discussion: The Color Master

Tyken Hightower
Automagical
Join date: 15 Feb 2006
Posts: 472
02-16-2007 17:06
UPDATE 02/26/07: Pasted new code into this post. Allows for prims to be part of multiple groups (explained in the script comments at top), and fixed minor bugs.

One night, out of boredom, I decided it would be fun and potentially useful to make a script that acts as a coloring engine (including alpha) for any whole object. I know many people tend to rely on color-changing abilities in their products by means of putting a script in every prim and having them take commands link messages. While this is generally an elegant solution that takes relatively little time to implement (all you need to do is make a script for each set of prims that needs to be colored, or, more tediously, for individual prims if they require different faces).
However, we've probably all seen things like the 170-prim (or more) wings, cars, etc., that have a script in every prim. Clearly, at this point, the solution is no longer efficient for simulator performance. So to that end, I made a solution that allows for all of this, in a single script.

Current Features (at time of posting)
> Fully menu-driven user interface
> Optional full-feature object-only control through link messages, plus the ability to trigger a menu for a specific avatar
> Definitions for specific sets of prims to color
> Definitions for specific faces on any prim
> Coloring options for specific prims on touch
> Coloring options for defined sets or all prims (can be a part of multiple groups)

In order to define sets of prims and faces, I store information in the linked prims' names, as well as the object's description. The arguments are separated by the '/' character. In the object description are the names of all the defined groups, the group the root prim belongs to (if any), and the predefined faces for the root prim (if any). The child prims store the same data in their names, except that the first argument in any child is the name of the child (if you want to name it), not the names of all of the groups. Finally, names of the groups are separated by commas, and the predefined faces are separated just by '/'s.

An example would look like this:
Root prim's description: "Foo,Bar/Foo/0/1/3"
Child prim's name: "Random Child Prim Name/Bar/0/2/3"

This means that there are two predefined groups, "Foo" and "Bar". The root prim is a member of "Foo," and the example child prim is a member of "Bar." The predefined faces to color on the root prim are 0, 1, and 3; the predefined faces on the child prim are 0, 2, and 3.

Play around with it; comments/inquiries/suggestions are welcome. Most of the code is not commented well, except the link message controls. I personally believe the purposes of many functions and such are obvious, although that's clearly only IMHO. ;)
I've mildly tested everything (except the link message controls), but it should all work fluidly. I made this in 2 days off and on, so it's not entirely as elegant as it could be. Some parts (particularly the listen conditionals) are spaghetti code (definitely needs comments, too lazy), and might look cleaner with revision. The script uses 2 separate listens on random, rare channels that automatically close themselves after 2 minutes of inactivity.

Cheers!

CODE

//In order to define sets of prims and faces, I store information in the linked prims' names, as well as the object's description.
//The arguments are separated by the '/' character.
//In the object description are the names of all the defined possible groups, the group(s) the root prim belongs to (if any), and the predefined faces for the root prim (if any).
//The child prims store the same data in their names, except that the first argument in any child is the name of the child (if you want to name it), not the names of all of the possible groups.
//Finally, names of the groups are separated by commas, and the predefined faces are separated just by '/'s.

//An example would look like this:
//Root prim's description: "Foo,Bar/Foo/0/1/3"
//Child prim's name: "Random Child Prim Name/Bar/0/2/3"
//Example child prim that belongs to both groups: "Random Child Prim Name/Foo,Bar/0/2/3"

//This means that there are two predefined groups, "Foo" and "Bar".
//The root prim is a member of "Foo," and the example child prim is a member of "Bar." The second example belongs to both.
//The predefined faces to color on the root prim are 0, 1, and 3; the predefined faces on the child prim are 0, 2, and 3.


integer listener = 0;
integer com_channel = 0;
integer data_listen = 0;
integer data_channel = 0;

integer touch_active = TRUE;
integer plugin_use = FALSE;
integer touched_link = -1;
string touched_data = "";
string touched_name = "";
list touched_faces = [];
string auto = "ALL_SIDES";
string group = "";

vector color = <1.0,1.0,1.0>;
float alpha = 1.0;

key user_id = "";
string last_menu = "";
string sub_menu = "";
string adjusting = "";

float touch_timeout = 120.0;

open_channel() {
llListenRemove(listener);
llListenRemove(data_listen);
com_channel = (integer)(llCeil(llFrand(2000000000.0)) + 1000.0);
data_channel = -(integer)(llCeil(llFrand(2000000000.0)) + 1000.0);
listener = llListen(com_channel, "", user_id, "");
data_listen = llListen(data_channel, "", user_id, "");
llSetTimerEvent(touch_timeout);
}

string fetch_name(string data) {
if (touched_link == LINK_ROOT || touched_link == 0) return llGetObjectName();
else return llList2String(llParseStringKeepNulls(data, ["/"], []), 0);
}

string fetch_group(string data) {
return llList2String(llParseStringKeepNulls(data, ["/"], []), 1);
}

list fetch_faces(string data) {
list temp = llParseStringKeepNulls(data, ["/"], []);
integer i = 0;
integer length = (temp != []);
for (i = 0; i < length; i = -~i)
temp = llListReplaceList(temp, [llList2Integer(temp, i)], i, i);
return llList2List(temp, 2, -1);
}

list generate_groups() {
list groups = llCSV2List(llList2String(llParseStringKeepNulls(llGetObjectDesc(), ["/"], []), 0));
integer i = 0;
integer length = (groups != []);
for (i = 0; i < length; i = -~i) if (llList2String(groups, i) == "") groups = llDeleteSubList(groups, i, i);
return groups;
}

string format_color() {
return "Current Color: " + (string)((integer)(color.x * 255.0)) + "R " + (string)((integer)(color.y * 255.0)) + "G " + (string)((integer)(color.z * 255.0)) + "B";
}

string format_alpha() {
return "Current Opacity: " + (string)((integer)(alpha * 100.0)) + "%";
}

main_menu() {
last_menu = "main_menu";
string text = "";
list buttons = ["Set Prims", "All Prims"];

if (touched_link >= 0) {
buttons = ["This Prim"] + buttons;
touched_name = fetch_name(touched_data);
if (touched_name != "") text += "Selection: " + touched_name + "\n\n";
else text += "Selection: [Unnamed Prim]\n\n";
}

if (plugin_use) buttons += ["Done"];

text += "Coloring options:";

open_channel();

llDialog(user_id, text, buttons, com_channel);
}

prim_menu() {
last_menu = "prim_menu";
string text = "";

if (touched_name != "") text += "Selection: " + touched_name + "\n";
else text += "Selection: [Unnamed Prim]\n";

if (touched_faces == []) touched_faces = fetch_faces(touched_data);
if (touched_faces == []) touched_faces = [ALL_SIDES];

text += "Selected Faces: [";
if (llList2Integer(touched_faces, 0) == ALL_SIDES) text += "ALL_SIDES]\n";
else text += llDumpList2String(touched_faces, ", ") + "]\n";

text += format_color() + "\n";
text += format_alpha() + "\n\n";

text += "Prim options:";

llDialog(user_id, text, ["Pick Faces", "Pick Color", "Apply Color", "Main Menu"], com_channel);
}

face_menu() {
sub_menu = "face_menu";
string text = "";

if (touched_name != "") text += "Selection: " + touched_name + "\n";
else text += "Selection: [Unnamed Prim]\n";

text += "Selected Faces: [";
if (llList2Integer(touched_faces, 0) == ALL_SIDES) text += "ALL_SIDES]\n\n";
else text += llDumpList2String(touched_faces, ", ") + "]\n\n";

text += "Face options:";

list buttons = [];
integer i = 0;
for (i = 0; i < 9; i = -~i) {
if (llListFindList(touched_faces, ) >= 0) buttons += ["- " + (string)i];
else buttons += ["+ " + (string)i];
}
buttons += ["+ ALL_SIDES", "Back"];

llDialog(user_id, text, buttons, data_channel);
}

color_menu() {
sub_menu = "color_menu";
string text = "Color Picker\n";

text += format_color() + "\n";
text += format_alpha() + "\n\n";

text += "Color options:";

llDialog(user_id, text, ["Red", "Green", "Blue", "Alpha", "Back"], com_channel);
}

color_adjust() {
sub_menu = "adjust_menu";
string text = adjusting + " Adjustment\n";

text += format_color() + "\n";
text += format_alpha() + "\n\n";

text += "Adjustment options:";

string char = llGetSubString(adjusting, 0, 0);
list buttons = ["- 1" + char, "- 15" + char, "- 50" + char, "+ 1" + char, "+ 15" + char, "+ 50" + char, "0" + char];
if (char == "A";) buttons += ["100A", "Back"];
else buttons += ["255" + char, "Back"];

llDialog(user_id, text, buttons, data_channel);
}

check_color() {
if (color.x < 0.0) color.x = 0.0;
else if (color.x > 1.0) color.x = 1.0;
if (color.y < 0.0) color.y = 0.0;
else if (color.y > 1.0) color.y = 1.0;
if (color.z < 0.0) color.z = 0.0;
else if (color.z > 1.0) color.z = 1.0;
if (alpha > 1.0) alpha = 1.0;
else if (alpha < 0.0) alpha = 0.0;
}

apply_color() {
if (last_menu == "prim_menu";) {
integer length = (touched_faces != []);
integer i = 0;
for (i = 0; i < length; i = -~i) {
llSetLinkColor(touched_link, color, llList2Integer(touched_faces, i));
llSetLinkAlpha(touched_link, alpha, llList2Integer(touched_faces, i));
}
} else if (last_menu == "group_menu";) {
integer prims = llGetNumberOfPrims();
integer i = 0;
if (auto == "Automatic";) {
if (llListFindList(llCSV2List(fetch_group(llGetObjectDesc())), [group]) > -1) {
list fetch = fetch_faces(llGetObjectDesc());
integer length = (fetch != []);
for (i = 0; i < length; i = -~i) {
llSetLinkColor(LINK_ROOT, color, llList2Integer(fetch, i));
llSetLinkAlpha(LINK_ROOT, alpha, llList2Integer(fetch, i));
}
}
integer n = 0;
for (i = 2; i <= prims; i = -~i) {
string link = llGetLinkName(i);
if (llListFindList(llCSV2List(fetch_group(link)), [group]) > -1) {
list fetch = fetch_faces(link);
integer length = (fetch != []);
for (n = 0; n < length; n = -~n) {
llSetLinkColor(i, color, llList2Integer(fetch, n));
llSetLinkAlpha(i, alpha, llList2Integer(fetch, n));
}
}
}
} else if (auto == "ALL_SIDES";) {
if (llListFindList(llCSV2List(fetch_group(llGetObjectDesc())), [group]) > -1) {
llSetLinkColor(LINK_ROOT, color, ALL_SIDES);
llSetLinkAlpha(LINK_ROOT, alpha, ALL_SIDES);
}
for (i = 2; i <= prims; i = -~i) {
string link = llGetLinkName(i);
if (llListFindList(llCSV2List(fetch_group(link)), [group]) > -1) {
llSetLinkColor(i, color, ALL_SIDES);
llSetLinkAlpha(i, alpha, ALL_SIDES);
}
}
}
} else if (last_menu == "object_menu";) {
integer prims = llGetNumberOfPrims();
integer i = 0;
if (auto == "Automatic";) {
list fetch = fetch_faces(llGetObjectDesc());
integer length = (fetch != []);
for (i = 0; i < length; i = -~i) {
llSetLinkColor(LINK_ROOT, color, llList2Integer(fetch, i));
llSetLinkAlpha(LINK_ROOT, alpha, llList2Integer(fetch, i));
}
integer n = 0;
for (i = 2; i <= prims; i = -~i) {
fetch = fetch_faces(llGetLinkName(i));
length = (fetch != []);
for (n = 0; n < length; n = -~n) {
llSetLinkColor(i, color, llList2Integer(fetch, n));
llSetLinkAlpha(i, alpha, llList2Integer(fetch, n));
}
}
} else if (auto == "ALL_SIDES";) {
llSetLinkColor(LINK_SET, color, ALL_SIDES);
llSetLinkAlpha(LINK_SET, alpha, ALL_SIDES);
}
}
}

group_menu() {
last_menu = "group_menu";
string text = "";

if (group != "";) text += "Selected Group: [" + group + "]\n";
text += "Selected Faces: [" + auto + "]\n";
text += format_color() + "\n";
text += format_alpha() + "\n\n";

text += "Group options:";

llDialog(user_id, text, ["Faces", "Pick Color", "Apply Color", "Pick Group", "Main Menu"], com_channel);
}

group_picker() {
sub_menu = "group_picker";
string text = "";

if (group != "";) text += "Selected Group: [" + group + "]\n\n";

text += "Available groups:";

list buttons = ["Back"];
list groups = generate_groups();
if (groups != []) buttons += groups;

llDialog(user_id, text, buttons, data_channel);
}

object_menu() {
last_menu = "object_menu";
string text = "";

text += "Selected Faces: [" + auto + "]\n";

text += format_color() + "\n";
text += format_alpha() + "\n\n";

text += "Object options:";

llDialog(user_id, text, ["Faces", "Pick Color", "Apply Color", "Main Menu"], com_channel);
}

default
{
state_entry() {
//llOwnerSay((string)llGetFreeMemory());
}

link_message(integer sender, integer data, string msg, key id) {
//Opens the touch menu for agent <id>
if (msg == "COLOR_MENU_OPEN";) {
plugin_use = TRUE;
user_id = id;
main_menu();
//Activates the touch menu trigger
} else if (msg == "COLOR_MENU_TOUCH_ON";) {
touch_active = TRUE;
//Removes the touch menu trigger
} else if (msg == "COLOR_MENU_TOUCH_OFF";) {
touch_active = FALSE;
//Sets the current color 255RGB scale ex: id = "50,100,150"
} else if (msg == "COLOR_MENU_COLOR";) {
list args = llCSV2List(id);
color = <llList2Float(args, 0), llList2Float(args, 1), llList2Float(args, 2)> / 255.0;
//Sets the current alpha 100% scale ex: id = "40"
} else if (msg == "COLOR_MENU_ALPHA";) {
alpha = (float)((string)id) / 100.0;
//Activates predefined prim faces to be painted on
} else if (msg == "COLOR_MENU_FACES_ON";) {
auto = "Automatic";
//Disables predefined prim face painting - ALL_SIDES will be used
} else if (msg == "COLOR_MENU_FACES_OFF";) {
auto = "ALL_SIDES";
//Paints on the specified prim group <id> ex: id = "Array 1"
//This is affected by predefined prim faces, if toggled on
} else if (msg == "COLOR_MENU_APPLY_GROUP";) {
group = (string)id;
last_menu = "group_menu";
apply_color();
//Paints on all prims in the object
//This is affected by predefined prim faces, if toggles on
} else if (msg == "COLOR_MENU_APPLY_ALL";) {
last_menu = "object_menu";
apply_color();
}
}

touch_start(integer number) {
if (touch_active) {
if (llDetectedKey(0) == llGetOwner()) {
plugin_use = FALSE;
if (llGetNumberOfPrims() > 1) {
touched_link = llDetectedLinkNumber(0);
touched_data = llGetLinkName(touched_link);
} else {
touched_link = 0;
touched_data = llGetObjectDesc();
}
user_id = llDetectedKey(0);
main_menu();
}
}
}

listen(integer channel, string name, key id, string msg) {
if (touch_active) llSetTimerEvent(touch_timeout);
if (channel == com_channel) {
if (msg == "This Prim";) prim_menu();
else if (msg == "Set Prims";) group_menu();
else if (msg == "All Prims";) object_menu();
else if (msg == "Pick Faces";) face_menu();
else if (msg == "Pick Color";) color_menu();
else if (msg == "Apply Color";) {
apply_color();
if (last_menu == "prim_menu";) prim_menu();
else if (last_menu == "group_menu";) group_menu();
else if (last_menu == "object_menu";) object_menu();
} else if (msg == "Back";) {
if (sub_menu == "adjust_menu";) color_menu();
else if (last_menu == "prim_menu";) prim_menu();
else if (last_menu == "group_menu";) group_menu();
else if (last_menu == "object_menu";) object_menu();
} else if (msg == "Pick Group";) {
group_picker();
} else if (msg == "Main Menu";) {
main_menu();
} else if (msg == "Red" || msg == "Green" || msg == "Blue" || msg == "Alpha";) {
adjusting = msg;
color_adjust();
} else if (msg == "Faces";) {
if (auto == "Automatic";) auto = "ALL_SIDES";
else if (auto == "ALL_SIDES";) auto = "Automatic";
if (last_menu == "object_menu";) object_menu();
else if (last_menu == "group_menu";) group_menu();
} else if (msg == "Done";) {
llMessageLinked(LINK_SET, 0, "COLOR_MENU_DONE", "";);
}
}
if (channel == data_channel) {
if (msg == "Back";) {
if (sub_menu == "adjust_menu";) color_menu();
else if (last_menu == "prim_menu";) prim_menu();
else if (last_menu == "group_menu";) group_menu();
else if (last_menu == "object_menu";) object_menu();
} else if (sub_menu == "face_menu";) {
if (msg == "+ ALL_SIDES";) touched_faces = [ALL_SIDES];
else if (llList2Integer(touched_faces, 0) == ALL_SIDES) touched_faces = [(integer)llGetSubString(msg, 2, 2)];
else if (llGetSubString(msg, 0, 0) == "+";) touched_faces += [(integer)llGetSubString(msg, 2, 2)];
else if (llGetSubString(msg, 0, 0) == "-";) {
integer index = llListFindList(touched_faces, [(integer)llGetSubString(msg, 2, 2)]);
touched_faces = llDeleteSubList(touched_faces, index, index);
}
face_menu();
} else if (sub_menu == "adjust_menu";) {
float amount = 0.0;
string lead = llGetSubString(msg, 0, 0);
if (lead == "0";) amount = -255.0;
else if (lead == "1" || lead == "2";) amount = 255.0;
else {
amount = (float)(llGetSubString(msg, 2, llStringLength(msg) - 2));
if (lead == "-";) amount = -amount;
}
string tail = llGetSubString(msg, -1, -1);
if (tail == "R";) color.x += (amount / 255.0);
else if (tail == "G";) color.y += (amount / 255.0);
else if (tail == "B";) color.z += (amount / 255.0);
else if (tail == "A";) alpha += (amount / 100.0);
check_color();
color_adjust();
} else if (sub_menu == "group_picker";) {
group = msg;
group_picker();
}
}
}

timer() {
llListenRemove(listener);
llListenRemove(data_listen);
llSetTimerEvent(0.0);
}
}
_____________________
Nada Epoch
The Librarian
Join date: 4 Nov 2002
Posts: 1,423
Original Thread
02-17-2007 14:25
/15/30/166639/1.html
_____________________
i've got nothing. ;)
Robin Peel
Registered User
Join date: 8 Feb 2005
Posts: 163
02-18-2007 01:56
*blushes* removed post
Tyken Hightower
Automagical
Join date: 15 Feb 2006
Posts: 472
02-22-2007 22:26
[NT]
_____________________
JoeTheCatboy Freelunch
Registered User
Join date: 14 Jun 2006
Posts: 42
03-10-2007 18:45
Wow. That looks complicated. All I usually do is just put llSetLinkedColor in a for loop in the main script
Naiman Broome
Registered User
Join date: 4 Aug 2007
Posts: 246
11-04-2007 03:04
Hi After I put in a prim and compile I get sintax error at 70 65 anyone can help?
Usagi Musashi
UM ™®
Join date: 24 Oct 2004
Posts: 6,083
11-04-2007 04:07
Very nice script. I be playing with this one
Dez Singh
NooB
Join date: 25 Apr 2007
Posts: 12
05-14-2008 12:44
From: Naiman Broome
Hi After I put in a prim and compile I get sintax error at 70 65 anyone can help?



Yeah I got it.
Usually when a script finds an "error" it places the marker right afterthe error itself.

in this case the error is a gap in the script text.

it looks like this..

(ll GetObjectDesc(), ["/"], []), 0));


It should be:

(llGetObjectDesc(), ["/"], []), 0));

(notice I have removed the space between ll and Get)


When you remove the space the text string should turn red in the reader.

Now press save and it will jump to another error of the same kind.
There are a total of 3 errors of the same kind in this script So I think I should just post the corrected version right away...


//In order to define sets of prims and faces, I store information in the linked prims' names, as well as the object's description.
//The arguments are separated by the '/' character.
//In the object description are the names of all the defined possible groups, the group(s) the root prim belongs to (if any), and the predefined faces for the root prim (if any).
//The child prims store the same data in their names, except that the first argument in any child is the name of the child (if you want to name it), not the names of all of the possible groups.
//Finally, names of the groups are separated by commas, and the predefined faces are separated just by '/'s.

//An example would look like this:
//Root prim's description: "Foo,Bar/Foo/0/1/3"
//Child prim's name: "Random Child Prim Name/Bar/0/2/3"
//Example child prim that belongs to both groups: "Random Child Prim Name/Foo,Bar/0/2/3"

//This means that there are two predefined groups, "Foo" and "Bar".
//The root prim is a member of "Foo," and the example child prim is a member of "Bar." The second example belongs to both.
//The predefined faces to color on the root prim are 0, 1, and 3; the predefined faces on the child prim are 0, 2, and 3.


integer listener = 0;
integer com_channel = 0;
integer data_listen = 0;
integer data_channel = 0;

integer touch_active = TRUE;
integer plugin_use = FALSE;
integer touched_link = -1;
string touched_data = "";
string touched_name = "";
list touched_faces = [];
string auto = "ALL_SIDES";
string group = "";

vector color = <1.0,1.0,1.0>;
float alpha = 1.0;

key user_id = "";
string last_menu = "";
string sub_menu = "";
string adjusting = "";

float touch_timeout = 120.0;

open_channel() {
llListenRemove(listener);
llListenRemove(data_listen);
com_channel = (integer)(llCeil(llFrand(2000000000.0)) + 1000.0);
data_channel = -(integer)(llCeil(llFrand(2000000000.0)) + 1000.0);
listener = llListen(com_channel, "", user_id, "";);
data_listen = llListen(data_channel, "", user_id, "";);
llSetTimerEvent(touch_timeout);
}

string fetch_name(string data) {
if (touched_link == LINK_ROOT || touched_link == 0) return llGetObjectName();
else return llList2String(llParseStringKeepNulls(data, ["/"], []), 0);
}

string fetch_group(string data) {
return llList2String(llParseStringKeepNulls(data, ["/"], []), 1);
}

list fetch_faces(string data) {
list temp = llParseStringKeepNulls(data, ["/"], []);
integer i = 0;
integer length = (temp != []);
for (i = 0; i < length; i = -~i)
temp = llListReplaceList(temp, [llList2Integer(temp, i)], i, i);
return llList2List(temp, 2, -1);
}

list generate_groups() {
list groups = llCSV2List(llList2String(llParseStringKeepNulls(llGetObjectDesc(), ["/"], []), 0));
integer i = 0;
integer length = (groups != []);
for (i = 0; i < length; i = -~i) if (llList2String(groups, i) == "";) groups = llDeleteSubList(groups, i, i);
return groups;
}

string format_color() {
return "Current Color: " + (string)((integer)(color.x * 255.0)) + "R " + (string)((integer)(color.y * 255.0)) + "G " + (string)((integer)(color.z * 255.0)) + "B";
}

string format_alpha() {
return "Current Opacity: " + (string)((integer)(alpha * 100.0)) + "%";
}

main_menu() {
last_menu = "main_menu";
string text = "";
list buttons = ["Set Prims", "All Prims"];

if (touched_link >= 0) {
buttons = ["This Prim"] + buttons;
touched_name = fetch_name(touched_data);
if (touched_name != "";) text += "Selection: " + touched_name + "\n\n";
else text += "Selection: [Unnamed Prim]\n\n";
}

if (plugin_use) buttons += ["Done"];

text += "Coloring options:";

open_channel();

llDialog(user_id, text, buttons, com_channel);
}

prim_menu() {
last_menu = "prim_menu";
string text = "";

if (touched_name != "";) text += "Selection: " + touched_name + "\n";
else text += "Selection: [Unnamed Prim]\n";

if (touched_faces == []) touched_faces = fetch_faces(touched_data);
if (touched_faces == []) touched_faces = [ALL_SIDES];

text += "Selected Faces: [";
if (llList2Integer(touched_faces, 0) == ALL_SIDES) text += "ALL_SIDES]\n";
else text += llDumpList2String(touched_faces, ", ";) + "]\n";

text += format_color() + "\n";
text += format_alpha() + "\n\n";

text += "Prim options:";

llDialog(user_id, text, ["Pick Faces", "Pick Color", "Apply Color", "Main Menu"], com_channel);
}

face_menu() {
sub_menu = "face_menu";
string text = "";

if (touched_name != "";) text += "Selection: " + touched_name + "\n";
else text += "Selection: [Unnamed Prim]\n";

text += "Selected Faces: [";
if (llList2Integer(touched_faces, 0) == ALL_SIDES) text += "ALL_SIDES]\n\n";
else text += llDumpList2String(touched_faces, ", ";) + "]\n\n";

text += "Face options:";

list buttons = [];
integer i = 0;
for (i = 0; i < 9; i = -~i) {
if (llListFindList(touched_faces, ) >= 0) buttons += ["- " + (string)i];
else buttons += ["+ " + (string)i];
}
buttons += ["+ ALL_SIDES", "Back"];

llDialog(user_id, text, buttons, data_channel);
}

color_menu() {
sub_menu = "color_menu";
string text = "Color Picker\n";

text += format_color() + "\n";
text += format_alpha() + "\n\n";

text += "Color options:";

llDialog(user_id, text, ["Red", "Green", "Blue", "Alpha", "Back"], com_channel);
}

color_adjust() {
sub_menu = "adjust_menu";
string text = adjusting + " Adjustment\n";

text += format_color() + "\n";
text += format_alpha() + "\n\n";

text += "Adjustment options:";

string char = llGetSubString(adjusting, 0, 0);
list buttons = ["- 1" + char, "- 15" + char, "- 50" + char, "+ 1" + char, "+ 15" + char, "+ 50" + char, "0" + char];
if (char == "A";) buttons += ["100A", "Back"];
else buttons += ["255" + char, "Back"];

llDialog(user_id, text, buttons, data_channel);
}

check_color() {
if (color.x < 0.0) color.x = 0.0;
else if (color.x > 1.0) color.x = 1.0;
if (color.y < 0.0) color.y = 0.0;
else if (color.y > 1.0) color.y = 1.0;
if (color.z < 0.0) color.z = 0.0;
else if (color.z > 1.0) color.z = 1.0;
if (alpha > 1.0) alpha = 1.0;
else if (alpha < 0.0) alpha = 0.0;
}

apply_color() {
if (last_menu == "prim_menu";) {
integer length = (touched_faces != []);
integer i = 0;
for (i = 0; i < length; i = -~i) {
llSetLinkColor(touched_link, color, llList2Integer(touched_faces, i));
llSetLinkAlpha(touched_link, alpha, llList2Integer(touched_faces, i));
}
} else if (last_menu == "group_menu";) {
integer prims = llGetNumberOfPrims();
integer i = 0;
if (auto == "Automatic";) {
if (llListFindList(llCSV2List(fetch_group(llGetObjectDesc())), [group]) > -1) {
list fetch = fetch_faces(llGetObjectDesc());
integer length = (fetch != []);
for (i = 0; i < length; i = -~i) {
llSetLinkColor(LINK_ROOT, color, llList2Integer(fetch, i));
llSetLinkAlpha(LINK_ROOT, alpha, llList2Integer(fetch, i));
}
}
integer n = 0;
for (i = 2; i <= prims; i = -~i) {
string link = llGetLinkName(i);
if (llListFindList(llCSV2List(fetch_group(link)), [group]) > -1) {
list fetch = fetch_faces(link);
integer length = (fetch != []);
for (n = 0; n < length; n = -~n) {
llSetLinkColor(i, color, llList2Integer(fetch, n));
llSetLinkAlpha(i, alpha, llList2Integer(fetch, n));
}
}
}
} else if (auto == "ALL_SIDES";) {
if (llListFindList(llCSV2List(fetch_group(llGetObjectDesc())), [group]) > -1) {
llSetLinkColor(LINK_ROOT, color, ALL_SIDES);
llSetLinkAlpha(LINK_ROOT, alpha, ALL_SIDES);
}
for (i = 2; i <= prims; i = -~i) {
string link = llGetLinkName(i);
if (llListFindList(llCSV2List(fetch_group(link)), [group]) > -1) {
llSetLinkColor(i, color, ALL_SIDES);
llSetLinkAlpha(i, alpha, ALL_SIDES);
}
}
}
} else if (last_menu == "object_menu";) {
integer prims = llGetNumberOfPrims();
integer i = 0;
if (auto == "Automatic";) {
list fetch = fetch_faces(llGetObjectDesc());
integer length = (fetch != []);
for (i = 0; i < length; i = -~i) {
llSetLinkColor(LINK_ROOT, color, llList2Integer(fetch, i));
llSetLinkAlpha(LINK_ROOT, alpha, llList2Integer(fetch, i));
}
integer n = 0;
for (i = 2; i <= prims; i = -~i) {
fetch = fetch_faces(llGetLinkName(i));
length = (fetch != []);
for (n = 0; n < length; n = -~n) {
llSetLinkColor(i, color, llList2Integer(fetch, n));
llSetLinkAlpha(i, alpha, llList2Integer(fetch, n));
}
}
} else if (auto == "ALL_SIDES";) {
llSetLinkColor(LINK_SET, color, ALL_SIDES);
llSetLinkAlpha(LINK_SET, alpha, ALL_SIDES);
}
}
}

group_menu() {
last_menu = "group_menu";
string text = "";

if (group != "";) text += "Selected Group: [" + group + "]\n";
text += "Selected Faces: [" + auto + "]\n";
text += format_color() + "\n";
text += format_alpha() + "\n\n";

text += "Group options:";

llDialog(user_id, text, ["Faces", "Pick Color", "Apply Color", "Pick Group", "Main Menu"], com_channel);
}

group_picker() {
sub_menu = "group_picker";
string text = "";

if (group != "";) text += "Selected Group: [" + group + "]\n\n";

text += "Available groups:";

list buttons = ["Back"];
list groups = generate_groups();
if (groups != []) buttons += groups;

llDialog(user_id, text, buttons, data_channel);
}

object_menu() {
last_menu = "object_menu";
string text = "";

text += "Selected Faces: [" + auto + "]\n";

text += format_color() + "\n";
text += format_alpha() + "\n\n";

text += "Object options:";

llDialog(user_id, text, ["Faces", "Pick Color", "Apply Color", "Main Menu"], com_channel);
}

default
{
state_entry() {
//llOwnerSay((string)llGetFreeMemory());
}

link_message(integer sender, integer data, string msg, key id) {
//Opens the touch menu for agent <id>
if (msg == "COLOR_MENU_OPEN";) {
plugin_use = TRUE;
user_id = id;
main_menu();
//Activates the touch menu trigger
} else if (msg == "COLOR_MENU_TOUCH_ON";) {
touch_active = TRUE;
//Removes the touch menu trigger
} else if (msg == "COLOR_MENU_TOUCH_OFF";) {
touch_active = FALSE;
//Sets the current color 255RGB scale ex: id = "50,100,150"
} else if (msg == "COLOR_MENU_COLOR";) {
list args = llCSV2List(id);
color = <llList2Float(args, 0), llList2Float(args, 1), llList2Float(args, 2)> / 255.0;
//Sets the current alpha 100% scale ex: id = "40"
} else if (msg == "COLOR_MENU_ALPHA";) {
alpha = (float)((string)id) / 100.0;
//Activates predefined prim faces to be painted on
} else if (msg == "COLOR_MENU_FACES_ON";) {
auto = "Automatic";
//Disables predefined prim face painting - ALL_SIDES will be used
} else if (msg == "COLOR_MENU_FACES_OFF";) {
auto = "ALL_SIDES";
//Paints on the specified prim group <id> ex: id = "Array 1"
//This is affected by predefined prim faces, if toggled on
} else if (msg == "COLOR_MENU_APPLY_GROUP";) {
group = (string)id;
last_menu = "group_menu";
apply_color();
//Paints on all prims in the object
//This is affected by predefined prim faces, if toggles on
} else if (msg == "COLOR_MENU_APPLY_ALL";) {
last_menu = "object_menu";
apply_color();
}
}

touch_start(integer number) {
if (touch_active) {
if (llDetectedKey(0) == llGetOwner()) {
plugin_use = FALSE;
if (llGetNumberOfPrims() > 1) {
touched_link = llDetectedLinkNumber(0);
touched_data = llGetLinkName(touched_link);
} else {
touched_link = 0;
touched_data = llGetObjectDesc();
}
user_id = llDetectedKey(0);
main_menu();
}
}
}

listen(integer channel, string name, key id, string msg) {
if (touch_active) llSetTimerEvent(touch_timeout);
if (channel == com_channel) {
if (msg == "This Prim";) prim_menu();
else if (msg == "Set Prims";) group_menu();
else if (msg == "All Prims";) object_menu();
else if (msg == "Pick Faces";) face_menu();
else if (msg == "Pick Color";) color_menu();
else if (msg == "Apply Color";) {
apply_color();
if (last_menu == "prim_menu";) prim_menu();
else if (last_menu == "group_menu";) group_menu();
else if (last_menu == "object_menu";) object_menu();
} else if (msg == "Back";) {
if (sub_menu == "adjust_menu";) color_menu();
else if (last_menu == "prim_menu";) prim_menu();
else if (last_menu == "group_menu";) group_menu();
else if (last_menu == "object_menu";) object_menu();
} else if (msg == "Pick Group";) {
group_picker();
} else if (msg == "Main Menu";) {
main_menu();
} else if (msg == "Red" || msg == "Green" || msg == "Blue" || msg == "Alpha";) {
adjusting = msg;
color_adjust();
} else if (msg == "Faces";) {
if (auto == "Automatic";) auto = "ALL_SIDES";
else if (auto == "ALL_SIDES";) auto = "Automatic";
if (last_menu == "object_menu";) object_menu();
else if (last_menu == "group_menu";) group_menu();
} else if (msg == "Done";) {
llMessageLinked(LINK_SET, 0, "COLOR_MENU_DONE", "";);
}
}
if (channel == data_channel) {
if (msg == "Back";) {
if (sub_menu == "adjust_menu";) color_menu();
else if (last_menu == "prim_menu";) prim_menu();
else if (last_menu == "group_menu";) group_menu();
else if (last_menu == "object_menu";) object_menu();
} else if (sub_menu == "face_menu";) {
if (msg == "+ ALL_SIDES";) touched_faces = [ALL_SIDES];
else if (llList2Integer(touched_faces, 0) == ALL_SIDES) touched_faces = [(integer)llGetSubString(msg, 2, 2)];
else if (llGetSubString(msg, 0, 0) == "+";) touched_faces += [(integer)llGetSubString(msg, 2, 2)];
else if (llGetSubString(msg, 0, 0) == "-";) {
integer index = llListFindList(touched_faces, [(integer)llGetSubString(msg, 2, 2)]);
touched_faces = llDeleteSubList(touched_faces, index, index);
}
face_menu();
} else if (sub_menu == "adjust_menu";) {
float amount = 0.0;
string lead = llGetSubString(msg, 0, 0);
if (lead == "0";) amount = -255.0;
else if (lead == "1" || lead == "2";) amount = 255.0;
else {
amount = (float)(llGetSubString(msg, 2, llStringLength(msg) - 2));
if (lead == "-";) amount = -amount;
}
string tail = llGetSubString(msg, -1, -1);
if (tail == "R";) color.x += (amount / 255.0);
else if (tail == "G";) color.y += (amount / 255.0);
else if (tail == "B";) color.z += (amount / 255.0);
else if (tail == "A";) alpha += (amount / 100.0);
check_color();
color_adjust();
} else if (sub_menu == "group_picker";) {
group = msg;
group_picker();
}
}
}

timer() {
llListenRemove(listener);
llListenRemove(data_listen);
llSetTimerEvent(0.0);
}
}