Table of Contents

Change Log

12 April 2011

  • Rewrote the entire code.

10 December 2011

  • Isolated money event and fixed payment for Marisa Gregan.

Introduction

I was asked for a vendor that only dispenses a certain amount of items. I made this script so that it should support any number of items and also keeps tracks of limits per items in the primitive's inventory. This uses the Giver script I created in order to generate menu items with forward- and back-buttons.

Setup

Super Item#50#3
Great Item#60#2

where the first field before the hash, Super Item is the item name in the primitive's inventory, the second field after the hash 50 represents the price for that item and the last field 3 represents the amount of items that the vendor will dispense before it is out of stock. For the second item, it is called Great Item, costs L$60 and the vendor will dispense two of them before it says it is out of stock.

The vendor's contents would look something like the following:

Code

limitvendor.lsl
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2013 - 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                    //
//////////////////////////////////////////////////////////
// This message is shown when the dialog pops up allowing
// a customer to select the product number.
string DIALOG_MESSAGE = "Please choose a number corresponding to the items on the main chat in order to purchase that item. You can use the back and forward buttons to choose the product number. The dialog will time out in 30 seconds. If that happens, please just touch the vendor again. Thank you! Happy Shopping!";
// This is the message that the vendor will send to the 
// customer in case the product is out of stock.
string OUT_OF_STOCK_MESSAGE = "Sorry, that item is out of stock. Try again later.";
// This is a message that is sent to a customer right before
// the script lists all the items in the vencor.
string BUYER_TOUCH_MESSAGE = "This vendor contains the following items:";
 
//////////////////////////////////////////////////////////
//                       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 menus = [];
list items = [];
list price = [];
list limit = [];
key tmpKey = NULL_KEY;
integer nLine = 0;
 
default
{
    state_entry() {
        integer i = llGetInventoryNumber(INVENTORY_NOTECARD)-1;
        do {
            if(llGetInventoryName(INVENTORY_NOTECARD, i) != "Vendor Items") jump continue;
            jump found_notecard;
@continue;
        } while(--i>-1);
        llSay(DEBUG_CHANNEL, "No notecard named Vendor Items found in inventory.");
        return;
@found_notecard;
        tmpKey = llGetNotecardLine("Vendor Items", nLine);
    }
 
    dataserver(key id, string data) {
        if(id != tmpKey) return;
        if(data == EOF) {
            llRequestPermissions(llGetOwner(), PERMISSION_DEBIT);
            return;
        }
        if(data == "") jump next_line;
        list in = llParseString2List(data, ["#"], []);
        items += llList2String(in, 0);
        price += llList2Integer(in, 1);
        limit += llList2Integer(in, 2);
        menus += nLine+1;
@next_line;
        tmpKey = llGetNotecardLine("Vendor Items", ++nLine);
    }
    run_time_permissions(integer perm) {
        if(perm & PERMISSION_DEBIT) state vendor;
    }
    changed(integer change) {
        if(change & CHANGED_INVENTORY) llResetScript();
    }
}
 
state vendor {
    state_entry() {
        llSetPayPrice(PAY_HIDE, [PAY_HIDE ,PAY_HIDE, PAY_HIDE, PAY_HIDE]);
    }
    touch_start(integer num) {
        tmpKey = llDetectedKey(0);
        llInstantMessage(tmpKey, BUYER_TOUCH_MESSAGE);
        integer i = llGetListLength(items)-1;
        do {
            string prod = (string)(i+1) + ".) " + llList2String(items, i) + " costs L$" + llList2String(price, i) + ".";
            if(llList2Integer(limit, i) == 0) prod += " (OUT OF STOCK)";
            llInstantMessage(tmpKey, prod);
        } while(--i>-1);
        state query;
    }
    on_rez(integer num) {
        llResetScript();
    }
}
 
state query {
    state_entry() {
        integer comChannel = (integer)("0x8" + llGetSubString(tmpKey, 0, 6));
        llListen(comChannel, "", tmpKey, "");
        llDialog(tmpKey, DIALOG_MESSAGE, wasDialogMenu(menus, ["<= Back", "", "Next =>"], ""), comChannel);
        llSetTimerEvent(60);
    }
    listen(integer channel, string name, key id, string message) {
        if(message == "<= Back") {
            llSetTimerEvent(60);
            llDialog(id, DIALOG_MESSAGE, wasDialogMenu(menus, ["<= Back", "", "Next =>"], "<"), channel);
            return;
        }
        if(message == "Next =>") {
            llSetTimerEvent(60);
            llDialog(id, DIALOG_MESSAGE, wasDialogMenu(menus, ["<= Back", "", "Next =>"], ">"), channel);
            return;
        }
        integer pay = llList2Integer(price, (integer)message-1);
        llSetPayPrice(pay, [pay,PAY_HIDE, PAY_HIDE, PAY_HIDE]);
        llInstantMessage(id, "The price for that item is: L$" + (string)pay + ". Please right click the vendor and select pay that ammount to receive your item.");
        state payment;
    }
    timer() {
        llInstantMessage(tmpKey, "Sorry, the vendor payment timed out...");
        state vendor;
    }
}
 
state payment
{
    state_entry() {
        llSetTimerEvent(60);
    }
    timer() {
        llInstantMessage(tmpKey, "Sorry, the vendor payment timed out...");
        state vendor;
    }
    money(key id, integer amount) {
        if(id == tmpKey) jump buyer_ok;
        llGiveMoney(id, amount);
        llInstantMessage(id, "Sorry, I'm busy with another transaction... Please wait...");
        return;
@buyer_ok;
        if(llListFindList(price, (list)amount) != -1) jump found_item;
        llGiveMoney(id, amount);
        llInstantMessage(id, "Sorry, that price is wrong.");
        return;
@found_item;
        if(llList2Integer(limit, llListFindList(price, (list)amount)) > 0) jump limit_ok;
        llInstantMessage(id, OUT_OF_STOCK_MESSAGE);
        llGiveMoney(id, amount);
        return;
@limit_ok;
        llGiveInventory(id, llList2String(items, llListFindList(price, (list)amount)));
        limit = llListReplaceList(limit, (list)(llList2Integer(limit, llListFindList(price, (list)amount))-1), llListFindList(price,(list)amount), llListFindList(price,(list)amount));
        state vendor;
    }
}