Table of Contents

ChangeLog

12 April 2013

22 November 2011

  • Done. The script now should send money properly as well. Cleaned up the tips.

21 November 2011

  • llKey2Name obviously fails when you click [ Send ] if the avatar keys in the BOOKMARK list are not all in the current region. Will be fixed soon…

About

From experience, I know (and so you should find out if you do not know), that it is most wise to create a secondary account to hold all your money. Running about in Second Life with all your cash on you is a bad idea. However, creating a secondary account is a bit cumbersome when it comes to sending money. If you run out, you will have to log-in the secondary account and transfer the money. Thanks to the feature of not have a log-out option in all the viewers, that either means you run two viewers at the same time (which is not possible on all machines), or you hotswap your accounts. This is not very convenient.

For that, I have created a bank that would allow you to link a secondary account to a primitive in world containing a script. You add yourself (hard-coded) to a list of people able to retrieve money, you grant the script permissions, you set a limit and then every time you want some money from your stash, you visit your primitive and retrieve the money.

There are also some tricks used in this script which might be interesting to developers.

Features

Setup

These steps have to be done with your alt:

list STASH_USERS = [ "5552f71f-60c9-4564-b861-55b200e12176" ];

This is a list of keys of the avatars that will be able to retrieve money from the stash. You should change this to the avatar key of your main account, for example. Or add other avatars if you wish to share the stash.

list SEND_BOOKMARKS = [  "5552f71f-60c9-4564-b861-55b200e12176", "a2e76fcd-9360-4f6d-a924-000000000003"  ];

This is a list of keys to whom somebody in the STASH_USERS list will be able to send money to. For example, this could be your landlord.

These steps may be done by your alt and anybody in your STASH_USERS list:

Code

stash_bank.lsl
///////////////////////////////////////////////////////////////////////////
//  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                      //
//////////////////////////////////////////////////////////
//                                                      //
 
// You can add to this list, the keys of the avatars 
// that will be able to retrieve money from the stash.
// Please change these to something... Sensible.
list STASH_USERS = [ "5552f71f-60c9-4564-b861-55b200e12176" ];
 
// Add all the avatar keys you wish to send money to.  
// This is even called "bookmarks" suggestively because  
// it will contain the people whom you frequently pay.       
list SEND_BOOKMARKS = [  "5552f71f-60c9-4564-b861-55b200e12176", "a2e76fcd-9360-4f6d-a924-000000000003" ];
 
//                                                      //
//                  END CONFIGURATION                   //
//////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////
//                              INTERNALS                                //
///////////////////////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
integer wasMenuIndex = 0;
list wasDialogMenu(list input, list actions, string direction) {
    integer cut = 11-wasListCountExclude(actions, [""]);
    if(direction == ">" &&  (wasMenuIndex+1)*cut+wasMenuIndex+1 < llGetListLength(input)) {
        ++wasMenuIndex;
        jump slice;
    }
    if(direction == "<" && wasMenuIndex-1 >= 0) {
        --wasMenuIndex;
        jump slice;
    }
@slice;
    integer multiple = wasMenuIndex*cut;
    input = llList2List(input, multiple+wasMenuIndex, multiple+cut+wasMenuIndex);
    input = wasListMerge(input, actions, "");
    return input;
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
integer wasListCountExclude(list input, list exclude) {
    if(llGetListLength(input) == 0) return 0;
    if(llListFindList(exclude, (list)llList2String(input, 0)) == -1) 
        return 1 + wasListCountExclude(llDeleteSubList(input, 0, 0), exclude);
    return wasListCountExclude(llDeleteSubList(input, 0, 0), exclude);
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
list wasListMerge(list l, list m, string merge) {
    if(llGetListLength(l) == 0 && llGetListLength(m) == 0) return [];
    string a = llList2String(m, 0);
    if(a != merge) return [ a ] + wasListMerge(l, llDeleteSubList(m, 0, 0), merge);
    return [ llList2String(l, 0) ] + wasListMerge(llDeleteSubList(l, 0, 0), llDeleteSubList(m, 0, 0), merge);
}
 
list bookmarks2name = [];
integer current_holdings = 0;
integer comHandle = 0;
integer comChannel = 0;
 
key cUser = NULL_KEY;
key sendToAv = NULL_KEY;
 
list DONORS = [];
list DONORS_AMOUNTS = [];
list RETRIEVERS = [];
list RETRIEVERS_AMOUNTS = [];
 
list menu_items = [];
 
default
{
    state_entry() {
        if(llSubStringIndex(llDumpList2String(STASH_USERS, " "), llGetOwner()) == -1) {
            STASH_USERS += llGetOwner();
        }
        cUser = NULL_KEY;
        current_holdings = 0;
        llSetText(llKey2Name(llGetOwnerKey(llGetKey())) + "\nPlease touch the primitive to configure.", <1,1,1>, 1);
    }
    on_rez(integer param) {
        llResetScript();
    }
    touch_start(integer total_number) {
        if(llDetectedKey(0) != llGetOwner()) return;
        sendToAv = llRequestAgentData(llList2Key(SEND_BOOKMARKS, current_holdings++), DATA_NAME);   
    }
    dataserver(key queryid, string data) {
        if(queryid == sendToAv) {
            bookmarks2name += data;
        }
        if(llGetListLength(bookmarks2name) == llGetListLength(SEND_BOOKMARKS)) {
            current_holdings = 0;
            llRequestPermissions(llGetOwner(), PERMISSION_DEBIT);
            return;
        }
        sendToAv = llRequestAgentData(llList2Key(SEND_BOOKMARKS, current_holdings++), DATA_NAME);
 
    }
    run_time_permissions(integer perm) {
        if(perm & PERMISSION_DEBIT)
            state configure;
    }
}
 
state configure
{
    state_entry() {
        llSetText(llKey2Name(llGetOwnerKey(llGetKey())) + " please make sure that this primitive and the script belongs to you.\n Then, pay all the money you wish this stash to hold.", <1,1,1>, 1);
    }
    money(key id, integer amount) {
        if(id == llGetOwner()) {
            current_holdings = amount;
            llSetText(llKey2Name(llGetOwnerKey(llGetKey())) + " those will be your current holdings.\nSwitching to running state in 10 seconds.\nPlease wait...", <1,1,1>, 1);
            llSetTimerEvent(10);
        }
    }
    timer() {
        llSetTimerEvent(0);
        llSetText("", <1,1,1>, 1);
        state stash;
    }
}
 
state stash
{
    state_entry() {
        llSensorRepeat("", NULL_KEY, AGENT, .1, .1, 60);
    }
 
    no_sensor() {
        if(llGetListLength(DONORS) > 25) {
            DONORS = [];
            DONORS_AMOUNTS = [];
        }
        if(llGetListLength(RETRIEVERS) > 25) {
            RETRIEVERS = [];
            RETRIEVERS_AMOUNTS = [];
        }
    }
 
    touch_start(integer total_number) {
        if(llSubStringIndex(llDumpList2String(STASH_USERS, " "), llDetectedKey(0)) == -1) return;
        cUser = llDetectedKey(0);
        comChannel = (integer)("0x8" + llGetSubString((string)cUser, 0, 6));
        llSetTimerEvent(10);
        comHandle = llListen(comChannel, "", cUser, "");
        llDialog(cUser, "Please select a transaction:\n ", [ "[ Take ]", "[ Reset ]", "[ Holdings ]", "[ Log ]", "[ Send ]" ], comChannel);
    }
    timer() {
        llSetTimerEvent(0);
        llInstantMessage(cUser, "Menu timeout.");
        llListenRemove(comHandle);
    }
    listen(integer channel, string name, key id, string message) {
        if(cUser != id && llSubStringIndex(llDumpList2String(STASH_USERS, " "), id) == -1) return;
        if(message == "<= Back") {
            llSetTimerEvent(60);
            llDialog(id, "Please select an avatar name:\n",  wasDialogMenu(menu_items, ["<= Back", "", "Next =>"], "<"), channel);
            return;
        }
        if(message == "Next =>") {
            llSetTimerEvent(60);
            llDialog(id, "Please select an avatar name:\n",  wasDialogMenu(menu_items, ["<= Back", "", "Next =>"], ">"), channel);
            return;
        }
        if(message == "[ Take ]") {
            llSetTimerEvent(10);
            llListenRemove(comHandle);
            comHandle = llListen(comChannel+1, "", id, "");
            llTextBox(id, "\nThis stash currently holds: L$" + (string)current_holdings + "\n\nPlease enter an amount that you would like to retrieve from this stash.", comChannel+1);
            return;
        }
        if(message == "[ Send ]") {
            llSetTimerEvent(10);
            llListenRemove(comHandle);
            comHandle = llListen(comChannel+2, "", id, "");
            integer i = llGetListLength(bookmarks2name)-1;
            do {
                menu_items += llList2String(bookmarks2name, i);
            } while(--i>-1);
            llDialog(id, "\nPlease select the name of the avatar to send money to:\n", wasDialogMenu(menu_items, ["<= Back", "", "Next =>"], ""), comChannel+2);
            return;
        }
        if(channel == comChannel+2) {
            llSetTimerEvent(10);
            llListenRemove(comHandle);
            sendToAv = llList2Key(SEND_BOOKMARKS, llListFindList(bookmarks2name, (list)message));
            comHandle = llListen(comChannel+3, "", id, "");
            llTextBox(id, "\nThe money will be sent to: " + message + "\nIf this is corrent, please type an amount of money that should be sent.", comChannel+3);
            return;
        }
        if(channel == comChannel+3 && current_holdings - (integer)message >= 0 && (integer)message != 0) {
            llSetTimerEvent(0);
            llListenRemove(comHandle);
            llGiveMoney(sendToAv, (integer)message);
            RETRIEVERS += llKey2Name(id);
            RETRIEVERS_AMOUNTS += (integer)message;
            current_holdings -= (integer)message;
            return;
        }
 
        if(message == "[ Holdings ]") {
            llSetTimerEvent(0);
            llListenRemove(comHandle);
            llInstantMessage(cUser, "Current holdings: " + (string)current_holdings);
            return;           
        }
 
        if(id == llGetOwner() && message == "[ Reset ]") {
            llSetTimerEvent(0);
            llListenRemove(comHandle);
            state default;
            return;
        }
        if(id == llGetOwner() && message == "[ Log ]") {
            llSetTimerEvent(0);
            llListenRemove(comHandle);
            integer itra;
            llSleep(llGetRegionTimeDilation());
            llOwnerSay("--------------- START RETRIEVERS ---------------");
            for(itra=0; itra<llGetListLength(RETRIEVERS); ++itra) {
                llOwnerSay(llList2String(RETRIEVERS, itra) + " took L$" + llList2String(RETRIEVERS_AMOUNTS, itra));
                llSleep(llGetRegionTimeDilation());
            }
            llOwnerSay("---------------- END RETRIEVERS ----------------");
            llSleep(llGetRegionTimeDilation());
            llOwnerSay("--------------- START DONORS ---------------");
            for(itra=0; itra<llGetListLength(DONORS); ++itra) {
                llOwnerSay(llList2String(DONORS, itra) + " paid L$" + llList2String(DONORS_AMOUNTS, itra));
                llSleep(llGetRegionTimeDilation());
            }
            llOwnerSay("---------------- END DONORS ----------------");
            return;
        }
 
        if(channel == comChannel+1 && current_holdings - (integer)message >= 0 && (integer)message != 0) {
            llSetTimerEvent(0);
            llListenRemove(comHandle);
            llGiveMoney(id, (integer)message);
            RETRIEVERS += llKey2Name(id);
            RETRIEVERS_AMOUNTS += (integer)message;
            current_holdings -= (integer)message;
            return;
        }
    }
 
    money(key id, integer amount) {
        current_holdings += amount;
        DONORS += llKey2Name(id);
        DONORS_AMOUNTS += amount;
    }
}