/source/texture-changing/texture-changing.lsl/texture-changing.lsl |
@@ -0,0 +1,664 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This is a script that uses Corrade to change the texture on the |
// available faces of the primitie. It requires that you have a Corrade bot |
// with the following permissions enabled for the group specified in the |
// configuration notecard inside this primitive: |
// |
// config -> groups -> group [your group] -> permissions -> interact |
// config -> groups -> group [your group] -> permissions -> friendship |
// |
// Additionally, you should be a friend of your bot and have granted modify |
// permissions to the bot by using the viewer interface: |
// |
// Contact -> Friends -> [ Bot ] -> Friend can edit, delete, take objects. |
// |
// The sit script works together with a "configuration" notecard that must |
// be placed in the same primitive as this script. The purpose of this |
// script is to demonstrate changing textures with Corrade and you are free |
// to use, change, and commercialize it under the GNU/GPLv3 license at: |
// http://www.gnu.org/licenses/gpl.html |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]); |
if(i != -1) return llList2String(a, 2*i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
vector wasCirclePoint(float radius) { |
float x = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); |
float y = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); |
if(llPow(x,2) + llPow(y,2) <= llPow(radius,2)) |
return <x, y, 0>; |
return wasCirclePoint(radius); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasCSVToList(string csv) { |
list l = []; |
list s = []; |
string m = ""; |
do { |
string a = llGetSubString(csv, 0, 0); |
csv = llDeleteSubString(csv, 0, 0); |
if(a == ",") { |
if(llList2String(s, -1) != "\"") { |
l += m; |
m = ""; |
jump continue; |
} |
m += a; |
jump continue; |
} |
if(a == "\"" && llGetSubString(csv, 0, 0) == a) { |
m += a; |
csv = llDeleteSubString(csv, 0, 0); |
jump continue; |
} |
if(a == "\"") { |
if(llList2String(s, -1) != a) { |
s += a; |
jump continue; |
} |
s = llDeleteSubList(s, -1, -1); |
jump continue; |
} |
m += a; |
@continue; |
} while(csv != ""); |
// postcondition: length(s) = 0 |
return l + m; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasListToCSV(list l) { |
list v = []; |
do { |
string a = llDumpList2String( |
llParseStringKeepNulls( |
llList2String( |
l, |
0 |
), |
["\""], |
[] |
), |
"\"\"" |
); |
if(llParseStringKeepNulls( |
a, |
[" ", ",", "\n", "\""], [] |
) != |
(list) a |
) a = "\"" + a + "\""; |
v += a; |
l = llDeleteSubList(l, 0, 0); |
} while(l != []); |
return llDumpList2String(v, ","); |
} |
|
// corrade data |
string CORRADE = ""; |
string GROUP = ""; |
string PASSWORD = ""; |
|
// for holding the callback URL |
string callback = ""; |
|
// for holding the selected face number |
string FACE = ""; |
// for holding the selected texture |
string TEXTURE = ""; |
// for holding the current user |
key TOUCH = NULL_KEY; |
|
// for notecard reading |
integer line = 0; |
|
// key-value data will be read into this list |
list tuples = []; |
|
default { |
state_entry() { |
if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find an inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading configuration file..."); |
llGetNotecardLine("configuration", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// invariant, length(tuples) % 2 == 0 |
if(llGetListLength(tuples) % 2 != 0) { |
llOwnerSay("Error in configuration notecard."); |
return; |
} |
CORRADE = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"corrade" |
] |
) |
+1); |
if(CORRADE == "") { |
llOwnerSay("Error in configuration notecard: corrade"); |
return; |
} |
GROUP = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"group" |
] |
) |
+1); |
if(GROUP == "") { |
llOwnerSay("Error in configuration notecard: password"); |
return; |
} |
PASSWORD = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"password" |
] |
) |
+1); |
if(GROUP == "") { |
llOwnerSay("Error in configuration notecard: group"); |
return; |
} |
// DEBUG |
llOwnerSay("Read configuration file..."); |
state url; |
} |
if(data == "") jump continue; |
integer i = llSubStringIndex(data, "#"); |
if(i != -1) data = llDeleteSubString(data, i, -1); |
list o = llParseString2List(data, ["="], []); |
// get rid of starting and ending quotes |
string k = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
0 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
string v = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
1 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
if(k == "" || v == "") jump continue; |
tuples += k; |
tuples += v; |
@continue; |
llGetNotecardLine("configuration", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state detect { |
state_entry() { |
// DEBUG |
llOwnerSay("Detecting if Corrade is online..."); |
llSetTimerEvent(5); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llSetTimerEvent(30); |
return; |
} |
llSensorRepeat("", (key)CORRADE, AGENT, 10, TWO_PI, 1); |
} |
no_sensor() { |
// DEBUG |
llOwnerSay("Teleporting Corrade..."); |
llInstantMessage((key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "teleport", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"entity", "region", |
"region", wasURLEscape(llGetRegionName()), |
"position", wasURLEscape( |
(string)( |
llGetPos() + wasCirclePoint(1) |
) |
), |
"callback", callback |
] |
) |
); |
} |
sensor(integer num) { |
llSetTimerEvent(0); |
state check_rights; |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "teleport" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Teleport failed: " + |
wasURLUnescape( |
wasKeyValueGet( |
"error", |
body |
) |
) |
); |
state detect_trampoline; |
} |
llSetTimerEvent(0); |
state check_rights; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
/* |
* Trampoline used for delaying teleport requests sent to Corrade. Once code |
* switches into this state, the script waits for some seconds and then |
* switches to the detect state. |
*/ |
state detect_trampoline { |
state_entry() { |
// DEBUG |
llOwnerSay("Sleeping..."); |
llSetTimerEvent(30); |
} |
timer() { |
llSetTimerEvent(0); |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
/* |
* This state checks to see whether Corrade has the necessary rights to |
* modify your objects. It checks by using the "getfrienddata" command |
* which requires "friendship" Corrade permissions and then tests whether |
* the "CanModifyTheirObjects" flag is set that indicates whether Corrade |
* is able to alter your objects. In the end it proceeds to the main state. |
*/ |
state check_rights { |
state_entry() { |
// DEBUG |
llOwnerSay("Checking whether Corrade has rights to modify your objects..."); |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "getfrienddata", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"agent", wasURLEscape(llGetOwner()), |
"data", "CanModifyTheirObjects", |
"callback", wasURLEscape(callback) |
] |
) |
); |
llSetTimerEvent(60); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "getfrienddata" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to get friend rights: " + |
wasURLUnescape( |
wasKeyValueGet( |
"error", |
body |
) |
) |
); |
state detect_trampoline; |
} |
// DEBUG |
llOwnerSay("Got friend rights, checking..."); |
list data = wasCSVToList(wasURLUnescape(wasKeyValueGet("data", body))); |
integer i = llListFindList(data, [ "CanModifyTheirObjects" ]); |
if(i == -1) { |
// DEBUG |
llOwnerSay("Friend data not returned by the \"getfrienddata\" command..."); |
state detect_trampoline; |
} |
if(llList2String(data, i+1) != "True") { |
// DEBUG |
llOwnerSay("Corrade cannot modify your objects, please grant Corrade permissions to modify your objects using your viewer..."); |
state detect_trampoline; |
} |
// DEBUG |
llOwnerSay("Corrade has permissions to modify your objects, proceeding..."); |
llSetTimerEvent(0); |
state select_face; |
} |
timer() { |
llSetTimerEvent(0); |
// DEBUG |
llOwnerSay("Timeout checking for friend rights..."); |
state detect_trampoline; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
/* |
* In this state we retrieve a face number from the user. |
*/ |
state select_face { |
state_entry() { |
if(TOUCH == NULL_KEY) { |
//DEBUG |
llOwnerSay("Please touch the primitive for a menu..."); |
return; |
} |
integer comChannel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6)); |
integer i = llGetNumberOfSides() - 1; |
list buttons = []; |
do { |
buttons += (string)i; |
llListen(comChannel, "", "", (string)i); |
} while(--i > -1); |
buttons += "default"; |
llListen(comChannel, "", "", "default"); |
buttons += "all"; |
llListen(comChannel, "", "", "all"); |
llDialog(TOUCH, "Please select a face to change...", buttons, comChannel); |
} |
touch_start(integer num) { |
TOUCH = llDetectedKey(0); |
integer comChannel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6)); |
integer i = llGetNumberOfSides() - 1; |
list buttons = []; |
do { |
buttons += (string)i; |
llListen(comChannel, "", "", (string)i); |
} while(--i > -1); |
buttons += "default"; |
llListen(comChannel, "", "", "default"); |
buttons += "all"; |
llListen(comChannel, "", "", "all"); |
llDialog(TOUCH, "Please select a face to change...", buttons, comChannel); |
} |
listen(integer channel, string name, key id, string face) { |
llOwnerSay("Got face: " + face); |
FACE = face; |
state select_texture; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
/* |
* In this state we let the user select a texture from inventory. |
*/ |
state select_texture { |
state_entry() { |
if(TOUCH == NULL_KEY) return; |
integer comChannel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6)); |
integer i = llGetInventoryNumber(INVENTORY_TEXTURE) -1; |
list buttons = []; |
do { |
string inventoryTexture = llGetInventoryName(INVENTORY_TEXTURE, i); |
buttons += inventoryTexture; |
llListen(comChannel, "", "", inventoryTexture); |
} while(--i > -1); |
llDialog(TOUCH, "Please select from the avilable textures...", buttons, comChannel); |
} |
touch_start(integer num) { |
TOUCH = llDetectedKey(0); |
integer comChannel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6)); |
integer i = llGetInventoryNumber(INVENTORY_TEXTURE) -1; |
list buttons = []; |
do { |
string inventoryTexture = llGetInventoryName(INVENTORY_TEXTURE, i); |
buttons += inventoryTexture; |
llListen(comChannel, "", "", inventoryTexture); |
} while(--i > -1); |
llDialog(TOUCH, "Please select from the avilable textures...", buttons, comChannel); |
} |
listen(integer channel, string name, key id, string inventoryTexture) { |
llOwnerSay("Got texture: " + inventoryTexture); |
TEXTURE = inventoryTexture; |
state change_texture; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
/* |
* This state is reponsible for setting the texture on the previously |
* selected face of this primitive. It makes a call to Corrade to set the |
* texture on the selected face and then returns to the face selection. |
* This state relies on the "interact" Corrade permission being set |
* for the group that you are using this script with. |
*/ |
state change_texture { |
state_entry() { |
// DEBUG |
llOwnerSay("Setting texture \"" + TEXTURE + "\" on face \"" + FACE + "\"."); |
llInstantMessage(CORRADE, wasKeyValueEncode( |
[ |
// set the texture for the 4th face |
// on a primitive in a 4m range |
// specified by UUID |
"command", "setprimitivetexturedata", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
// the UUID of the primitive |
"item", wasURLEscape(llGetKey()), |
"range", 5, |
// the face |
"face", FACE, |
// just set the texture to a texture UUID |
"data", wasURLEscape( |
wasListToCSV( |
[ |
"TextureID", llGetInventoryKey(TEXTURE) |
] |
) |
), |
"callback", wasURLEscape(callback) |
] |
) |
); |
llSetTimerEvent(60); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "setprimitivetexturedata" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to set texture: " + |
wasURLUnescape( |
wasKeyValueGet( |
"error", |
body |
) |
) |
); |
state detect_trampoline; |
} |
// DEBUG |
llOwnerSay("Texture set successfully..."); |
state select_face; |
} |
timer() { |
llSetTimerEvent(0); |
// DEBUG |
llOwnerSay("Timeout setting texture..."); |
state detect_trampoline; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|