I wanted to recreate all the features of a commercial tipjar as well as implement some of my own twists. Here are the features of this tipjar:
Tipjar Spoils
should be added to the same primitive in which this script resides.And an uncommon and personal one:
Tipjar Access
. This notecard will contain a list of people who will be able to log into the tipjar and receive tips. The format of the card is the following:<avatar name_1>#<avatar key_1> <avatar name_2>#<avatar key_2>
For example, if two people are allowed to log into the system, the Tipjar Access
notecard would look like this:
Morgan LeFay#1ad33407-a792-416d-a5e3-06007c0802bf William Riker#82c73d6d-facf-4322-b17b-06d8cd66a116
<avatar name_1>#<avatar key_1>#<percentage> <avatar name_2>#<avatar key_2>#<percentage>
So, following our previous example, for two people, the notecard would look like this:
Tasha Yar#1ad33417-a792-476d-a5e3-86007c7802bf#75 Frank Black#82c73d7d-facf-4332-b17b-06d8cd66a116#25
That means, that after a tipjar user, either Morgan LeFay
or William Riker
logout, the tipjar will distribute 75% of all profits to Tasha Yar
and 25% to Frank Black
.
Tipjar Access
list will be able to log into the tipjar and use it. To start distributing the profits after several tips, any of the people in the Tipjar Spoils
notecard will be able to log out, as well as the user currently using the tipjar.There are several options you can configure in the CONFIGURATION section:
string THANKS_MESSAGE = "Thank you for your tip %n%! %m% greatly appreciates it!"; string TIPPER_TOUCH_MESSAGE = "Hello %n%! This tipjar belongs to %m%. If you like my work, please consider giving me a tip. To tip me, please right-click my tipjar and select Pay and enter the ammount you would like to tip me with. Thank you!"; string TIPPER_DIALOG_MESSAGE = "Hello, %n%! Please choose an option from the ones below:\nJoin - will send you a group invite.\nGift - will send you a free gift!"; string OVERHEAD_MESSAGE = "%m%'s Tipjar, any tip is welcome!"; string LOGOUT_MESSAGE = "%m%'s Tipjar is logging off, please standby to receive your share %n%..."; list TIPPER_TOUCH_MENU = [ "◆ Join ◆", "◆ Gift ◆" ]; key INVITE_GROUP_KEY = "004079ff-e1b0-c671-dd5e-eb4f6e361684"; string INVITE_GROUP_MESSAGE = "Please join the group %n%! To do so, please click the link in your history window (ctrl+h):"; list PAY_BUTTONS = [ "20", "40", "60", "80" ]; integer EXCLUDE_ACCESS_FROM_SPOILS = 1; integer TIPJAR_ROAMING = 1; integer ROAM_INTERVAL = 30; integer ROAM_RANGE = 10;
Here is a description of what they are and what they are meant for:
THANKS_MESSAGE
is a message that will be sent to a tipper after they have tipped the avatar currently using the tipjar. As you can see, this string contains some keywords like %n%
and %m%
. They are expanded dynamically depending on the situation. The keyword %n%
will be dynamically replaced by the person tipping the user currently using the avatar. The keyword %m%
will be dynamically replaced by the avatar's name who is currently using the tipjar.TIPPER_TOUCH_MESSAGE
is a message that will be sent to a tipper when they touch the tipjar.TIPPER_DIALOG_MESSAGE
is the message in the dialog that pops up when a tipper touch the tipjar.OVERHEAD_MESSAGE
is a message that will be displayed while an avatar is using and logged into the tipjar.LOGOUT_MESSAGE
is a message that will be sent to all the people in the Tipjar Spoils
notecard informing them that they are about to receive their share of the spoils.TIPPER_TOUCH_MENU
represents the buttons that a potential tipper will get when they touch the tipjar. You can remove, for example, "◆ Gift ◆" if you do not wish to give the people who click your tipjar a gift. If you chose not to give a gift, then that line would look like:list TIPPER_TOUCH_MENU = [ "◆ Join ◆" ];
If somebody clicks the ◆ Join ◆
button, they will be sent an invite to a group of your choosing (see below). You can disable both of these options, enable both, or choose one or the other. Please note, that in order to give a gift, an object should be placed in the tipjar contents tab.
INVITE_GROUP_KEY
is the key of the group that potential tippers will be invited to when they press "◆ Join ◆". You should replace this key with the key of your group of choosing.INVITE_GROUP_MESSAGE
is the message that a potential tipper will get after pressing the "◆ Join ◆" button.EXCLUDE_ACCESS_FROM_SPOILS
is a special option. For example, suppose that you put some employees in the Tipjar Access
notecard and assign a certain percentage of the spoils to them. When an employee logs into the tipjar, if this option is set to 1, then all the other employees in the Tipjar Access
notecard will be excluded from the current session. This is fairly understandable because they are not currently working and logged into the tipjar. This will allow you to have multiple employees in the notecard and not change their names in the Tipjar Access
notecard when one of them is working.TIPJAR_ROAMING
is an option that enables the tipjar to move from avatar to avatar in the room in a given interval. Set this to 1 to enable and 0 to disable.ROAM_INTERVAL
this is the amount of time between which the tipjar starts roaming from avatar to avatar in the room.ROAM_RANGE
is the maximum range that the tipjar will see when it chooses to move from avatar to avatar./////////////////////////////////////////////////////////////////////////// // Copyright (C) Wizardry and Steamworks 2011 - License: GNU GPLv3 // // Please see: http://www.gnu.org/licenses/gpl.html for legal details, // // rights of fair usage, the disclaimer and warranty conditions. // /////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// // CONFIGURATION // ////////////////////////////////////////////////////////// // // string THANKS_MESSAGE = "Thank you for your tip %n%! %m% greatly appreciates it!"; string TIPPER_TOUCH_MESSAGE = "Hello %n%! This tipjar belongs to %m%. If you like my work, please consider giving me a tip. To tip me, please right-click my tipjar and select Pay and enter the ammount you would like to tip me with. Thank you!"; string TIPPER_DIALOG_MESSAGE = "Hello, %n%! Please choose an option from the ones below:\nJoin - will send you a group invite.\nGift - will send you a free gift!"; string OVERHEAD_MESSAGE = "%m%'s Tipjar, any tip is welcome!"; string LOGOUT_MESSAGE = "%m%'s Tipjar is logging off, please standby to receive your share %n%..."; list TIPPER_TOUCH_MENU = [ "◆ Join ◆", "◆ Gift ◆" ]; key INVITE_GROUP_KEY = "004079ff-e1b0-c671-dd5e-eb4f6e361684"; string INVITE_GROUP_MESSAGE = "Please join the group %n%! To do so, please click the link in your history window (ctrl+h):"; list PAY_BUTTONS = [ 20, 40, 60, 80 ]; integer EXCLUDE_ACCESS_FROM_SPOILS = 1; integer TIPJAR_ROAMING = 1; integer ROAM_INTERVAL = 30; integer ROAM_RANGE = 10; // // // END CONFIGURATION // ////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// // INTERNALS // /////////////////////////////////////////////////////////////////////////// string tokenSubstitute(string input, key id) { list kSubst = llParseString2List(input, ["%"], [""]); integer itra; for(itra=0; itra<llGetListLength(kSubst); ++itra) { if(llList2String(kSubst, itra) == "n") kSubst = llListReplaceList(kSubst, (list)llKey2Name(id), itra, itra); if(llList2String(kSubst, itra) == "m") kSubst = llListReplaceList(kSubst, (list)activeAvatarName, itra, itra); } return llDumpList2String(kSubst, " "); } moveTo(vector position) { llTargetRemove(targetID); targetID = llTarget(position, .8); llLookAt(position, .6, .6); llMoveToTarget(position, 3); } physics(integer bool) { if(bool) llSetForce(<0,0,9.81> * llGetMass(), 0); llSetStatus(STATUS_PHYSICS, bool); llSetStatus(STATUS_PHANTOM, bool); } list spoilMemberNames = []; list spoilMemberKeys = []; list spoilPercents = []; list tippers = []; list tipperAmounts = []; list accessListNames = []; list accessListKeys = []; string activeAvatarName = ""; key activeAvatarKey = NULL_KEY; key sQuery = NULL_KEY; integer sLine = 0; integer readNotecard = 0; integer comHandleTiper = 0; integer comHandleSpoiler = 0; integer allSpoils = 0; vector landingPoint; rotation landingRotation; list avPositions = []; integer positionRoam = 0; integer targetID = 0; integer roaming = 0; default { state_entry() { physics(FALSE); accessListNames = []; accessListKeys = []; sLine = 0; readNotecard = 0; activeAvatarName = ""; activeAvatarKey = NULL_KEY; llSetText("Tipjar loading access, please wait...", <1,1,1>, 1); if(llGetInventoryType("Tipjar Access") == INVENTORY_NOTECARD) jump found_access; llSetText("No access list. Please revise your configuration.", <1,1,1>, 1); llInstantMessage(llGetOwner(), "Failed to find Tipjar Access card. Please add a notecard called Tipjar Access and configure it apropriately."); return; @found_access; sQuery = llGetNotecardLine("Tipjar Access", sLine); llSetTimerEvent(5.0); } changed(integer change) { if(change & CHANGED_INVENTORY) llResetScript(); } on_rez(integer num) { physics(FALSE); } timer() { if(readNotecard) { llSetTimerEvent(.0); llSetText("Tipjar idle. Please click me to activate.", <1,1,1>, 1); return; } llSetTimerEvent(5.0); } dataserver(key id, string data) { if(id != sQuery) return; if(data == EOF) { readNotecard = 1; return; } if(data == "") jump next_line; list accessParse = llParseString2List(data, ["#"], [""]); accessListNames += llList2String(accessParse, 0); accessListKeys += llList2Key(accessParse, 1); @next_line; sQuery = llGetNotecardLine("Tipjar Access", ++sLine); } touch_start(integer num) { if(~llListFindList(accessListKeys, (list)llDetectedKey(0))) { activeAvatarName = llDetectedName(0); activeAvatarKey = llDetectedKey(0); state init; } } } state init { state_entry() { allSpoils = 0; tippers = []; tipperAmounts = []; spoilMemberNames = []; spoilMemberKeys = []; spoilPercents = []; readNotecard = 0; sLine = 0; llSetText("Tipjar initalizing, please wait...", <1,1,1>, 1); if(llGetInventoryType("Tipjar Spoils") == INVENTORY_NOTECARD) jump found_spoils; llSetText("Falied! Please check Tipjar Spoils notecard.", <1,1,1>, 1); llInstantMessage(llGetOwner(), "Failed to find Tipjar Spoils card. Please add a notecard called Tipjar Spoils and configure it apropriately."); return; @found_spoils; sQuery = llGetNotecardLine("Tipjar Spoils", sLine); landingPoint = llGetPos(); landingRotation = llGetRot(); llSetTimerEvent(5.0); } changed(integer change) { if(change & CHANGED_INVENTORY) llResetScript(); } timer() { if(readNotecard) { llSetTimerEvent(.0); integer itra; integer percents = 0; for(itra=0; itra<llGetListLength(spoilPercents); ++itra) { percents += llList2Integer(spoilPercents, itra); } if(percents > 100) { llOwnerSay("The percents in your Tipjar Spoils notecard add up to " + (string)percents + "%. They should add up to 100%. Please check your setup again."); state default; } llRequestPermissions(llGetOwner(), PERMISSION_DEBIT); return; } llSetTimerEvent(5.0); } listen(integer channel, string name, key id, string message) { if(id != activeAvatarKey) return; if(message == "[ Confirm ]") { llListenRemove(comHandleSpoiler); state tipjar; } llResetScript(); } dataserver(key id, string data) { if(id != sQuery) return; if(data == EOF) { readNotecard = 1; return; } if(data == "") jump next_line; list spoilList = llParseString2List(data, ["#"], [""]); if(EXCLUDE_ACCESS_FROM_SPOILS && ~llListFindList(accessListNames, (list)llList2String(spoilList, 0))) jump next_line; spoilMemberNames += llList2String(spoilList, 0); spoilMemberKeys += llList2Key(spoilList, 1); spoilPercents += llList2String(spoilList, 2); @next_line; sQuery = llGetNotecardLine("Tipjar Spoils", ++sLine); } run_time_permissions(integer perm) { if(perm & PERMISSION_DEBIT) { integer comChannel = ((integer)("0x"+llGetSubString((string)llGetOwner(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF; comHandleSpoiler = llListen(comChannel, "", activeAvatarKey, ""); integer itra; string confirmText; for(itra=0; itra < llGetListLength(spoilMemberNames); ++itra) { confirmText += llList2String(spoilMemberNames, itra) + " gets " + llList2String(spoilPercents, itra) + "%.\n"; } confirmText += "\n\n"; llDialog(activeAvatarKey, "Tipjar: Please revise and confirm the spoils distribution:\n\n" + confirmText, [ "[ Confirm ]", "[ Reject ]" ], comChannel); } } } state tipjar { state_entry() { llOwnerSay("Tipjar initialized and ready to be tipped. Good Luck " + activeAvatarName + "!"); llSetPayPrice(100, PAY_BUTTONS); llSetText(tokenSubstitute(OVERHEAD_MESSAGE, activeAvatarKey), <1,1,1>, 1); if(TIPJAR_ROAMING) llSensorRepeat("", "", AGENT, ROAM_RANGE, PI, ROAM_INTERVAL); } sensor (integer num) { if(roaming) return; roaming = 1; integer itra; for(itra=0, avPositions = [], positionRoam = 0; itra<num; ++itra) { avPositions += llDetectedPos(itra); } avPositions += landingPoint; physics(TRUE); moveTo(llList2Vector(avPositions, positionRoam++)); } at_target(integer tnum, vector targetpos, vector ourpos) { if(tnum != targetID) return; if(positionRoam == llGetListLength(avPositions)) { physics(FALSE); llSetPos(landingPoint); llSetRot(landingRotation); roaming = 0; return; } moveTo(llList2Vector(avPositions, positionRoam++)); } touch_start(integer num) { integer comChannel; if(~llListFindList(spoilMemberKeys, (list)llDetectedKey(0)) || llDetectedKey(0) == activeAvatarKey) jump spoiler_touch; llInstantMessage(llDetectedKey(0), tokenSubstitute(TIPPER_TOUCH_MESSAGE, llDetectedKey(0))); comChannel = ((integer)("0x"+llGetSubString((string)llGetKey(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF; comHandleTiper = llListen(comChannel, "", llDetectedKey(0), ""); llDialog(llDetectedKey(0), tokenSubstitute(TIPPER_DIALOG_MESSAGE, llDetectedKey(0)), TIPPER_TOUCH_MENU, comChannel); return; @spoiler_touch; comChannel = ((integer)("0x"+llGetSubString((string)llGetOwner(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF; comHandleSpoiler = llListen(comChannel, "", llDetectedKey(0), ""); llDialog(llDetectedKey(0), "Tipjar: Please choose an option:\n", [ "◆ LogOut ◆", "◆ Tipers ◆", "◆ Tops ◆", "◆ Total ◆" ], comChannel); } listen(integer channel, string name, key id, string message) { if(~llListFindList(spoilMemberNames, (list)name) || name == activeAvatarName) jump spoiler_com; if(message == "◆ Join ◆") { llInstantMessage(id, tokenSubstitute(INVITE_GROUP_MESSAGE, id) + "\n secondlife:///app/group/" + (string)INVITE_GROUP_KEY + "/about"); } if(message == "◆ Gift ◆") { integer itra; list gifts; for(itra=0; itra<llGetInventoryNumber(INVENTORY_OBJECT); ++itra) { gifts += llGetInventoryName(INVENTORY_OBJECT, itra); } for(itra=0; itra<llGetListLength(gifts); ++itra) { llGiveInventory(id, llList2String(gifts, itra)); } } llListenRemove(comHandleTiper); return; @spoiler_com; if(message == "◆ LogOut ◆") { llListenRemove(comHandleTiper); llListenRemove(comHandleSpoiler); if(TIPJAR_ROAMING) llSensorRemove(); if(TIPJAR_ROAMING && roaming) { physics(FALSE); state gohome; } state payments; } if(message == "◆ Tipers ◆") { integer itra; llInstantMessage(id, "---------- BEGIN TIPPERS ----------"); for(itra=0; itra<llGetListLength(tippers); ++itra) { llInstantMessage(id, llKey2Name(llList2Key(tippers, itra)) + " has tipped you: l$" + llList2String(tipperAmounts, itra)); } llInstantMessage(id, "----------- END TIPPERS -----------"); } if(message == "◆ Tops ◆") { integer itra; llInstantMessage(id, "---------- BEGIN TOP TIPPERS ----------"); integer topNum; for(itra=0; itra<llGetListLength(tippers); ++itra) { if(itra==3) jump end_tippers; integer tip = llList2Integer(llListSort(tipperAmounts, 1, 0), itra); llInstantMessage(id, llKey2Name(llList2Key(tippers, llListFindList(tippers, (list)tip))) + " has tipped you: l$" + (string)tip); } @end_tippers; llInstantMessage(id, "----------- END TOP TIPPERS -----------"); } if(message == "◆ Total ◆") { llInstantMessage(id, "So far, " + activeAvatarName + " has made l$" + (string)allSpoils); } llListenRemove(comHandleSpoiler); } money(key id, integer amount) { if(~llListFindList(tippers, (list)id)) { integer tip = amount + llList2Integer(tipperAmounts, llListFindList(tippers, (list)id)); tipperAmounts = llListReplaceList(tipperAmounts, (list)tip, llListFindList(tippers, (list)id), llListFindList(tippers, (list)id)); jump tippers_updated; } tippers += id; tipperAmounts += amount; @tippers_updated; allSpoils += amount; llShout(0, tokenSubstitute(THANKS_MESSAGE, id)); llInstantMessage(activeAvatarKey, llKey2Name(id) + " has just tipped you: l$" + (string)amount + "."); } } state gohome { state_entry() { llSetText("Please wait, returning home...", <1,1,1>, 1); physics(TRUE); moveTo(landingPoint); } at_target(integer tnum, vector targetpos, vector ourpos) { if(tnum != targetID) return; physics(FALSE); llSetPos(landingPoint); llSetRot(landingRotation); state payments; } } state payments { state_entry() { llSetText("Loging out...", <1,1,1>, 1); physics(FALSE); integer remSpoils = allSpoils; integer itra; for(itra=0; itra<llGetListLength(spoilMemberKeys) && remSpoils; ++itra) { llInstantMessage(llList2Key(spoilMemberKeys, itra), tokenSubstitute(LOGOUT_MESSAGE, llList2Key(spoilMemberKeys, itra))); integer share = (integer)((llList2Float(spoilPercents, itra)/100.0) * (float)allSpoils); if(share) llGiveMoney(llList2Key(spoilMemberKeys, itra), share); remSpoils -= (integer)((llList2Float(spoilPercents, itra)/100.0) * (float)allSpoils); } state default; } }