This variation adds the capability for donation jars placed on the same simulator to synchronise the donation amounts and user-data.
The script uses a notecard named configuration
that must be placed in the same primitive as the script:
#### START CONFIGURATION #### # the title to show in hoover-text above # the progress bar and the statistics. title = "Synchronized Donation Jar" # this is the goal you want to attain in L$ goal = 500 # this is an LSL color vector which will # be used for the overhead text color = <0.5, 0.5, 1> # this is a list of pay buttons displayed # when an avatar click the donation jar buttons = 10 ,100, 500, 1000 # the synchronization password must # is the shared secret between all the # donation jars that will synchronize. password = "feelin'blu!" ##### END CONFIGURATON ######
/////////////////////////////////////////////////////////////////////////// // Copyright (C) Wizardry and Steamworks 2021 - License: GNU GPLv3 // // Please see: http://www.gnu.org/licenses/gpl.html for legal details, // // rights of fair usage, the disclaimer and warranty conditions. // /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// string wasProgress(integer percent, integer length, list symbols) { percent /= (integer)((float)100.0/(length)); string p = llList2String(symbols,0); integer itra = 0; do { if(itra>percent-1) p += llList2String(symbols,2); else p += llList2String(symbols,1); } while(++itra<length); return p + llList2String(symbols,3); } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// string wasKeyValueSet(string k, string v, string data) { if(llStringLength(data) == 0) return k + "=" + v; if(llStringLength(k) == 0) return ""; if(llStringLength(v) == 0) return ""; integer i = llListFindList( llList2ListStrided( llParseString2List(data, ["&", "="], []), 0, -1, 2 ), [ k ]); if(i != -1) return llDumpList2String( llListReplaceList( llParseString2List(data, ["&"], []), [ k + "=" + v ], i, i), "&"); return data + "&" + k + "=" + v; } /////////////////////////////////////////////////////////////////////////// // 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) 2015 Wizardry and Steamworks - License: CC BY 2.0 // /////////////////////////////////////////////////////////////////////////// string wasKeyValueGet(string k, string data) { if(llStringLength(data) == 0) return ""; if(llStringLength(k) == 0) return ""; list a = llParseStringKeepNulls(data, ["&", "="], []); integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]); if(i != -1) return llList2String(a, 2*i+1); return ""; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// string wasKeyValueDelete(string k, string data) { if(llStringLength(data) == 0) return ""; if(llStringLength(k) == 0) return ""; integer i = llListFindList( llList2ListStrided( llParseString2List(data, ["&", "="], []), 0, -1, 2 ), [ k ]); if(i != -1) return llDumpList2String( llDeleteSubList( llParseString2List(data, ["&"], []), i, i), "&"); return data; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// list wasCompoundToList(integer T, list compound) { if(llGetListLength(compound) == 0) return []; if(T == TYPE_FLOAT) return llList2Float(compound, 0) + wasCompoundToList(T, llDeleteSubList(compound, 0, 0)); if(T == TYPE_INTEGER) return llList2Integer(compound, 0) + wasCompoundToList(T, llDeleteSubList(compound, 0, 0)); if(T == TYPE_STRING) return llList2String(compound, 0) + wasCompoundToList(T, llDeleteSubList(compound, 0, 0)); if(T == TYPE_KEY) return llList2Key(compound, 0) + wasCompoundToList(T, llDeleteSubList(compound, 0, 0)); if(T == TYPE_VECTOR) return llList2Vector(compound, 0) + wasCompoundToList(T, llDeleteSubList(compound, 0, 0)); if(T == TYPE_ROTATION) return llList2Rot(compound, 0) + wasCompoundToList(T, llDeleteSubList(compound, 0, 0)); return wasCompoundToList(T, llDeleteSubList(compound, 0, 0)); } integer line = 0; list settings = []; integer pipe = 0; default { state_entry() { // generate synchronization channel pipe = (integer)("0x8" + llGetSubString(llGetOwner(), 0, 6)); if(llGetInventoryType(llGetInventoryName(INVENTORY_NOTECARD, 0)) != INVENTORY_NOTECARD) { llSay(DEBUG_CHANNEL, "Sorry, could not find an inventory notecard."); return; } llGetNotecardLine( llGetInventoryName( INVENTORY_NOTECARD, 0 ), line ); } dataserver(key id, string data) { if(data == EOF) { // invariant, length(tuples) % 2 == 0 if(llGetListLength(settings) % 2 == 0) state update; llSay(DEBUG_CHANNEL, "Error in configuration notecard."); return; } if(data == "") { llGetNotecardLine( llGetInventoryName( INVENTORY_NOTECARD, 0 ), ++line ); return; } 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 == "") { llGetNotecardLine( llGetInventoryName( INVENTORY_NOTECARD, 0 ), ++line ); return; } settings += k; settings += v; llGetNotecardLine( llGetInventoryName( INVENTORY_NOTECARD, 0 ), ++line ); } on_rez(integer num) { llResetScript(); } changed(integer change) { llResetScript(); } } state donations { state_entry() { // Listen on the synchronization channels. llListen(pipe, "", "", ""); // Set the pay buttons and click action. llSetPayPrice(100, wasCompoundToList(TYPE_INTEGER, llParseString2List( wasKeyValueGet("buttons", wasKeyValueEncode(settings) ), [",", " "], []) ) ); llSetClickAction(CLICK_ACTION_PAY); } listen(integer channel, string name, key id, string message) { string password = wasKeyValueGet("password", message); // no password was sent bail - are you tickling my channels? if(password == "") return; // bail if the password does not match our settings if(password != wasKeyValueGet("password", wasKeyValueEncode(settings))) { return; } // overload message with the password filtered message = wasKeyValueDelete("password", message); // this should not happen at this point if(message == "") return; // and commit the rest llSetObjectDesc(message); // switch to update state update; } money(key id, integer amount) { llSetObjectDesc( wasKeyValueSet( "lastname", llList2String(llParseString2List(llKey2Name(id), [" "], [""]), 0), llGetObjectDesc() ) ); llSetObjectDesc( wasKeyValueSet( "lastamount", (string)amount, llGetObjectDesc() ) ); llShout(0, "Thank you, " + llKey2Name(id) + " for your generous donation!"); llSetObjectDesc( wasKeyValueSet("donated", (string)((integer)wasKeyValueGet( "donated", llGetObjectDesc() )+amount), llGetObjectDesc() ) ); // Overload the description with the password from the // settings and then pass the message through the pipe. llRegionSay( pipe, wasKeyValueSet( "password", wasKeyValueGet( "password", wasKeyValueEncode(settings) ), llGetObjectDesc() ) ); state update; } on_rez(integer num) { llResetScript(); } changed(integer change) { llResetScript(); } } state update { state_entry() { // Update the text with the current progress. integer percent = 100*(integer)wasKeyValueGet( "donated", llGetObjectDesc() )/(integer)wasKeyValueGet("goal", wasKeyValueEncode(settings)); llSetText( wasKeyValueGet("title", wasKeyValueEncode(settings) ) + "\nGoal: " + wasProgress(percent, 5, ["[", "█", "░", "]"]) + " %\n" + (string)percent + "% of L$" + wasKeyValueGet("goal", wasKeyValueEncode(settings)), (vector)wasKeyValueGet("color", wasKeyValueEncode(settings)), 1); // If the goal hasn't been reached, jump to the doner section. if(percent < 100) { // If somebody tipped and this is not a script restart, // then get the display text and add a thank you message // for the last tipper. We grab just the first name of // the tipper to make the donation jar more personal. string lastname = wasKeyValueGet("lastname", llGetObjectDesc()); integer lastamount = (integer)wasKeyValueGet("lastamount", llGetObjectDesc()); if(lastname == "") { // Go back to accepting donations. state donations; } if(lastamount == 0) { // Go back to accepting donations. state donations; } llSetText(llList2String( llGetPrimitiveParams( [ PRIM_TEXT ] ), 0) + "\nLast donation: " + "L$" + (string)lastamount + " from " + lastname + "!" + "\nTotal donations: " + "L$" + wasKeyValueGet( "donated", llGetObjectDesc() ), (vector)wasKeyValueGet("color", wasKeyValueEncode(settings)), 1); state donations; } // If the goal has been attained or overstepped, // let the owner know, schedule a reset and return. llInstantMessage(llGetOwner(), "Donation goal attained at " + llGetRegionName() + "!"); llSetObjectDesc( wasKeyValueSet("donated", (string)0, llGetObjectDesc() ) ); llSetObjectDesc( wasKeyValueDelete( "lastname", llGetObjectDesc() ) ); llSetObjectDesc( wasKeyValueDelete( "lastamount", llGetObjectDesc() ) ); llResetScript(); } on_rez(integer num) { llResetScript(); } changed(integer change) { llResetScript(); } }