mirror of
https://github.com/RuthAndRoth/Ruth2.git
synced 2026-08-14 09:02:22 +00:00
llList2ListStrided() behaves differently in Halcyon than Second Life when the src list has a single element. The workaround ensures that this does not happen with !R2 CONFIG notecards that have only a single data element. Signed-off-by: Serie Sumei <seriesumei@avimail.org>
556 lines
18 KiB
Plaintext
556 lines
18 KiB
Plaintext
// r2_hud_receiver.lsl - Ruth2 v3 HUD Receiver
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
// Copyright 2017 Shin Ingen
|
|
// Copyright 2019 Serie Sumei
|
|
|
|
// v3.0 02Apr2020 <seriesumei@avimail.org> - Based on ss-v5 from Controb/Serie Sumei
|
|
// v3.1 04Apr2020 <seriesumei@avimail.org> - Add alphamode and elements to v2 API
|
|
// v3.2 01May2020 <seriesumei@avimail.org> - Use notecard element map for skins, alpha
|
|
// v3.3 09May2020 <seriesumei@avimail.org> - Re-enable default hand animation
|
|
// v3.4 20May2020 <seriesumei@avimail.org> - Only listen when attached by default
|
|
// v3.5 19Jun2020 <seriesumei@avimail.org> - Changes for OpenSim 0.8 compatibility
|
|
// v3.6 16Aug2020 <seriesumei@avimail.org> - Rename fingernail and toenail regions
|
|
// v3.7 04Oct2020 <seriesumei@avimail.org> - Add a workaround for Halcyon
|
|
|
|
// This is a heavily modified version of Shin's RC3 receiver scripts for
|
|
// head, body, hands and feet combined into one.
|
|
//
|
|
// It has some requirements of the hands and feet mesh similar to that already
|
|
// on the body with regard to linking and prim naming. Link order does not
|
|
// matter, everything works based on the prim name and description fields.
|
|
|
|
// The commands have been expanded a bit to allow more flexibility in texturing
|
|
// the parts. It is still totally compatible with the RC2 and RC3 commands
|
|
// provided the APP_ID is correct.
|
|
|
|
// The app ID is used on calculating the actual channel number used for communication
|
|
// and must match in both the HUD and receivers.
|
|
integer APP_ID = 20181024;
|
|
integer APP_ID_ALT1 = 20171105;
|
|
|
|
// Listen on alternate app ID channel
|
|
integer MULTI_LISTEN = TRUE;
|
|
|
|
// Only listen when attached
|
|
integer ATTACHED_ONLY = TRUE;
|
|
|
|
// Which API version do we implement?
|
|
integer API_VERSION = 2;
|
|
|
|
// Enumerate the body region types as of Bakes on Mesh
|
|
// We added our fingernail and toenail types at the end
|
|
// The index of this list is the value of <body-region> in the element_map
|
|
|
|
list regions = [
|
|
"HEAD",
|
|
"UPPER",
|
|
"LOWER",
|
|
"EYES",
|
|
"SKIRT",
|
|
"HAIR",
|
|
"LEFTARM",
|
|
"LEFTLEG",
|
|
"AUX1",
|
|
"AUX2",
|
|
"AUX3",
|
|
"FNAILS",
|
|
"TNAILS"
|
|
];
|
|
|
|
// Any linkset that includes a part named "hands" will run the
|
|
// default hand pose
|
|
integer has_hands = FALSE;
|
|
string hand_animation = "bentohandrelaxedP1";
|
|
|
|
// Refresh hand animation wait, in seconds
|
|
// Set to 0.0 to disable the refresh
|
|
float hand_refresh = 30.0;
|
|
|
|
// Map prim name and descriptions to link numbers
|
|
list prim_map = [];
|
|
list prim_desc = [];
|
|
|
|
integer element_stride = 4;
|
|
list element_map = [];
|
|
list section_map = [];
|
|
|
|
// Spew some info
|
|
integer VERBOSE = FALSE;
|
|
|
|
// Memory limit
|
|
integer MEM_LIMIT = 48000;
|
|
|
|
// The name of the XTEA script
|
|
string XTEA_NAME = "r2_xtea";
|
|
|
|
// Set to encrypt 'message' and re-send on channel 'id'
|
|
integer XTEAENCRYPT = 13475896;
|
|
|
|
// Set in the reply to a received XTEAENCRYPT if the passed channel is 0 or ""
|
|
integer XTEAENCRYPTED = 8303877;
|
|
|
|
// Set to decrypt 'message' and reply vi llMessageLinked()
|
|
integer XTEADECRYPT = 4690862;
|
|
|
|
// Set in the reply to a received XTEADECRYPT
|
|
integer XTEADECRYPTED = 3450924;
|
|
|
|
integer haz_xtea = FALSE;
|
|
|
|
// ***
|
|
// Notecard
|
|
string DEFAULT_NOTECARD = "!R2 CONFIG";
|
|
string notecard_name;
|
|
key notecard_qid;
|
|
integer line;
|
|
string current_section;
|
|
string current_buffer;
|
|
integer reading_notecard = FALSE;
|
|
//***
|
|
|
|
// save the listen handles
|
|
integer listen_main;
|
|
integer listen_alt1;
|
|
integer r2channel;
|
|
integer r2channel_alt1;
|
|
integer last_attach = 0;
|
|
|
|
log(string msg) {
|
|
if (VERBOSE == 1) {
|
|
llOwnerSay(msg);
|
|
}
|
|
}
|
|
|
|
// See if the notecard is present in object inventory
|
|
integer can_haz_notecard(string name) {
|
|
integer count = llGetInventoryNumber(INVENTORY_NOTECARD);
|
|
while (count--) {
|
|
if (llGetInventoryName(INVENTORY_NOTECARD, count) == name) {
|
|
log("Found notecard: " + name);
|
|
return TRUE;
|
|
}
|
|
}
|
|
llOwnerSay("Notecard " + name + " not found");
|
|
return FALSE;
|
|
}
|
|
|
|
integer can_haz_xtea() {
|
|
// See if the XTEA script is present in object inventory
|
|
integer count = llGetInventoryNumber(INVENTORY_SCRIPT);
|
|
while (count--) {
|
|
if (llGetInventoryName(INVENTORY_SCRIPT, count) == XTEA_NAME) {
|
|
log("XTEA encryption enabled");
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
send(string msg) {
|
|
llSay(r2channel+1, msg);
|
|
if (VERBOSE == 1) {
|
|
llOwnerSay("S: " + msg);
|
|
}
|
|
}
|
|
|
|
// Send the list of command args as CSV, breaking up into chunks if the
|
|
// length exceeds 1000 chars. Chunked messages have a '+' char prepended
|
|
// to the comamnd word (first word in the list) for all but the last chunk.
|
|
send_csv(list msg) {
|
|
string strmsg = llList2CSV(msg);
|
|
if (llStringLength(strmsg) > 1000) {
|
|
// break it up
|
|
string cmd = llList2String(msg, 0);
|
|
strmsg = llList2CSV(llList2List(msg, 1, -1));
|
|
do {
|
|
// Send a chunk with a marker on the command
|
|
// Make the chunk a bit smaller than above to allow for command overhead
|
|
send("+" + cmd + "," + llGetSubString(strmsg, 0, 990));
|
|
strmsg = llGetSubString(strmsg, 991, -1);
|
|
} while (llStringLength(strmsg) > 990);
|
|
// Send the remaining bit without the marker so the receiver knows this is the end
|
|
send(cmd + "," + strmsg);
|
|
} else {
|
|
send(strmsg);
|
|
}
|
|
}
|
|
|
|
// Calculate a channel number based on APP_ID and owner UUID
|
|
integer keyapp2chan(integer id) {
|
|
return 0x80000000 | ((integer)("0x" + (string)llGetOwner()) ^ id);
|
|
}
|
|
|
|
map_linkset() {
|
|
// Create map of all links to prim names
|
|
integer i = 0;
|
|
integer num_links = llGetNumberOfPrims() + 1;
|
|
for (; i < num_links; ++i) {
|
|
list p = llGetLinkPrimitiveParams(i, [PRIM_NAME, PRIM_DESC]);
|
|
prim_map += [llToUpper(llList2String(p, 0))];
|
|
prim_desc += [llToUpper(llList2String(p, 1))];
|
|
}
|
|
}
|
|
|
|
load_notecard(string name) {
|
|
notecard_name = name;
|
|
if (notecard_name == "") {
|
|
notecard_name = DEFAULT_NOTECARD;
|
|
}
|
|
llOwnerSay("Reading notecard: " + notecard_name);
|
|
if (can_haz_notecard(notecard_name)) {
|
|
line = 0;
|
|
current_buffer = "";
|
|
element_map = [];
|
|
reading_notecard = TRUE;
|
|
notecard_qid = llGetNotecardLine(notecard_name, line);
|
|
} else {
|
|
// Force initialization when no notecard is present
|
|
late_init();
|
|
}
|
|
}
|
|
|
|
save_section() {
|
|
// Save what we have
|
|
log(" " + current_section + " " + (string)current_buffer);
|
|
// skin_config += current_buffer;
|
|
// skin_map += current_section;
|
|
|
|
// Clean up for next line
|
|
current_buffer = "";
|
|
current_section = "";
|
|
}
|
|
|
|
read_config(string data) {
|
|
if (data == EOF) {
|
|
// All done
|
|
save_section();
|
|
return;
|
|
}
|
|
|
|
data = llStringTrim(data, STRING_TRIM_HEAD);
|
|
if (data != "" && llSubStringIndex(data, "#") != 0) {
|
|
if (llSubStringIndex(data, "[") == 0) {
|
|
// Save previous section if valid
|
|
save_section();
|
|
// Section header
|
|
integer end = llSubStringIndex(data, "]");
|
|
if (end != 0) {
|
|
// Well-formed section header
|
|
current_section = llToUpper(llGetSubString(data, 1, end-1));
|
|
log("Reading section " + current_section);
|
|
// Reset section globals
|
|
}
|
|
} else {
|
|
if (current_section = "elements") {
|
|
// <prim-name>, <face>, <group>, <body-region>
|
|
list d = llCSV2List(data);
|
|
element_map += [
|
|
llToUpper(llList2String(d, 0)),
|
|
(integer)llList2String(d, 1),
|
|
llToUpper(llList2String(d, 2)),
|
|
(integer)llList2String(d, 3)
|
|
];
|
|
}
|
|
}
|
|
}
|
|
notecard_qid = llGetNotecardLine(notecard_name, ++line);
|
|
}
|
|
|
|
// ALPHA,<target>,<face>,<alpha>
|
|
do_alpha(list args) {
|
|
if (llGetListLength(args) > 3) {
|
|
string target = llStringTrim(llToUpper(llList2String(args, 1)), STRING_TRIM);
|
|
integer face = llList2Integer(args, 2);
|
|
float alpha = llList2Float(args, 3);
|
|
integer link = llListFindList(prim_map, [target]);
|
|
integer found = FALSE;
|
|
|
|
// Look for target in the section list
|
|
integer section = llListFindList(section_map, [target]);
|
|
if (target == "ALL") {
|
|
// Set entire linkset
|
|
integer i;
|
|
integer len = llGetListLength(prim_map);
|
|
|
|
for (; i < len; ++i) {
|
|
llSetLinkAlpha(i, alpha, face);
|
|
}
|
|
}
|
|
else if (link >= 0) {
|
|
// Target is a prim name
|
|
llSetLinkAlpha(link, alpha, face);
|
|
}
|
|
else if (section >= 0) {
|
|
// Put a texture on faces belonging to a group
|
|
integer len = llGetListLength(section_map);
|
|
integer i;
|
|
for (; i < len; ++i) {
|
|
// Look for matching sections in the element map
|
|
if (llList2String(section_map, i) == target) {
|
|
// Get link via link name in element_map
|
|
link = llListFindList(prim_map, [llList2String(element_map, i * element_stride)]);
|
|
if (link >= 0) {
|
|
llSetLinkAlpha(
|
|
link,
|
|
alpha,
|
|
llList2Integer(element_map, (i * element_stride) + 1)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ALPHAMODE,<target>,<face>,<alpha>
|
|
do_alphamode(list args) {
|
|
if (llGetListLength(args) > 4) {
|
|
string target = llStringTrim(llToUpper(llList2String(args, 1)), STRING_TRIM);
|
|
integer face = llList2Integer(args, 2);
|
|
integer alpha_mode = llList2Integer(args, 3);
|
|
integer mask_cutoff = llList2Integer(args, 4);
|
|
|
|
integer i;
|
|
integer len = llGetListLength(prim_map);
|
|
|
|
for (; i < len; ++i) {
|
|
string name = llList2String(prim_map, i);
|
|
log(" name: " + name);
|
|
if (name == target || target == "ALL") {
|
|
llSetLinkPrimitiveParamsFast(i, [
|
|
PRIM_ALPHA_MODE, face, alpha_mode, mask_cutoff
|
|
]);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// STATUS,<hud-api-version>
|
|
do_status(list args) {
|
|
send_csv(["STATUS", API_VERSION, last_attach]);
|
|
}
|
|
|
|
set_tex(string target, integer face, string texture, vector color) {
|
|
integer link = llListFindList(prim_map, [target]);
|
|
if (link >= 0) {
|
|
if (texture != "") {
|
|
llSetLinkPrimitiveParamsFast(
|
|
link,
|
|
[
|
|
PRIM_TEXTURE,
|
|
face,
|
|
texture,
|
|
<1,1,0>,
|
|
<0,0,0>,
|
|
0
|
|
]
|
|
);
|
|
}
|
|
if (color.x > -1) {
|
|
// Only set if color is valid
|
|
llSetLinkColor(link, color, face);
|
|
}
|
|
}
|
|
}
|
|
|
|
// TEXTURE,<target>,<texture>[,<face>,<color>]
|
|
do_texture(list args) {
|
|
// Check for v1 args
|
|
if (llGetListLength(args) >= 3) {
|
|
string target = llStringTrim(llToUpper(llList2String(args, 1)), STRING_TRIM);
|
|
string texture = llList2String(args, 2);
|
|
integer face = ALL_SIDES;
|
|
vector color = <-1, 0, 0>; // not a legal color so we can test for it
|
|
if (llGetListLength(args) > 3) {
|
|
// Get v2 face
|
|
face = llList2Integer(args, 3);
|
|
// Get v2 color arg
|
|
color = (vector)llList2String(args, 4);
|
|
}
|
|
integer region = llListFindList(regions, [llToUpper(target)]);
|
|
if (region < 0) {
|
|
// Assume target is a prim name
|
|
set_tex(target, face, texture, color);
|
|
} else {
|
|
// Put a texture on faces belonging to a group
|
|
list e3 = llList2ListStrided(llDeleteSubList(element_map, 0, 2), 0, -1, element_stride);
|
|
integer len = llGetListLength(e3);
|
|
integer i;
|
|
for (; i < len; ++i) {
|
|
// Look for matching groups in the element map
|
|
if (llList2Integer(e3, i) == region) {
|
|
set_tex(
|
|
llList2String(element_map, i * element_stride),
|
|
llList2Integer(element_map, (i * element_stride) + 1),
|
|
texture,
|
|
color
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialization after notecard has been completely read
|
|
late_init() {
|
|
// Map sections from notecard
|
|
// section is third in stride
|
|
section_map = llList2ListStrided(llDeleteSubList(element_map, 0, 1), 0, -1, element_stride);
|
|
|
|
has_hands = (llListFindList(section_map, ["HANDS"]) >= 0 &&
|
|
llGetInventoryType(hand_animation) == INVENTORY_ANIMATION);
|
|
if (has_hands) {
|
|
log("Using hand animation " + hand_animation);
|
|
llRequestPermissions(llGetOwner(), PERMISSION_TRIGGER_ANIMATION);
|
|
}
|
|
|
|
// Set up listener
|
|
listen_main = 0;
|
|
listen_alt1 = 0;
|
|
r2channel = keyapp2chan(APP_ID);
|
|
r2channel_alt1 = keyapp2chan(APP_ID_ALT1);
|
|
if (!ATTACHED_ONLY || last_attach > 0) {
|
|
listen_main = llListen(r2channel, "", "", "");
|
|
if (MULTI_LISTEN) {
|
|
listen_alt1 = llListen(r2channel_alt1, "", "", "");
|
|
}
|
|
}
|
|
}
|
|
|
|
default {
|
|
state_entry() {
|
|
// Set up memory constraints
|
|
llSetMemoryLimit(MEM_LIMIT);
|
|
|
|
haz_xtea = can_haz_xtea();
|
|
|
|
// Initialize attach state
|
|
last_attach = llGetAttached();
|
|
log("state_entry() attached="+(string)last_attach);
|
|
|
|
map_linkset();
|
|
|
|
reading_notecard = FALSE;
|
|
load_notecard(notecard_name);
|
|
|
|
log("Free memory " + (string)llGetFreeMemory() + " Limit: " + (string)MEM_LIMIT);
|
|
}
|
|
|
|
dataserver(key query_id, string data) {
|
|
if (query_id == notecard_qid) {
|
|
read_config(data);
|
|
if (data == EOF) {
|
|
// Do end work here
|
|
reading_notecard = FALSE;
|
|
|
|
// Add a work-aound for Halcyon behaviour in llList2ListStrided()
|
|
// that we seen when the config notecard only has a single data
|
|
// line in a section. By adding a list element that will otherwise
|
|
// be ignored we avoid passing in a list of length 1.
|
|
element_map += [0];
|
|
|
|
late_init();
|
|
llOwnerSay("Finished reading notecard " + notecard_name);
|
|
llOwnerSay("Free memory " + (string)llGetFreeMemory() + " Limit: " + (string)MEM_LIMIT);
|
|
}
|
|
}
|
|
}
|
|
|
|
run_time_permissions(integer perm) {
|
|
if (has_hands && (perm & PERMISSION_TRIGGER_ANIMATION)) {
|
|
llStopAnimation(hand_animation);
|
|
llStartAnimation(hand_animation);
|
|
llSetTimerEvent(hand_refresh);
|
|
}
|
|
}
|
|
|
|
timer() {
|
|
if (has_hands) {
|
|
llRequestPermissions(llGetOwner(), PERMISSION_TRIGGER_ANIMATION);
|
|
}
|
|
}
|
|
|
|
listen(integer channel, string name, key id, string message) {
|
|
if (llGetOwnerKey(id) == llGetOwner()) {
|
|
if (channel == r2channel || channel == r2channel_alt1) {
|
|
log("R: " + message);
|
|
list cmdargs = llCSV2List(message);
|
|
string command = llToUpper(llList2String(cmdargs, 0));
|
|
|
|
if (command == "ALPHA") {
|
|
do_alpha(cmdargs);
|
|
}
|
|
else if (command == "ALPHAMODE") {
|
|
do_alphamode(cmdargs);
|
|
}
|
|
else if (command == "ELEMENTS") {
|
|
send_csv(["ELEMENTS", ""]);
|
|
// send_csv(["ELEMENTS", llList2Json(JSON_ARRAY, element_map)]);
|
|
}
|
|
else if (command == "RESETANIM") {
|
|
llRequestPermissions(llGetOwner(), PERMISSION_TRIGGER_ANIMATION);
|
|
}
|
|
else if (command == "STATUS") {
|
|
do_status(cmdargs);
|
|
}
|
|
else if (command == "TEXTURE") {
|
|
do_texture(cmdargs);
|
|
}
|
|
else {
|
|
if (haz_xtea) {
|
|
llMessageLinked(LINK_THIS, XTEADECRYPT, message, "");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
link_message(integer sender_number, integer number, string message, key id) {
|
|
if (number == XTEADECRYPTED) {
|
|
list cmdargs = llCSV2List(message);
|
|
string command = llToUpper(llList2String(cmdargs, 0));
|
|
|
|
if (command == "ALPHA") {
|
|
do_alpha(cmdargs);
|
|
}
|
|
else if (command == "ALPHAMODE") {
|
|
do_alphamode(cmdargs);
|
|
}
|
|
else if (command == "ELEMENTS") {
|
|
send_csv(["ELEMENTS", ""]);
|
|
// send_csv(["ELEMENTS", llList2Json(JSON_ARRAY, element_map)]);
|
|
}
|
|
else if (command == "RESETANIM") {
|
|
llRequestPermissions(llGetOwner(), PERMISSION_TRIGGER_ANIMATION);
|
|
}
|
|
else if (command == "STATUS") {
|
|
do_status(cmdargs);
|
|
}
|
|
else if (command == "TEXTURE") {
|
|
do_texture(cmdargs);
|
|
}
|
|
}
|
|
}
|
|
|
|
attach(key id) {
|
|
if (id == NULL_KEY) {
|
|
// Reset attach state
|
|
last_attach = 0;
|
|
llListenRemove(listen_main);
|
|
llListenRemove(listen_alt1);
|
|
} else {
|
|
// Record attach state
|
|
last_attach = llGetAttached();
|
|
late_init();
|
|
}
|
|
log("attach() attached="+(string)last_attach);
|
|
}
|
|
|
|
changed(integer change) {
|
|
if (change & (CHANGED_OWNER | CHANGED_INVENTORY)) {
|
|
llResetScript();
|
|
}
|
|
}
|
|
}
|