Table of Contents

About

This variation is a tipjar that will follow the owner through the simulator and provide quick-pay buttons for agents to donate money. The tipjar is configurable via a notecard called "configuration" that is placed in the same primitive as this script.

Features

Setup

Configuration

######################### START CONFIGURATION ################################

# This is a configuration notecard based on the key-value data Wizardry and Steamworks reader. 
# Everything following the "#" character is ignored along with blank lines and values can be set for 
# various parameters in a simple and understandable format with the following syntax: KEY = "VALUE" 
# Every time you change this configuration notecard, the script will reset itself and the new 
# configuration will be read from this notecard.

# This is the dampening time in seconds that the tip jar will use to move to its next destination. 
# Intuitively the larger the number the slower the movement and the smaller the number the faster the 
# tip jar will hoover around its owner. This setting must be an integer above 0.
dampening = "20"

# The maximum distance in meters from the owner that the tip jar will hover around. This does not 
# mean that the tip jar will not get close to the owner, but it rather represents the maximum distance 
# that the tip jar will be allowed to stray away from its owner.
radius = "10"

# This is the distance in meters to scan for the owner. If the owner is not found within this range, then 
# the tip jar attempts to find the owner on the entire simulator and then jumps to them. Intuitively, this 
# value should be larger than the "radius" range setting or the tip jar will keep jumping erratically.
sensor = "10"

# A list of values that will be presented whenever someone clicks your tip jar. There can only be four of 
# these buttons and they have to be positive and above zero.
buttons = "25, 50, 100, 200"

# A message that will be sent automatically to the tipper once they tip the owner.
thanks = "Thank you for the awesome tip!"

# A color vector for the color of the overhead text. This is in LSL notation and colors are represented in 
# a [0, 1] interval notation with 0 being the RGB equivalent of 0 and 1 being the RGB equivalent of 255.
# Here is a quick cheat sheet:
# <1, 0, 0> - red
# <0, 1, 0> - green
# <0, 0, 1> - blue
# <1, 1, 0> - yellow
# <1, 0, 1> - magenta
# <0, 1, 1> - cyan
color = "<1,1,0>"

# This is the title text that will appear above the statistics.
title = "-:[ Nanny's Tip Jar ]:-"

######################### END CONFIGURATION #################################

Code

following_personal_tipjar
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2014 - 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    //
///////////////////////////////////////////////////////////////////////////
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));
}
 
///////////////////////////////////////////////////////////////////////////
//    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) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
string wasCreateSLURL(string region, vector position) {
    list l = llParseString2List((string)position, ["<", ",", ">"], []);
    return "secondlife://" + llEscapeURL(region) + "/" + 
        (string)llList2Integer(l, 0) + "/" + 
        (string)llList2Integer(l, 1) + "/" + 
        (string)llList2Integer(l, 2);
}
 
moveTo(vector position) {
    llTarget(position, .8);
    llLookAt(position, .5, 1);
    llMoveToTarget(
        position, 
        llList2Float(
            tuples, 
            llListFindList(
                tuples, 
                [
                    "dampening"
                ]
            )+1
        )
    );    
}
 
// for notecard reading
integer line = 0;
 
// key-value data will be read into this list
list tuples = [];
 
// stores the current position of the object
vector mePosition = ZERO_VECTOR;
 
// storage for donations
list stash = [];
list users = [];
 
default {
    state_entry() {
        // set up initial tips
        users += llKey2Name(
            llGetOwner()
        );
        stash += 0;
        // stop all physics
        llSetStatus(STATUS_PHYSICS, FALSE);
        llSetStatus(STATUS_BLOCK_GRAB_OBJECT, FALSE);
        llVolumeDetect(FALSE);
        // read the configuration notecard
        state configure;
    }
    changed(integer change) {
        if(change & CHANGED_INVENTORY) {
            llResetScript();
        }
    }
    on_rez(integer num) {
        llResetScript();
    }
}
 
state configure {
    state_entry() {
        if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) {
            llSay(DEBUG_CHANNEL, "Sorry, could not find an inventory notecard.");
            return;
        }
        llGetNotecardLine("configuration", line);
    }
    dataserver(key id, string data) {
        if(data == EOF) { // invariant, length(tuples) % 2 == 0
            state wander;
        }
        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);
    }
    changed(integer change) {
        if(change & CHANGED_INVENTORY) {
            llResetScript();
        }
    }
    on_rez(integer num) {
        llResetScript();
    }
}
 
state wander {
    state_entry() {
        // set overhead text.
        llSetText(
            llList2String(
                tuples, 
                llListFindList(
                    tuples, 
                    [
                        "title"
                    ]
                )+1
            ) +
            "\nLast tip: L$" + 
                (string)
                    llList2Integer(
                        stash, 
                    -1) + " by " + 
                llList2String(
                    users, 
                    -1
                ) +
            "\nHighest tip: L$" + 
                (string)(
                    (integer)
                        llListStatistics(
                            LIST_STAT_MAX, 
                            stash
                        )
                    ) + " by " + 
                llList2String(
                    users, 
                    llListFindList(
                        stash, 
                        [
                            (integer)llListStatistics(
                                LIST_STAT_MAX, 
                                stash
                            )
                        ]
                    )
            ) + 
            "\nTotal tips: L$" + 
                (string)
                    (
                        (integer)
                            llListStatistics(
                                LIST_STAT_SUM, 
                                stash
                            )
                    ), 
            (vector)
                llList2String(
                    tuples, 
                    llListFindList(
                        tuples, 
                        [
                            "color"
                        ]
                    )
                +1), 
            1
        );
        // set pay buttons
        list price = wasCompoundToList(
            TYPE_INTEGER, 
            llParseString2List(
                llList2String(
                    tuples, 
                    llListFindList(
                        tuples, 
                        [
                            "buttons"
                        ]
                    )+1
                ), 
                [",", " "], 
                []
            )
        );
        // set click action to pay
        llSetClickAction(CLICK_ACTION_PAY);
        llSetPayPrice(llList2Integer(price, 0), price);
        // scan for owner
        llSensorRepeat(
            "", 
            llGetOwner(), 
            AGENT, 
            llList2Float(
                tuples, 
                llListFindList(
                    tuples, 
                    [
                        "sensor"
                    ]
                )+1
            ), 
            TWO_PI, 
            1
        );
    }
    sensor(integer num) {
        mePosition = llDetectedPos(0);
        llSetStatus(STATUS_PHYSICS, TRUE);
        llSetStatus(STATUS_BLOCK_GRAB_OBJECT, TRUE);
        llVolumeDetect(TRUE);
        moveTo(
            mePosition + 
            wasCirclePoint(
                llList2Float(
                    tuples, 
                    llListFindList(
                        tuples, 
                        [
                            "radius"
                        ]
                    )+1
                )
            )
        );
    }
    no_sensor() {
        // stop physics
        llSetStatus(STATUS_PHYSICS, FALSE);
        llSetStatus(STATUS_BLOCK_GRAB_OBJECT, FALSE);
        llVolumeDetect(FALSE);
        // if the owner is not on the current simulator, send them a message
        list o = llGetObjectDetails(llGetOwner(), [OBJECT_POS]);
        if(o == []) {
            llInstantMessage(
                llGetOwner(), 
                "Hey! I'm your tip jar, you forgot to pick me up. I'm at: " + 
                wasCreateSLURL(
                    llGetRegionName(), 
                    llGetPos()
                )
            );
            return;
        }
        mePosition = (vector)llList2String(o,0);
        // reposition in a 1m range radius from owner
        llSetRegionPos(mePosition + wasCirclePoint(1));
    }
    at_target(integer tnum, vector targetpos, vector ourpos) {
        if(
            llVecDist(
                ourpos, 
                targetpos
            ) > llList2Float(
                tuples, 
                llListFindList(
                    tuples, 
                    [
                        "radius"
                    ]
                )+1
            )
        ) return;
        moveTo(
            mePosition + 
            wasCirclePoint(
                llList2Float(
                    tuples, 
                    llListFindList(
                        tuples, 
                        [
                            "radius"
                        ]
                    )+1
                )
            )
        );
    }
    money(key id, integer amount) {
        // thank the tipper
        llInstantMessage(
            id, 
            llList2String(
                tuples, 
                llListFindList(
                    tuples, 
                    [
                        "thanks"
                    ]
                )+1
            )
        );
        // add the user and the tip to memory
        users += llKey2Name(id);
        stash += amount;
        // set overhead text.
        llSetText(
            llList2String(
                tuples, 
                llListFindList(
                    tuples, 
                    [
                        "title"
                    ]
                )+1
            ) +
            "\nLast tip: L$" + 
                (string)
                    llList2Integer(
                        stash, 
                    -1) + " by " + 
                llList2String(
                    users, 
                    -1
                ) +
            "\nHighest tip: L$" + 
                (string)(
                    (integer)
                        llListStatistics(
                            LIST_STAT_MAX, 
                            stash
                        )
                    ) + " by " + 
                llList2String(
                    users, 
                    llListFindList(
                        stash, 
                        [
                            (integer)llListStatistics(
                                LIST_STAT_MAX, 
                                stash
                            )
                        ]
                    )
            ) + 
            "\nTotal tips: L$" + 
                (string)
                    (
                        (integer)
                            llListStatistics(
                                LIST_STAT_SUM, 
                                stash
                            )
                    ), 
            (vector)
                llList2String(
                    tuples, 
                    llListFindList(
                        tuples, 
                        [
                            "color"
                        ]
                    )
                +1), 
            1
        );
        // Trigger poof effect
        llParticleSystem(
            [
                PSYS_SRC_TEXTURE, "0cb260bf-1874-cb0e-b406-fb7065759b0c",
                PSYS_PART_START_SCALE, < .08, .08, FALSE >, PSYS_PART_END_SCALE, < .1, .1, FALSE >,
                PSYS_PART_START_COLOR, < 1, 1, 0 >, PSYS_PART_END_COLOR, < 1, 1, 0 >,
                PSYS_PART_START_ALPHA, 1, PSYS_PART_END_ALPHA, .1,
                PSYS_SRC_BURST_PART_COUNT, 1,
                PSYS_SRC_BURST_RATE, .05,
                PSYS_PART_MAX_AGE, 4,
                PSYS_SRC_MAX_AGE, 5,
                PSYS_SRC_PATTERN, 2,
                PSYS_SRC_BURST_SPEED_MIN, 0, PSYS_SRC_BURST_SPEED_MAX, 0,
                PSYS_SRC_BURST_RADIUS, 1.5,
                PSYS_SRC_TARGET_KEY, llGetKey(),
                PSYS_PART_FLAGS, 
                    PSYS_PART_INTERP_COLOR_MASK |
                    PSYS_PART_INTERP_SCALE_MASK |
                    PSYS_PART_EMISSIVE_MASK |
                    PSYS_PART_FOLLOW_VELOCITY_MASK |
                    PSYS_PART_TARGET_POS_MASK
            ]
        );
        llSetTimerEvent(5);
    }
    timer() {
        llSetTimerEvent(0);
        llParticleSystem([]);
    }
    changed(integer change) {
        if(change & CHANGED_INVENTORY) {
            llResetScript();
        }
    }
    on_rez(integer num) {
        llResetScript();
    }
}