Welcome to the Second Life Forums Archive

These forums are CLOSED. Please visit the new forums HERE

Discussion: Multi-Category, Quasi-Networked Versatile Vendor System

Fenrir Reitveld
Crazy? Don't mind if I do
Join date: 20 Apr 2005
Posts: 459
03-24-2006 18:37
I am releasing my SVN vendor system free for use or resale as long as you follow the terms of the license (GPL v2.0).

NOTE: Incase it's not immediately clear, no this has nothing to do with the version control system called SVN. ;)

Why am I releasing something that is probably a good 2-3 weeks worth of my personal in-SL scripting/testing/design time? Mainly because I don't want to support the dang thing if I were to sell it. :) Also, there has been truckloads of free items/scripts that I have examined, taken apart, or even adapted for my own use -- so this is my way of giving back to the community.

This is going to be a multi-post thread and contain a good number of scripts. Between the vendor and the master inventory handler, there's at least 15 scripts. Not all are necessary, however. Also, I have created three example vendors for your use; A podium style vendor that uses just 5 prims, a 12 prim upright stand vendor, and a larger wall vendor. So, to get all of this, your best bet would be to visit my in-SL store at my primary store in Hoodoo and pick up the SVN kit. You can also buy it for 0 $L from SL Exchange.

First off, here is the documentation notecard, part 1 of 2:

CODE
=========================
MechMind SVN System User Guide v1.00
=========================

SVN is written by Fenrir Reitveld.

Lots of thanks to these people who helped SVN become what it is today:

Spuds Milk
Maklin Deckard
Grifter Kawabata
Fenrix Murakami

I appreciate your testing, feature suggestions, and optimization tips. :)


----------------
License to Use
----------------

All script and contents copyright by Fenrir Reitveld, 02/10/06

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program in a notecard; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

GPL license version 2 on the web: http://www.gnu.org/copyleft/gpl.html (Note, URL might change once v3 is released.)

Here's a human readable version of GPL as given by the Creative Commons people: http://creativecommons.org/licenses/GPL/2.0/

What does this mean in SL terms? I'm going to defer to Francis Chung's interpretation:

- If you make script changes, you should release those changes open-source. (IE: Make the SVN scripts available to any and all who ask for the changes.)
- You are welcome to create new vendors/master inventory objects that are modify/nocopy/notransfer and resell the whole thing.
- The scripts should remain full permissions (mod/copy/transfer), and the objects they reside in remain modify.

In other words, to comply with GPL any items you distribute that use SVN scripts should be modify and contain the SVN scripts as full-perms, so that future owners can apply their own SVN code changes, or replace the scripts with different versions of SVN.

Technical support is NOT PROVIDED by me, or any of the current or future contributors to SVN code base. If you have a question/suggestion/comment, you are more than welcome to send them to me and I will try to help as best as I can, but be advised I am releasing this entire project to the whole of Second Life with no intention to provide support.

If you release a product based upon SVN, I ask (but do not require) you to display the ? somewhere on your product information page. If you wish a version without the background, then use ?.


--------------
Introduction
--------------

SVN is a multi-category, multi-item vendor system designed by Fenrir Reitveld of MechMind Industries. It was originally designed to fill a role for my Second Life business; I wanted an easy to use multi-category vendor that was scalable. It should be able to handle 1 or a 100 items, and work with either a single prim hanging on a wall (in very restrictive vendoring areas) or massive walk-in vending monsters. It should be flexible and support a number of options I find necessary in vendors, such as the ability to seamlessly switch between 2D/3D item views (ie: holos) and give out item notecards.

SVN meets and exceeds all of these requirements. SVN is a highly versatile vending system that can support both single-prim touch-driven vendors all the way up to massive walk-in displays. It is low lag in that it does not use email/RPC-XML monitoring events, and only uses sensors/listens for as long as necessary. %99 of the time the SVN vendor will show up as a PASSIVE | SCRIPTED object.

SVN is a quasi-networked vendor. It does not syncronize to a central server, nor does the inventory live in a master repository and is handed out from such. However, there is a Master Inventory item. This object pushes its inventory contents to all nearby SVN Vendors, and then cleans itself up once done. I term this "Fire & Forget Updating,", in that you can teleport near your vendor and drop off the update item, start the update, and go about your business or even leave the sim. As long as the sim doesn't crash, your vendor(s) will be updated. If updating DOES fail, you vendors will be offline so there is no chance of leaving them with partial inventory or config notecards. Vendors will notify you with an (optional) email when they complete their updates.

SVN providers absolutely no sales tracking or monitoring, however all vendors are designed to send an email upon every item purchase. (If you enable this.) IMHO, statistical analysis of sales data should occur outside of Second Life, where one has a much wider variety of tools. Even if you do not do sales analysis, it is handy to have an email record of every purchase in case of customer questions or concerns.

Both the Vendors and the Master Inventory object are configured with notecards. The Vendor has its own config notecard, and so does the Master obj. Both the Vendors and Master share a set of category configuration notecards. These define the various item categories, and the item prices themselves.

The concept of the item database is simple: The item name is stored, plus its price in $L, and the texture key to display when the item is selected in the vendor.

Note that this vendor can only sell objects. This does not mean you cannot sell animations, textures, or gestures with it; You will just need to "box them up" inside a prim.

Selling objects for 0$L is not supported due to limitations in the llSetPayPrice() script call, though you can sell freebies for 1 $L which seems to be a perfectly acceptable practice even if you did not create the item.

SVN is a vendor system of medium difficulty. I have tried to simplify the system as much as possible, but one must keep in mind it was designed by a programmer for a programmer's needs. I will be discussing "texture keys" and "notecards" and you need to be aware of what these are and how to manipulate these items, as there is just too much to cover in this document. There ARE simpler vendors out there, if you are looking for something you can get up and running in just 15 minutes. SVN isn't designed to be a quick and easy solution; It is flexible and therefore requires more set up time to use optimally.

As a "booster" for those inexperienced with SVN, I have included several common vendor styles, ranging from a buttonless 5-prim podium to a large, upright stand vendor, to a multi-button wall vendor. You are free to use and abuse these vendor frameworks as you wish as long as you stay within the license.

What does SVN stand for? Standalone Vendor Nexus. It's not exactly a network. :)


------------------
Vendor Overview
------------------

Vendors in SVN are self-contained objects which can consist of one to any number of prims. It is possible to create a single-prim vendor, or one that uses 100 prims. While there is no limit on the configuration of your vendor (wall, podium, walk in, etc.), there are some limitations on how the scripts must be arranged in your vendor.

A "Vendor" in SVN terms is MINIMALLY a single prim that contains the vendor scripts, plus your inventory and config notecards. One side ("face") of the prim is used to display a texture about the item, while the current item's name, category, and price hovers over the prim in floating text. SVN does not currently handle "multi-view" type vendors which display more than one item at once.

All inventory and SVN scripts should live in a single prim, usually the root prim. The only two scripts which do not need to live in this prim are MM SVN Texture Manager and MM SVN Titler. But more on these two shortly.

All of your Vendor's inventory exists in the same prim where the vendor scripts live, and is accessed by the Primary and Vendor Inv Manager scripts.

While it is not required that you use the Master Inventory object, if you do you should consider the inventory present in your Vendors to be transitory and not the "final" represensation of your stock. While normally a Vendor and Master Inventory object should contain the same inventory, there are potentially situations where it might differ, such as when an inventory push fails.

There are two scripts which will need to be placed specifically in your vendor for it to function correctly. They are:

1. MM SVN Texture Manager: This script controls the display of item textures. (For more information about item textures, see the section "Textures for Items".) This script will likely need to be modified, as by default it attempts to change the texture on face #1 of the prim. This might or might not be the face you want. If you are unsure about sides or how to figure out which face is the proper one, there is a section in the Wiki concerning prim faces and their numbering here: http://secondlife.com/badgeo/wakka.php?wakka=side

2. MM SVN Titler: This script is what generates the floating text over a vendor. You need to put this script in at least one prim of your vendor, so that users can see feedback from the vendor. In the interest of chat spam, the vendor will use llOwnerSay() to note various loading states to the owner, but otherwise does not whisper or say anything outloud to anyone nearby -- All status information is given through floating text. More often than not, this will live in the same prim that contains the Texture Manager to keep a consistant interface feel to the vendor.

It is quite acceptable to have both of the prior scripts in the same prim as the rest of the SVN scripts. The ability to seperate them is what gives you the additional flexability. it's also possible to have MULTIPLE copies of the script in different prims, for different effects. (Say, a large display stand that might show the item texture at different areas.)

A vendor should contain these scripts in the a single prim (usually ROOT PRIM):

MM SVN Primary v1.07
MM SVN Common Functions v1.02
MM SVN Vendor Inv Manager v1.00
MM SVN SMTP scheduler v1.00
MM SVN SMTP daemon #1 v1.00
MM SVN Item Database v2.02 Cat #1
MM SVN Item Database v2.02 Cat #2
MM SVN Item Database v2.02 Cat #3
..
MM SVN Item Database v2.02 Cat #10

Optional scripts in the ROOT PRIM:

MM SVN Unsitter v1.00
MM SVN Texture Manager v1.01
MM SVN Titler v1.00

(Note: Version numbers may change between the writing of this document and SVN release.)

You should place the MM SVN Titler and MM SVN Texture Manager in the display child prim of your vendor, or just leave it in the root if your vendor is a unified package. (Or if the display prim happens to be the root prim.)

The number of MM SVN Item Database scripts is determined by the number of category notecards you have loaded. While you can leave all 10 of the database scripts in the vendor, it is HIGHLY RECOMMENDED that you trim this down to absolutely minimum number of categories. For example, if you have four categories, then only include four database scripts. This will help with sim resource usage, and ultimately lag. For more information, see the section "Category Database Scripts (in Vendor)".


----------------
Vendor Usage
----------------

To use the vendor, simply click on the appropriate action buttons, or click on the vendor itself to bring up the menu. The options are fairly self-explanatory, such as switching between items and categories, or switching the vendor between 2D and 3D modes.

There is a menu that bears explanation, and this menu option will only appear for the owners of the SVN vendor: Admin

Options under Admin consist of:

Reset = This simply reboots the vendor. Necessary if you change the category or config notecards!
Update = Requests a nearby Master Inventory object updates the vendor.
Empty Inv = This will flush the entire inventory/notecard content of the vendor, and reset it. This is a good way to return the vendor to a clean state.

Note that requesting an inventory flush will cause the vendor to error upon reboot! This is normal, as part of the inventory flush is to remove the category notecards.

For notes on how to configure the category/item list notecards, see the section named "Category Notecards".


----------------------------------
Vendor Configuration Notecard
----------------------------------

The Vendor config ntoecard is named "MM SVN Vendor Config". It contains options, much like an INI file does under Windows. Any line beginning with a pound sign (#) is considered a comment and will be ignored by SVN.

NOTE: IF YOU EDIT THE CONFIG NOTECARD, CHANGES WILL NOT GO INTO EFFECT UNTIL YOU RESTART THE VENDOR. See the section "Master Inventory Object Usage" for details on how to restart the master object.

You can comment out an option by preceeding it with a #. In these examples, the options will be commented out, but if you use them in the config file they should have the preceeding # removed.

These options are available in the vendor config notecard:

# email_address = email@myaddress.com

This defines the email address that the vendor will use to email you both sales and inventory load completion notices. If you leave this commented out, your vendor will not email at all.

# command_channel = -1234567

Command channel is the chat channel that the vendor uses to talk to the master inventory object. You really don't need to know that much about this other than to make sure both command_channels are the same in the two configuration files.

# master_inventory_name = SVN Master Inventory Example

This sets the prefix to look for when doing inventory updates. See the section "Master Inventory Object Overview" for more details.

# holo_offset = <1.0, 0.0, -0.2>

When a holo item is rezzed, it will appear at this offset from the vendor. This is given in standard LSL vector format, which is X, Y, and Z. Note that this is relative to the root prim!

# purchase_sound = mm_thankyou
# notecard_sound = mm_notecard

Your vendor can play two sounds. The first is played whenever someone purchases an item. The second is only played when the vendor gives them a notecard. Use with caution, as vendors that make sound might not be appreciated!

# help_message = Hello @! To use the vendor, just click on it and interact via the menus! Select the item that you want, and then right click on the vendor and select "Pay..." from the pie menu.

The help message is given when a user selects "Help!" from the menu. Note that the at-symbol (@) is replaced with the name of the user who requested the help. This information is IMed to them and will not be visible in the world.

# purchase_message = Thank you for shopping with SVN Vendor, @!

A purchase message is spoken when a person buys an item. This is the only time a vendor will speak outloud during normal transactions. As with the help message, @ will be replaced with the user's name.


---------------------------
Creating Vendor Buttons
---------------------------

The Vendor Primary script does not require button scripts in child prims of the Vendor. This handy features lets you save sim processing/load by using scriptless buttons. To do so, just name your child prim as given below for the appropriate function. The case is not important.

Prev Item = Moves to previous item
Next Item = Moves to next item
Prev Cat = Moves to previous category
Next Cat = Moves to next category
Notecard = Gives informational notecard
View = Switches view between 2D/3D
Cat # = Direct switch to category # (where # starts from 1 and goes up)

You can still use scripts in buttons if you wish to do custom processing for various events (such as hiding the notecard button when there is no notecard), but this is not necessary.

To make a button control a Vendor using the script method, you need to use llMessageLinked () to send this command to the Primary script. Several numeric events are fired by the Primary Vendor script based upon events during Vendor operation, so your button scripts can respond to these if you wish. A common practice is to have the Give Notecard button automatically hide itself when the Vendor is currently displaying an item with no notecard.

A very simple example would be for a notecard button:

******************* [ Script: Give Notecard ]
// Give Notecard button script example
default
{
state_entry()
{
}

touch_start (integer num)
{
llMessageLinked (LINK_SET, 5002, "", llDetectedKey(0)) ; // Ask the Primary script to give us a notecard, passing it the key of the requestor
llSleep (0.2) ; // Sleep a short bit, to keep the user from spamming the vendor
}

link_message (integer sender, integer num, string msg, key id)
{
if (num == 6002) // Message from Primary: We have a notecard to give
llSetAlpha (1, ALL_SIDES) ;
if (num == 6003) // Message from Primary: We do not have a notecard...
llSetAlpha (0, ALL_SIDES) ;
}
}
******************* [ End: Give Notecard ]

What this script does is turn the button on (set all sides alpha 1.0) when the Primary emits a message stating that we have a notecard available (Linked message #6002), otherwise it blanks the button when there is no notecard (Message #6003).

For a full list of messages and their parameters pertaining to buttons, consult the Technical Documentation section.


--------------------------------------
Master Inventory Object Overview
--------------------------------------

You are not required to use the Master Inventory object. If you are not interested in doing fire & forget pushes, you can simply ignore all Master Inventory sections of this document.

If you do wish to use the quasi-networked method of SVN, then I will explain a bit more in detail how the system works.

All vendors are seperate entities. They do not attempt to contact a central server or the master inventory object unless an update has been requested by the vendor itself. When they request a inventory load, they will then delete all of the items and notecards from their inventory (with the exception of the configuration notecard), and then request an inventory push from the master object.

Normally, inventory load requests only come from the vendors. There is one scenerio when a vendor will accept a remote inventory load without you requesting it directly; If the vendor is in a Fatal Error state, it will then accept remote loads. Therefore, if you wish to mass-load a set of vendors, the best way to do this is to do empty the inventory on all vendors, wait for them to reboot into the Error state, and then do a Force Push on the master inventory object. This will allow you to walk away and let the inventory load proceed without requesting it individually for all vendors.

How does the master object find vendors? How do the vendors find the master object? Simple; They use sensor events. These trigger once when either an Update or a Force Push is requested. (See respective Usage sections on how to invoke these events.) Both the master object and the vendors look for a particular OBJECT NAME PREFIX, as defined in their respective config notecards.

For example, when one of the example vendors has been requested to do an Update, it will search for an object named "SVN Master Inventory Example". If it finds this object, it will then direct a request at it over the Command Channel. The same is reverse of the master inventory object, except is searches for "Example SVN Vendor".

Note that this is prefix matching. Your vendor or master object can be called whatever you want, as long as the prefix is found. For example, "Example SVN Vendor v2.00 (Hoodoo)" is a valid name for your vendor, as it still contains "Example SVN Vendor" and therefore will be found by your master inventory object.


----------------------------------
Master Inventory Object Usage
----------------------------------

The master object only has one menu, invoked by touching the prim; It contains these options:

Force Push = Forces a push to all nearby vendors. Only works if the vendors are in Error state! (Red floating text.)
Check Inv = Askes the master object to perform an extensive inventory consistancy check. This might take a while.
Reset = Reboots the master object. Necessary when editing the configuration notecard!

You load your inventory into the master object, along with the appropriate Category#/Categories notecards.


-----------------------------------------------------
Master Inventory Object Configuration Notecard
-----------------------------------------------------

The master object confi notecard is named "MM SVN Master Config". It contains options, much like an INI file does under Windows. Any line beginning with a pound sign (#) is considered a comment and will be ignored by SVN.

NOTE: IF YOU EDIT THE CONFIG NOTECARD, CHANGES WILL NOT GO INTO EFFECT UNTIL YOU RESTART THE MASTER OBJECT. See the section "Vendor Usage" for details on how to restart the vendor.

You can comment out an option by preceeding it with a #. In these examples, the options will be commented out, but if you use them in the config file they should have the preceeding # removed.

These options are available in the master inventory object config notecard:

# command_channel = -1234567

Command channel is the chat channel that the master inventory object uses to talk to the vendor. You really don't need to know that much about this other than to make sure both command_channels are the same in the two configuration files.

# vendor_name_prefix = Example SVN Vendor

This sets the vendor prefix to look for when doing inventory updates. See the section "Master Inventory Object Overview" for more details.


--------------------
Textures for Items
--------------------

Each item in the SVN vendor is associated with a texture which is displayed whenever that item is selected on the vendor. ALL ITEMS MUST HAVE A TEXTURE KEY IN THE CATEGORY NOTECARD. Skipping this will cause the vendor and master inventory object not to function properly. If you do not wish to have a texture to display for that item, this is fine, but you will still need to supply a texture key. I suggest using a "default" texture if you do not want to create a seperate texture for each item, or if the item doesn't really have a texture associated with it.

Since the SVN vendors only deal with texture keys, it is not necessary to have the textures physically present in the vendor or the master inventory object. Therefore, it is not necessary to leave these textures anywhere in the grid; Storing them in your inventory is perfectly fine.

How do you get texture keys? With the texture in your inventory, simply right click on it. Select the option "Copy Asset UUID" and you will have a copy of the texture key in your cut-and-paste buffer. Simple go to the category notecard (more on this later, in the "Category Notecards" section) and just paste it.


----------------------
Category Notecards
----------------------

These notecards exist both in the vendors and the master inventory object. They define the actual category listing, plus the item listing for each category. There are two types of category notecards; The master category notecard name "Categories" which lists all of the categories in the system. This is what the Vendor and Master Inventory object uses to calculate the number of total categories, and to map category numbers to human-readable category names.

Typical contents of the Categories notecard would be:

******************* [ Notecard: Categories ]
1|Test Category #1
2|Test Category #2
******************* [ End: Categories ]

All category notecard parameters are seperated by pipes. (Shift-\ on 101 USD keyboards.)

The first parameter is the category number. This is actually ignored by SVN -- But it gives you an easy way to map category names to numbers. The first category listed with always be #1, the second #2, and so on. So re-ordering the numbers WILL NOT affect category numbering. You must physically re-order the entries.

In our example, Category #1 will be named "Test Category #1". This is the name that will be displayed by the vendor.

The individual category notecards contain the item lists. The name of the notecard is "CategoryX", where X is the category number. This needs to match what is in Categories! Case and lack of space between Category and X is important.

A typical CategoryX notecard looks like:

******************* [ Notecard: Category1 ]
CUBE!|1|a2486397-6c4a-d33d-b882-3968f1028269
CONE!|1|b64462f2-fddb-58d3-852d-db905b47c181
CYLINDER!|1|96ab6d84-fff1-4648-d705-46be0c6bc836
******************* [ End: Category1 ]

The first parameter is the ITEM NAME as found in the vendor's inventory. The second parameter is ITEM PRICE in $L, with a minimum of 1. The third and final parameter is TEXTURE KEY (UUID). See "Textures for Items" for more information about how to get texture keys.

I have tested Category notecards with around 50-60 items, and about 10 categories total. This is a significant number of items, though of course the more items you have in your vendor, the longer operations such as load and such will take. Item/category switching is fairly fast, however, even with a lot of items. I wouldn't recommend more than 20 items per category as a way to even load however. You can always add more categories.


------------------------------------------
Category Database Scripts (in Vendor)
------------------------------------------

In the Vendor there are Item Database scripts, labeled "MM SVN Item Database v2.02 Cat #1" up to any number after "Cat #". These scripts load the entire category notecard (Category1 for Cat #1, etc) into memory and then reference it when requested by the Primary script.

What does this mean in layman's terms? You must have one Item Database Cat #X script for every CategoryX script. For example, if you have these notecards:

Category1
Category2
Category3

You need to also have these scripts in the root prim of your vendor:

MM SVN Item Database v2.02 Cat #1
MM SVN Item Database v2.02 Cat #2
MM SVN Item Database v2.02 Cat #3

How do you create more of the Item Database scripts? Just make a copy of any of the existing Item Database scripts and then re-name it. The script examines its own name and figures out which category it will be responsible for. If you do not have an Item Database script for a category, then you vendor will not load that category properly! It will say "Just a Moment... Loading items for Category XXXX" forever as you try to switch to that category.

CONTINUED...>
Fenrir Reitveld
Crazy? Don't mind if I do
Join date: 20 Apr 2005
Posts: 459
SVN System part 2
03-24-2006 20:13
Documentation notecard, part 2:

CODE
-----------------------------------------
Holo Items & Informational Notecards
-----------------------------------------

Notecards are simply notecards stored in the vendor/master inventory object which contain information about your item. They can list instructions, features, or whatever you want. When a user presses the "Get Info" button under the menu of a vendor, or uses a button assigned to the same task, the vendor will hand them a notecard if it exists.

The notecard is always named "ITEMNAME Info". ITEMNAME is of course the name of the item that you wish to associate a notecard with. It is VERY IMPORTANT to note that " Info" is (SPACE)(Capital-I)(nfo). If you mix capitalization (such as INFO or info), it will not work.

Holo items are temporary-on-rez items that are rezzed when the user selects the 3D view. You can use this to demonstrate exactly how your object will look by having the user be able to browse a scaled down version. SVN allows you to easily mix 2D/3D views of your objects.

To prepare an item for holo view, you should rez the object that you wish to make a holo of. Strip out all scripts that might make the item function independantly (such as vehicle scripts). You will likely want to resize the item to fit your vendor display. Once you are done with this, name the item "ITEMNAME Holo", where ITEMNAME is the vendor's item name corresponding to the object that this holo will represent. Like with notecards, the Holo must be (SPACE)(Capital-H)(olo).

After this, drag a copy of the "MM SVN Holo Controller v1.02" into your holo item. This will be the script used to control the de-rez of the holo item. It also controls the intial rotation of your holo item.

Your item will rotate around its center point, along the Z-up axis. Therefore, you might need to modify the Holo Controller script in case your object's root prim does not face Z-up. An easy way to do this is documented in the Holo Controller script and will be reprinted here:

// "HEY MY ITEM IS UPSIDE-DOWN/SIDEWAYS/ETC!!"
// You can fix this by setting the initial rez angle. Due to the way the SVN vendor works, it will always
// rez the item at 0,0,0 rotation. You can override this below if you have some bizarrely made item with
// a root prim which doesn't face Z-Up.
//
// "Uh, what?" Okay, rez your holo item. Rotate it how you want it to appear. Go to the edit menu and
// then write down the X/Y/Z rotation numbers you see there. Plug them into the variable below, so that
// X is the first number, Y is the second, and Z is the last.

Always be sure to test your holo items by taking it into your inventory, and then rezzing it by hand before placing it into the vendor. This will help you rectify rotation/size issues without having to deal with the vendor.

Holo items are rezzed at a specific offset as given in the MM SVN Vendor Config script, under the parameter "holo_offset". See the section "Vendor Configuration Notecard" for details.


------------------
Troubleshooting
------------------

Q. My vendors can't find the master inventory object if I do an update!

A. Make sure that the master object is prefixed with the name as listed under the "master_inventory_name" option in the vendor config file.

Q. My vendor has errored out, but the master object will not push to it, even with a Force Push.

A. Verify that the vendor name prefix is listed in the "vendor_name_prefix" option under the master object config file.

Q. My vendor finds the master inventory object, but it never actually does anything. I see it send the request, but it just sits there.

A. Check to make sure that the Command Channel option is the SAME in both vendor and master object configs.

Q. My vendor is stuck! It says "Just a moment... Reading items for Category Name" and never does anything else?

A. Read the section entitled "Category Database Scripts (in Vendor)". You do not have an Item Database script that matches the category. This will cause the vendor to never load the items and therefore stick.

----------------------------
Technical Documentation
----------------------------

This is a list of all of the messages passed between Vendor scripts. I use the message parameter as an argument stack (seperated by pipes), and occasionally use the id key as a way to store requestor. Therefore, I use numeric messages for all intra-process communictions, which can be a bit tedious to track. Therefore, I have included a list of all of the messages I pass around. Note on legend: msg refers to message, and id to the key, as passed by llMessageLinked() in the third and fourth parameters.

All intra-process messages are emitted to LINK_THIS, and therefore all Vendor scripts should be kept in the same prim. The ONLY EXCEPTIONS to this is the Texture Manager and the Titler scripts; Primary emits the messages to the entire link set. (LINK_SET)

******************* [ Info: Link numbers ]
Button command messages

5000 = Direct category button (msg = category number)
5001 = Flip from 2d to 3d
5002 = Information request (id = agent requesting info)
5003 = Prev cat button
5004 = Next cat button
5005 = Prev item button
5006 = Next item button

Button status messages

6000 = Viewer is in 2D mode
6001 = Viewer is in 3D mode
6002 = We have an informational notecard
6003 = We do not have a notecard
6004 = We have a 3D view
6005 = We do not have a 3D view

Configuration messages

1200 = Ask the config module what the config is
1201 = Ask config module to restart

1300 = reply from config module

Inventory load messages

2000 = Load inventory for category #
2001 = Inventory lookup request

3000 = Reply with inventory # to indicate inventory loaded
3001 = Inventory lookup reply
3002 = Inventory lookup failure!

Common functions messages

4000 = llSay emitter with parsing
4001 = llInstantMessage emitter with parsing
4500 = Set object floating text (color is in key)

Inventory update messages

8000 = Begin update procedure
8001 = Flush inventory
8500 = Request master script reboot from inactive
8501 = Return to normal operation

10000 = Remove inventory
10001 = Inv removal done

Master restart messages

600000 = master restart message
******************* [ End: Link numbers ]

Areas that SVN could be optimized/improved:

* Currently, the Master Inventory object sends all of its inventory to the vendors. This includes both physical objects and notecards. But there's really no reason to send notecards! Why? Because scripts can read a notecard from a key, just like they can load a texture or play a sound from a key; These items are just blocks of data on the asset server, and in the case of notecards, are received by the actual script by a data server event. This means that instead of actually physically handing notecards to an object, the master object could just pass it a series of keys for the Categories and Category# notecards.

* The loading of the categories database in the primary vendor script could be optimized, as switching categories isn't very efficient. However, I've found it's fast enough for my purposes. Furthermore, additional changes to the Primary script could very likely overflow script memory (as of all of SVN, it's by far the most complicated script). This really needs to broken apart into more sub-scripts, but I feel that SVN is already too big.

* Condense email handling logic to just one script, instead of two as it is currently. This wouldn't be that hard to do, but as I stated in the SMTP Scheduler introduction I am just re-using code from another project.

* Condense the Inventory Manager into the Common Functions script on the Vendor script set. Really no reason for it to be a seperate script, as inventory loads/clears will cause the vendor to already be in a non-user interactive state. I simply seperated the script out just in case for future growth in the loading logic, which never happened.

* I really hate that each category sits on a 16k chunk of scripting memory inside of each script. The only other option is to load the inventory for each currently-browsed category, which is how the vendor originally worked. -- But this is just way too slow in sims that are running anything less than 1.0 time dilation and/or small script ips perf numbers. Therefore, to speed this up I had to store each complete inventory notecard inside a single script. This makes the SVN vendor nearly as fast as a "standard" vendor that just loads all of the items into a list, at the cost of wasting 16k X number of categories memory. (160k in my case.) Maybe when Mono comes along and we get dynamic script memory allocation, this won't be such an issue. But then again, I have a feeling we'll get Mono about the time Duke Nukem Forever is released. ;)

* I'd like to see multiple windows that can be browsed individually. In other words, lock prim A to category X, and have prev/next item keys that can manipulate it directly. So one could do a multi-item display easily. However, due to the structure of SVN this isn't all that easy to add. It would be doable, but it means abstracting a few things that are currently not abstracted. :) Easiest way to do this would be to rewrite Primary's logic to take into account multiple Primaries, running on seperate prims. This wouldn't be all that hard to do, as currently the Primary/Inventory manager scripts emit link messages to LINK_THIS. They do not REQUIRE you to have everything loaded in the root prim.


---------
The End
---------


And now, we're on the Master Inventory object (see the docs)!

The Master Inv Object is made of four scripts, all loaded in a single prim of your choice.

MM SVN Master v1.00
MM SVN Loader v1.00
MM SVN Inventory Validation v1.00
MM SVN Inventory Database v1.02

Master Inv Obj Script: MM SVN Master v1.00
CODE
// -[ Licensing and Copyright ]-----------------------
//
// Script copyright by Fenrir Reitveld, 02/10/06
//
// This program is free software; you can redistribute it and/or modify it under the terms of the
// GNU General Public License version 2 as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
// the GNU General Public License for more details.

// You should have received a copy of the GNU General Public License along with this program in a
// notecard; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
// Boston, MA 02110-1301, USA.
//
// GPL license version 2 on the web: http://www.gnu.org/copyleft/gpl.html (Note, URL might change
// once v3 is released.)

// -[ Script Purpose ]--------------------------------
//

// You can change the menu chat channel for maximum security
integer MENUCHAN = -492104 ;
integer COMMANDCHAN = -2820004 ;

string configname = "MM SVN Master Config" ;
key queryhandle ;
integer line ;

string vendor_name_prefix ;

list vendor_keys ;

// START OF FUNCTIONS

// killme: Does as it says, removes the Master inventory object from the sim
killme ()
{
//llSetPrimitiveParams ([ PRIM_TEMP_ON_REZ , TRUE]) ; // No point in setting TEMP_ON_REZ, as this only applies to newly rezzed objs
llOwnerSay (" ** ALL DONE, CLEANING UP ** ") ;
// BAM! Make sure you frequently take a copy to your inventory when testing stuff out. :)
llDie() ;
}

// request_load: Send an inventory load request to the first vendor found in queue vendor list
request_load()
{
key id = (key)llList2String (vendor_keys, 0) ;
llOwnerSay ("Requesting vendor " + llKey2Name (id) + " do inv flush and load.") ;
llShout (COMMANDCHAN, "ril|" + (string)id) ; // Request Inventory Load
llSetTimerEvent (10.0) ; // Give our vendor 10 seconds to reply to request
}

// eatspace: My own version of BASIC's LTRIM(RTRIM(STR$)), basically just eats spaces at begin/end of string
string eatspace (string in)
{
// We now return you to your regularly scheduled string parsing...
integer i = 0 ;
while (llGetSubString (in, i, i) == " " && i < llStringLength (in))
i++ ;
string tmp = llGetSubString (in, i, -1) ;
i = llStringLength (tmp) - 1 ;
while (llGetSubString (tmp, i, i) == " " && i > 0)
i-- ;
string tmp2 = llGetSubString (tmp, 0, i) ;
return tmp2 ;
}

// END OF FUNCTIONS


default
{
state_entry()
{
// initial startup
llOwnerSay ("Resetting " + llGetObjectName() + "...") ;
// let's set some defaults
queryhandle = llGetNotecardLine(configname, line);
line++ ;
}
on_rez (integer p)
{
llResetScript() ;
}
changed (integer change)
{
if (change & CHANGED_OWNER) llResetScript() ;
}
dataserver(key query_id, string data) {
if (query_id == queryhandle) {
// I've written this token parser so many times in so many languages...
if (data != EOF) { // not at the end of the notecard
// yay! Parsing time
// first, is it a comment? or an empty line?
if (llGetSubString (data, 0, 0) != "#" && llStringLength (data) > 0)
{
list parsed = llParseString2List (data, ["="], [""]) ;
string token = llToLower (eatspace (llList2String (parsed, 0))) ;
if (token == "vendor_name_prefix")
vendor_name_prefix = llToLower (eatspace (llList2String (parsed, 1))) ;
if (token == "command_channel")
COMMANDCHAN = (integer)eatspace (llList2String (parsed, 1)) ;
}
queryhandle = llGetNotecardLine(configname, line);
line++;
} else {
state running ;
}
}
}
}

// END OF FUNCTIONS

state running
{
state_entry()
{
llOwnerSay ("Master inventory online! I am listening on command_channel " + (string)COMMANDCHAN + ".") ;
llListen (COMMANDCHAN, "", "", "") ;
llListen (MENUCHAN, "", llGetOwner(), "") ;
llSetPrimitiveParams ([ PRIM_TEMP_ON_REZ , FALSE]) ;
}

on_rez (integer p)
{
llResetScript() ;
}
touch_start (integer num)
{
if (llDetectedKey (0) == llGetOwner())
{
// Give our owner a menu
llDialog (llDetectedKey (0), "Select an option.\n\nForce Push = Forces a push on crashed/inactive vendors.\nCheck Inv = Check all inventory items to make sure notecards agree.", [ "Force Push", "Check Inv", "Reset" ], MENUCHAN) ;
}
}

sensor (integer num)
{
// The sensor event is fired when the owner wants to manually force a vendor reload
vendor_keys = [] ;
integer i ;
for (i = 0; i < num; i++)
{
if (llDetectedOwner (i) == llGetOwner())
{
if (llSubStringIndex (llToLower (llDetectedName (i)), vendor_name_prefix) != -1)
{
llOwnerSay ("Found vendor named " + llDetectedName (i) + ". Asking it to send request.") ;
llShout (COMMANDCHAN, "ril|" + (string)llDetectedKey(i)) ; // Request Inventory Load
}
}
}
}

no_sensor ()
{
// Ooops
llOwnerSay ("Unable to find any SVN Vendors in range!") ;
}

listen (integer chan, string name, key id, string in)
{
if (llGetOwnerKey (id) == llGetOwner())
{
// Requests from a vendor rezzed in sim
if (chan == COMMANDCHAN)
{
if (in == "dil") // A vendor wants us to load inventory
{
vendor_keys += [ id ] ;
// queue up our inventory load request
llOwnerSay ("Inventory load request from " + name + " QUEUED.") ;
llMessageLinked (LINK_THIS, COMMANDCHAN, "doload", id) ;
}
}
// Requests from llDialog()
if (chan == MENUCHAN)
{
if (in == "Force Push") // Request for manual push
{
llOwnerSay ("Forcing inventory push to nearby inactive vendors. (No effect on running vendors.)") ;
// Start our single sensor ping
llSensor ("", "", PASSIVE | ACTIVE | SCRIPTED, 96, PI) ;
}
if (in == "Check Inv") // Request for inventory check
{
llMessageLinked (LINK_THIS, 0, "doinvcheck", "") ;
llOwnerSay ("Doing inventory check.") ;
}
if (in == "Reset") // Request to reset scripts
llResetScript() ;
}
}
}

link_message (integer sender, integer num, string msg, key id)
{
if (msg == "loaddone") // Inventory loaded has completed sending inventory to vendor
{
integer i ;
for (i = 0; i < llGetListLength (vendor_keys); i++)
{
if ((key)llList2String (vendor_keys, i) == id)
{
// Remove that vendor from our queue
vendor_keys = llDeleteSubList (vendor_keys, i, i) ;
jump nomore ;
}
}
@nomore ;
// Do we have any more vendors waiting in the queue? If not, then go bye-bye
if (llGetListLength (vendor_keys) == 0)
killme() ;
else
request_load () ;
}
}


timer ()
{
// Okay, our load request was ignored after 10 seconds, so let's move on to next vendor in queue
key id = (key)llList2String (vendor_keys, 0) ;
vendor_keys = llDeleteSubList (vendor_keys, 0, 0) ;

if (llGetListLength (vendor_keys) > 0)
request_load () ;
else
llSetTimerEvent (0) ;
}

}

Master Inv Obj Script: MM SVN Loader v1.00
CODE
// -[ Licensing and Copyright ]-----------------------
//
// Script copyright by Fenrir Reitveld, 02/10/06
//
// This program is free software; you can redistribute it and/or modify it under the terms of the
// GNU General Public License version 2 as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
// the GNU General Public License for more details.

// You should have received a copy of the GNU General Public License along with this program in a
// notecard; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
// Boston, MA 02110-1301, USA.
//
// GPL license version 2 on the web: http://www.gnu.org/copyleft/gpl.html (Note, URL might change
// once v3 is released.)

// -[ Script Purpose ]--------------------------------
//
// This script simply loads a vendor with the current Master's inventory. This is broken out into
// a seperate script so that inventory pushes will not block the Master script. This allows for
// additional load requests to be received by the Master and queued for send.

integer COMMANDCHAN = -2820004 ; // Default, no need to manually set this as it is loaded from config

default
{
state_entry()
{

}
on_rez (integer p)
{
llResetScript() ;
}

link_message (integer sender, integer num, string msg, key id)
{
if (msg == "doload") // We have been asked to load inventory into vendor key id.
{
integer i ;
COMMANDCHAN = num ;
llOwnerSay ("Inventory load beginning for " + llKey2Name (id) + "! Sending objects.") ;
// This next second actually pushes the inventory to the vendor
// This Might Take a While, especially on a loaded sim.
for (i = 0; i < llGetInventoryNumber (INVENTORY_OBJECT); i++)
llGiveInventory (id, llGetInventoryName (INVENTORY_OBJECT, i)) ;
// Sometimes, sending the notecards can take a while too.
llOwnerSay ("Sending notecards to " + llKey2Name (id) + ".") ;
for (i = 0; i < llGetInventoryNumber (INVENTORY_NOTECARD); i++)
llGiveInventory (id, llGetInventoryName (INVENTORY_NOTECARD, i)) ;

llOwnerSay ("Done with vendor " + llKey2Name (id) + "!") ;
llShout (COMMANDCHAN, "ilc|" + (string)id) ; // Inventory Load Complete
llMessageLinked (LINK_THIS, 0, "loaddone", id) ; // Tell master script we finished for this vendor.
}
}
}

Master Inv Obj Script: MM Inventory Database v1.02
CODE
// -[ Licensing and Copyright ]-----------------------
//
// Script copyright by Fenrir Reitveld, 02/10/06
//
// This program is free software; you can redistribute it and/or modify it under the terms of the
// GNU General Public License version 2 as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
// the GNU General Public License for more details.

// You should have received a copy of the GNU General Public License along with this program in a
// notecard; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
// Boston, MA 02110-1301, USA.
//
// GPL license version 2 on the web: http://www.gnu.org/copyleft/gpl.html (Note, URL might change
// once v3 is released.)

// -[ Script Purpose ]--------------------------------
//
// This is actually the older version of the inventory load module from the vendor. In this
// script, we are told by Inventory Validation which category to load. We then load a list of
// items, compare them to our actual object inventory, then test to make sure that the texture
// keys given are valid.
//
// Note that this script will change the texture on face #0 of the prim. This is the ONLY WAY
// I could figure out if a certain texture key actually is valid or not; Use llSetTexture to
// write the texture out, then read it back. If the two match, then it's a valid texture and
// the permissions are all okay. (SVN requires full permissions on the item display textures.)
// If not the llGetTexture will fail and return NULL_KEY and it will report this.

string currcategoryname ;
integer currcategory ;
string notecardname ;
integer line ;
integer numitems ;
string items ;
key queryhandle ;

// ----------------------------------------------
// Beginning load (default) state of script
// Purpose: Reads the vendor config notecard, then moves on handling requests
// ----------------------------------------------
default
{
state_entry()
{

}
changed (integer change)
{
if (change & CHANGED_OWNER)
llResetScript() ;
}
link_message (integer sender, integer num, string msg, key id)
{
if (num == 2000)
{
currcategory = (integer)msg ;
currcategoryname = (string)id ;
state read_items ;
}
}

}

// ----------------------------------------------
// Load items from category notecard
// Purpose: Reads the category notecard and all items within, then moves on waiting for requests
// ----------------------------------------------
state read_items
{
state_entry()
{
// Now, let's read category X
line = 0 ;
numitems = 0 ;
items = "" ;
// Check to make sure we have that notecard in inventory
notecardname = "Category" + (string)(currcategory + 1) ;
if (llGetInventoryType (notecardname) == INVENTORY_NONE)
{
llOwnerSay ("We're missing our item list notecard: " + notecardname) ;
llMessageLinked (LINK_THIS, 9999, "", "") ; // Problem, shutdown
llResetScript() ;
}
queryhandle = llGetNotecardLine(notecardname, line);
line++ ;
}
changed (integer change)
{
if (change & CHANGED_OWNER)
llResetScript() ;
}
dataserver(key query_id, string data) {
if (query_id == queryhandle) {
if (data != EOF) { // not at the end of the notecard
// first, is it a comment?
if (llGetSubString (data, 0, 0) != "#" && llStringLength (data) > 0)
{
list parsed = llParseString2List (data, ["|"], []) ;
items += (string)numitems + "|" + data + "&" ;
numitems++ ;

if (llGetFreeMemory() < 700)
{
llOwnerSay ("Low memory reading item #" + (string)(numitems + 1) + " from " + notecardname + "! Too many items!") ;
llMessageLinked (LINK_THIS, 9999, "", "") ; // Problem, shutdown
llResetScript() ;
}
}
queryhandle = llGetNotecardLine(notecardname, line);
line++;
} else {
state waiting ;
}
}
}
link_message (integer sender, integer num, string msg, key id)
{
if (num == 2000) // category reload
{
currcategory = (integer)msg ;
currcategoryname = (string)id ;
state read_items ;
}
}
}

// ----------------------------------------------
// Wait for requests for inventory consistancy check, or switch categories
// Purpose: Handles requests to check item validity, and texture existance. Also, reloads items if new category asked for
// ----------------------------------------------
state waiting
{
changed (integer change)
{
if (change & CHANGED_OWNER)
llResetScript() ;
}
state_entry ()
{
llMessageLinked (LINK_THIS, 3000, (string)numitems, "") ; // We are done loading
}
link_message (integer sender, integer num, string msg, key id)
{
if (num == 2000) // category reload
{
currcategory = (integer)msg ;
currcategoryname = (string)id ;
state read_items ;
}
if (num == 2002) // force inventory check
{
integer i ;
integer index ;
string parsed ;
integer found ;
list iteml ;
for (i = 0; i < numitems; i++)
{
index = llSubStringIndex (items, "&") ;
parsed = llGetSubString (items, 0, index - 1) ;
items = llDeleteSubString (items, 0, index);
items += parsed + "&" ;
iteml = llParseString2List (parsed, ["|"], []) ;
string name = llList2String (iteml, 1) ;
// Look for it in our inventory
if (llGetInventoryType (name) == INVENTORY_NONE)
{
llOwnerSay ("Unable to locate item " + name + "!") ;
llMessageLinked (LINK_THIS, 3003, name ,"") ;
llResetScript() ;
}
// Now let's locate the texture
llSetTexture (llList2String (iteml, 3), 0) ;
if (llGetTexture (0) != (key)llList2String (iteml, 3))
{
// Kinda hacky way to look for textures...
llOwnerSay ("Texture key " + llList2String (iteml, 3) + " does not exist! Correct this for item " + name + "!") ;
}

}
llSetTexture ("7e501c48-c80d-3f04-34d2-1b923e0ed096", 0) ; // Set it back to the SVN logo. ;)
llMessageLinked (LINK_THIS, 3002, "" ,"") ; // Tell validator we are done
}
}
}


Continued!
Fenrir Reitveld
Crazy? Don't mind if I do
Join date: 20 Apr 2005
Posts: 459
SVN System Part 3
03-24-2006 20:21
Master Inv Obj Script: MM SVN Inventory Validation v1.00

CODE
// -[ Licensing and Copyright ]-----------------------
//
// Script copyright by Fenrir Reitveld, 02/10/06
//
// This program is free software; you can redistribute it and/or modify it under the terms of the
// GNU General Public License version 2 as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
// the GNU General Public License for more details.

// You should have received a copy of the GNU General Public License along with this program in a
// notecard; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
// Boston, MA 02110-1301, USA.
//
// GPL license version 2 on the web: http://www.gnu.org/copyleft/gpl.html (Note, URL might change
// once v3 is released.)

// -[ Script Purpose ]--------------------------------
//
// Inventory validation is important. It lets you tell if you have your notecards correctly loaded
// and the keys for the textures correct. It also examines to make sure that the items you list in
// your notecards actually exist inside your master inventory object.
//
// This script is actually a heavily chopped up part of the vendor script, as it uses the same
// functions for loading the category note cards, extract prices, etc. So you can pretty sure that
// if something doesn't work here, it won't work in the real vendor.

// databases
string categories ;
integer numcategories ;

// misc
integer line ;
key queryhandle ;
string notecardname ;
integer currcategory ;
string currcategoryname ;

// START OF FUNCTIONS

// set_category: Set the current category and load its name
set_category()
{
integer i ;
for (i = 0; i < numcategories; i++)
{
integer index = llSubStringIndex (categories, "&") ;
string parsed = llGetSubString (categories, 0, index - 1) ;
categories = llDeleteSubString (categories, 0, index);
categories += parsed + "&" ;
if (i == currcategory)
{
list iteml = llParseString2List (parsed, ["|"], []) ;
currcategoryname = llList2String (iteml, 1) ;
}
}
}

// END OF FUNCTIONS

// ----------------------------------------------
// Default script state
// Purpose: Just do nothing, wait for incoming inventory validation requests
// ----------------------------------------------
default
{
state_entry()
{
llSetText ("", ZERO_VECTOR, 0.0) ;
// wait for requests
}
link_message (integer sender, integer num, string msg, key id)
{
if (msg == "doinvcheck")
state initialstart ;
}


}

// ----------------------------------------------
// Initial start of inventory validation
// Purpose: Loads categories from the "Categories" notecard
// ----------------------------------------------
state initialstart
{
state_entry()
{
// let's read category notecard
line = 0 ;
numcategories = 0 ;
llSetText ("Just a moment...\nLoading categories!", <1, 0, 0>, 1.0) ;
// Check to make sure we have that notecard in inventory
notecardname = "Categories" ;
if (llGetInventoryType (notecardname) == INVENTORY_NONE)
{
llOwnerSay ("We're missing our category list notecard: " + notecardname) ;
llResetScript() ;
}
queryhandle = llGetNotecardLine(notecardname, line);
line++ ;
}
changed (integer change)
{
if (change & CHANGED_OWNER)
llResetScript() ;
}
dataserver(key query_id, string data) {
if (query_id == queryhandle) {
if (data != EOF) { // not at the end of the notecard
// first, is it a comment?
if (llGetSubString (data, 0, 0) != "#" && llStringLength (data) > 0)
{
list parsed = llParseString2List (data, ["|"], []) ;
numcategories++ ;
categories += data + "&" ;
if (llGetFreeMemory() < 1024)
llOwnerSay (notecardname + " has too many items!") ;
}
queryhandle = llGetNotecardLine(notecardname, line);
line++;
} else {
currcategory = 0 ;
state startinvcheck ;
}
}
}
}


// ----------------------------------------------
// Item reader
// Purpose: Instructs the seperate item DB script to load all inventory items
// ----------------------------------------------
state startinvcheck
{
state_entry()
{
// What is the point of this empty state? ^_^ Well, we need to set the category to load its name, then we
// need to fire off the read_items state. Really, there's no point for this state. Just leftover cruft
// from the vendor script. Feel free to optimize out, as I've done a code freeze for this release and don't
// want to do testing again.
set_category () ;
state read_items ;
}
}

// ----------------------------------------------
// Item reader
// Purpose: Instructs the seperate item DB script to load all inventory items
// ----------------------------------------------
state read_items
{
state_entry()
{
llSetText ("Just a moment...\nReading items for " + currcategoryname + ".", <1, 0, 0>, 1.0) ;
llSetTimerEvent (120.0) ;
llResetTime() ;
llMessageLinked (LINK_THIS, 2000, (string)currcategory, currcategoryname) ; // Ask our inventory DB to load this category
}
timer ()
{
llMessageLinked (LINK_THIS, 2000, (string)currcategory, currcategoryname) ; // Ask it again, in case it didn't hear after 2 minutes ^_^
}
link_message (integer sender, integer num, string msg, key id)
{
if (num == 3000) // inv load done
state invcheckitemsread ;
if (num == 9999) // Ooops, some error happened
{
llOwnerSay ("Inventory load failed!") ;
llResetScript() ;
}
}
}

// ----------------------------------------------
// Item checker
// Purpose: Now that we have the inventory loaded by the database, we can do consistancy check
// ----------------------------------------------
state invcheckitemsread
{
state_entry()
{
llSetText ("Just a moment...\nChecking category " + currcategoryname + "...", <1, 0, 0>, 1.0) ;
llMessageLinked (LINK_THIS, 2002, "", "") ; // Ask the inventory DB script to do our consistance check
}
link_message (integer sender, integer num, string msg, key id)
{
if (num == 3002) // inv check done
{
// We've done with that category. Let's move on the next one, if there is a next one...
currcategory++ ;
if (currcategory > numcategories - 1)
{
llOwnerSay ("Inventory check done. Items are okay.") ;
llResetScript() ;
}
set_category() ;
state read_items ;
}
if (num == 3003) // inv check error
{
// Ooops. Stop the process and list the bad item for the user.
llOwnerSay ("FATAL ERROR. Unable to find item [" + msg + "] in category <" + currcategoryname + ">") ;
llResetScript() ;
}
}
}


AND that's it for the master inventory object! Now, the next set of scripts are for the actual vendor itself. Not ALL of these scripts are necessary, as demonstrated in the documentation notecard.

These scripts are MANDATORY:

MM SVN Primary v1.07
MM SVN Common Functions v1.02
MM SVN Vendor Inv Manager v1.00
MM SVN SMTP scheduler v1.00
MM SVN SMTP daemon #1 v1.00
MM SVN Item Database v2.02 Cat #1
..
MM SVN Item Database v2.02 Cat #10

These are not mandatory, but generally you want them so that the user interface operates. :) You will have to modify these for your tastes. For example, the texture manager defaults to changing the texture on side #1 of a prim -- adjust for your own configuration.

MM SVN Unsitter v1.00
MM SVN Texture Manager v1.01
MM SVN Titler v1.00

Vendor: MM SVN Common Functions v1.02
CODE
// -[ Licensing and Copyright ]-----------------------
//
// Script copyright by Fenrir Reitveld, 02/10/06
//
// This program is free software; you can redistribute it and/or modify it under the terms of the
// GNU General Public License version 2 as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
// the GNU General Public License for more details.

// You should have received a copy of the GNU General Public License along with this program in a
// notecard; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
// Boston, MA 02110-1301, USA.
//
// GPL license version 2 on the web: http://www.gnu.org/copyleft/gpl.html (Note, URL might change
// once v3 is released.)

// -[ Script Purpose ]--------------------------------
// This is a catch-all for the SVN vendor script assembly. It contains a bunch of glue code for
// the various modules, such as the Primary script when it wants to do inventory updates, or
// output friendly-formated messages to the user.
//
// It's original name was "Config Module" but I expanded it to include other functions so I had
// to rename it. :) It's original purpose was just to handle loading the config notecard and this
// is its primary function.

// -[ User Config Section ]---------------------------

// Set the name of you master inventory object. This is the EXACT NAME that your vendor will look
// for when trying to grab inventory. By changing this, you CAN have two or more seperate sets of
// vendors and inventories.
string MASTER_INVENTORY_NAME = "SVN Master Inventory Example" ;

// Set our standard "please stand by" texture
key STANDBY_TEXTURE = "d64323a7-d8e3-088f-d800-79161cfc8b09" ;

// -[ Core Program ]----------------------------------
//
// Everything below here isn't considered user-editable. Unless you want to change my code of
// course. :) Just be sure to adhere to the GPL if you do!

string configname = "MM SVN Vendor Config" ;
integer COMMANDCHAN = -2820004 ;
key queryhandle ;
integer line ;

// Config data loaded from notecard, with some sane defaults
string email_address ;
string holo_offset ;
string notecard_sound ;
string purchase_sound ;
string help_message = "No help was defined. @, please ask vendor owner to add some. :)" ;
string purchase_message = "Thanks for your business, @!" ;

// START OF FUNCTIONS


// eatspace: Recreation of the LTRIM(RTRIM(STR$)) command from BASIC. (ie: removes spaces padding begin/end of a line)
string eatspace (string in)
{
// We now return you to your regularly scheduled string parsing...
integer i = 0 ;
while (llGetSubString (in, i, i) == " " && i < llStringLength (in))
i++ ;
string tmp = llGetSubString (in, i, -1) ;
i = llStringLength (tmp) - 1 ;
while (llGetSubString (tmp, i, i) == " " && i > 0)
i-- ;
string tmp2 = llGetSubString (tmp, 0, i) ;
return tmp2 ;
}

// END OF FUNCTIONS

// ----------------------------------------------
// Beginning load (default) state of script
// Purpose: Reads the vendor config notecard, then moves on handling requests
// ----------------------------------------------
default
{
state_entry()
{
// initial startup
llOwnerSay ("Reading vendor config.") ;
// let's set some defaults
holo_offset = "<0, 0, -0.6>" ;
queryhandle = llGetNotecardLine(configname, line);
line++ ;
}
on_rez (integer p)
{
// Always re-read upon rez
llResetScript() ;
}
changed (integer change)
{
if (change & CHANGED_OWNER) llResetScript() ;
}
dataserver(key query_id, string data) {
if (query_id == queryhandle) {
// I've written this token parser so many times in so many languages...
if (data != EOF) { // not at the end of the notecard
// yay! Parsing time
// first, is it a comment? or an empty line?
if (llGetSubString (data, 0, 0) != "#" && llStringLength (data) > 0)
{
list parsed = llParseString2List (data, ["="], [""]) ;
string token = llToLower (eatspace (llList2String (parsed, 0))) ;
if (token == "email_address")
email_address = eatspace (llList2String (parsed, 1)) ;
if (token == "holo_offset")
holo_offset = eatspace (llList2String (parsed, 1)) ;
if (token == "notecard_sound")
notecard_sound = eatspace (llList2String (parsed, 1)) ;
if (token == "purchase_sound")
purchase_sound = eatspace (llList2String (parsed, 1)) ;
if (token == "help_message")
help_message = eatspace (llList2String (parsed, 1)) ;
if (token == "purchase_message")
purchase_message = eatspace (llList2String (parsed, 1)) ;
if (token == "master_inventory_name")
MASTER_INVENTORY_NAME = eatspace (llList2String (parsed, 1)) ;
if (token == "command_channel")
COMMANDCHAN = (integer)eatspace (llList2String (parsed, 1)) ;
}
queryhandle = llGetNotecardLine(configname, line);
line++;
} else {
if (email_address == "")
llOwnerSay ("NOTICE! No email_address specified. You will NOT receive purchase emails or status updates!") ;
state handlerequests ;
}
}
}
}

// ----------------------------------------------
// Handle incoming requests from Primary script
// Purpose: Waiting state where we just sit and wait for incoming linked messages
// ----------------------------------------------
state handlerequests
{
state_entry()
{
// Give some user feedback...
llOwnerSay ("Config read. Ready.") ;
}
changed (integer change)
{
if (change & CHANGED_OWNER) llResetScript() ;
}
link_message (integer sender, integer num, string msg, key id)
{
if (num == 1200) // request for config data
{
llMessageLinked (LINK_SET, 1300, email_address + "|" + holo_offset + "|" + notecard_sound + "|" + purchase_sound + "|" + help_message + "|" + purchase_message + "|" + (string)COMMANDCHAN, "") ;
return ;
}
if (num == 1201) // request to reload config
{
llResetScript() ;
}
if (num == 4000) // emit a string with parsing
{
// let's recurse through string and find all occurances of @
integer index = llSubStringIndex (msg, "@") ;
while (index != -1)
{
msg = llDeleteSubString (msg, index, index) ;
msg = llInsertString (msg, index, llKey2Name (id)) ;
index = llSubStringIndex (msg, "@") ;
}
llSay (0, msg) ;
return ;
}
if (num == 4001) // instant-message a string with parsing
{
// let's recurse through string and find all occurances of @
integer index = llSubStringIndex (msg, "@") ;
while (index != -1)
{
msg = llDeleteSubString (msg, index, index) ;
msg = llInsertString (msg, index, llKey2Name (id)) ;
index = llSubStringIndex (msg, "@") ;
}
llInstantMessage (id, msg) ;
return ;
}
if (num == 8000) // Request to update from remote source
{
state update ;
}
}
}

// ----------------------------------------------
// Remote update
// Purpose: Update inventory in vendor from master object
// ----------------------------------------------
state update
{
state_entry ()
{
llSetPayPrice (PAY_HIDE, [ PAY_HIDE, PAY_HIDE, PAY_HIDE, PAY_HIDE ]) ;
llMessageLinked (LINK_SET, 1000, "", STANDBY_TEXTURE) ;
llMessageLinked (LINK_SET, 4500, "-- Inventory Load --\nScanning for SVN Updater.", "<0, 1, 0>") ;
llOwnerSay ("Scanning for " + MASTER_INVENTORY_NAME + "...") ;
llSensor (MASTER_INVENTORY_NAME, "", PASSIVE | ACTIVE | SCRIPTED, 96, PI) ;
}
sensor (integer num)
{
// just use first master inventory object we find
if (llDetectedOwner (0) == llGetOwner())
{
// Okay, it's ours, we can continue
llMessageLinked (LINK_SET, 4500, "-- Inventory Load --\nFlushing inventory.", "<0, 1, 0>") ;
llMessageLinked (LINK_THIS, 10000, "", "") ;
}
}
no_sensor ()
{
// Ooops
llOwnerSay ("!!!!! I did not find an object named '" + MASTER_INVENTORY_NAME+ "' in range.") ;
llMessageLinked (LINK_THIS, 8501, "", "") ; // return to normal operation
}
link_message (integer sender, integer num, string msg, key id)
{
if (num == 1201 || num == 1200) // request to reload config
{
llResetScript() ;
}
if (num == 10001) // remove inventory done
{
llOwnerSay ("Requesting inventory load...") ;
llMessageLinked (LINK_SET, 4500, "-- Inventory Load --\nLoading inventory.", "<0, 1, 0>") ;
llListen (COMMANDCHAN, "", "", "") ;
llShout (COMMANDCHAN, "dil") ; // Do Inventory Load
}
}
listen (integer chan, string name, key id, string in)
{
if (llGetOwnerKey (id) == llGetOwner())
{
list cmds = llParseString2List (in, ["|"], []) ;
if ((key)llList2String (cmds, 1) == llGetKey()) // The msg was meant for us?
{
if (llList2String (cmds, 0) == "ilc") // Inventory load complete
{
llMessageLinked (LINK_THIS, 0, "mailer_send|" + email_address + "|[STATUS] Inv reload complete.|Invetory reload was requested by object " + llKey2Name (id) + " with key " + (string)id + ".\n", "") ;
llMessageLinked (LINK_THIS, 8500, "", "") ; // restart primary script
llResetScript() ;
}
}
}
}
}


Vendor: MM SVN Item Database v2.02 Cat #1
CODE
// -[ Licensing and Copyright ]-----------------------
//
// Script copyright by Fenrir Reitveld, 02/10/06
//
// This program is free software; you can redistribute it and/or modify it under the terms of the
// GNU General Public License version 2 as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
// the GNU General Public License for more details.

// You should have received a copy of the GNU General Public License along with this program in a
// notecard; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
// Boston, MA 02110-1301, USA.
//
// GPL license version 2 on the web: http://www.gnu.org/copyleft/gpl.html (Note, URL might change
// once v3 is released.)

// -[ Script Purpose ]--------------------------------
//
// This is a category database script.
// A category database is just a collection of item names, prices, and texture UUIDs used to populate
// the vendor's item selector.

// The name of the script determines what category # it serves. For example, if the script is named
// "MM SVN Item Database vX.XX Cat #1", then it will load the category database as given in a notecard
// named "Category1".
//
// Recent optimizations suggested by Spuds Milk, who helped unjam my illogical view of how to handle
// speedy string seaches. :) Thanks to Maklin Deckard for helping me discover exactly what areas I
// needed to optimize.

// I really hate doing it this way, because it means a category with only one item will consume 16k
// of script memory. But the alternative (loading the item notecard every time a category is switched)
// is simply too slow for realtime use, especially with the new script scheduler. SLers as a whole
// are only recently getting used to a slower, less-responsive world and therefore aren't quite so
// understanding when a large scale vendor doesn't immediately respond. ^_^

integer currcategory ;
string notecardname ;
integer line ;
integer numitems ;
string items ;
key queryhandle ;


default
{
state_entry()
{

// Let's figure out what category # we are handling
currcategory = (integer)llGetSubString (llGetScriptName(), llSubStringIndex (llGetScriptName(), "Cat #") + 5, -1) ;
//llOwnerSay ("Reading inventory for category #" + (string)currcategory) ;
// Now, let's read category X
line = 0 ;
numitems = 0 ;
items = "" ;
// Check to make sure we have that notecard in inventory
notecardname = "Category" + (string)(currcategory) ;
//if (llGetInventoryType (notecardname) == INVENTORY_NONE)
//{
//llOwnerSay ("We're missing our item list notecard: " + notecardname) ;
//llMessageLinked (LINK_THIS, 9999, "", "") ; // Problem, shutdown
//}
if (llGetInventoryType (notecardname) == INVENTORY_NOTECARD)
{
queryhandle = llGetNotecardLine(notecardname, line);
line++ ;
}
}
changed (integer change)
{
if (change & CHANGED_OWNER)
llResetScript() ;
}
link_message (integer sender, integer num, string msg, key id)
{
if (num == 600000) // Master restart requested
llResetScript() ;
}

dataserver(key query_id, string data) {
if (query_id == queryhandle) {
if (data != EOF) { // not at the end of the notecard
// first, is it a comment?
if (llGetSubString (data, 0, 0) != "#" && llStringLength (data) > 0)
{
list parsed = llParseString2List (data, ["|"], []) ;
items += "&" + (string)numitems + "|" + data ;
numitems++ ;

if (llGetFreeMemory() < 700)
{
llOwnerSay ("Low memory reading item #" + (string)(numitems + 1) + " from " + notecardname + "! Too many items!") ;
llMessageLinked (LINK_THIS, 9999, "", "") ; // Problem, shutdown
}
}
queryhandle = llGetNotecardLine(notecardname, line);
line++;
} else
state waiting ;
}
}
}

state waiting
{
state_entry ()
{
llOwnerSay ("Done loading category #" + (string)currcategory) ;
//llMessageLinked (LINK_THIS, 3000, (string)numitems, "") ; // We are done loading
}
changed (integer change)
{
if (change & CHANGED_OWNER)
llResetScript() ;
}
link_message (integer sender, integer num, string msg, key id)
{
if (num == 600000) // Master restart requested
llResetScript() ;

if (num == 2000) // Ask to do category switch
{
// Let's see if we are the category that the Primary is looking for
if ((integer)msg != currcategory)
return ;
llMessageLinked (LINK_THIS, 3000, (string)numitems, "") ;
return ;
}
if (num == 2001) // item lookup
{
// Let's see if we are the category that the Primary is looking for
list data = llParseString2List (msg, ["|"], []) ;
if (llList2Integer (data, 0) != currcategory)
return ; // it's not a request for us
// Okay, next section of code is a little heap spammy
// But I had to optimize, as the original version was causing several-minutes-long inventory lookups
// in sims with a time dilation of less than 0.70
integer curritem = (integer)msg ;
integer index ;
list iteml ;

index = llSubStringIndex (items, "&" + llList2String (data, 1) + "|") ;
if (index != -1)
{
// Okay, walk to end now
integer end = llSubStringIndex (llGetSubString (items, index + 1, -1), "&") ;
if (end == -1)
iteml = llParseString2List (llGetSubString (items, index + 1, -1), ["|"], []) ;
else
iteml = llParseString2List (llGetSubString (items, index + 1, index + end), ["|"], []) ;

integer holo = FALSE ;
if (llGetInventoryType (llList2String (iteml, 1) + " Holo") != INVENTORY_NONE)
holo = TRUE ;
integer note = FALSE ;
if (llGetInventoryType (llList2String (iteml, 1) + " Info") != INVENTORY_NONE)
note = TRUE ;
//llOwnerSay (llList2String (iteml, 0) + "|" + llList2String (iteml, 1) + "|" + llList2String (iteml, 2) + "|" + llList2String (iteml, 3) + "|" + (string)holo + "|" + (string)note) ;
llMessageLinked (LINK_THIS, 3001, llList2String (iteml, 0) + "|" + llList2String (iteml, 1) + "|" + llList2String (iteml, 2) + "|" + llList2String (iteml, 3) + "|" + (string)holo + "|" + (string)note, id) ;
}
else
llMessageLinked (LINK_THIS, 3002, "", id) ; // not found
return ;
}
}
}


Vendor: MM SVN SMTP daemon #1 v1.00
CODE
// -[ Licensing and Copyright ]-----------------------
//
// Script copyright by Fenrir Reitveld, 02/10/06
//
// This program is free software; you can redistribute it and/or modify it under the terms of the
// GNU General Public License version 2 as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
// the GNU General Public License for more details.

// You should have received a copy of the GNU General Public License along with this program in a
// notecard; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
// Boston, MA 02110-1301, USA.
//
// GPL license version 2 on the web: http://www.gnu.org/copyleft/gpl.html (Note, URL might change
// once v3 is released.)

// -[ Script Purpose ]--------------------------------
//
// Not much to talk about here. This script handles farmed out SMTP requests by the SMTP Scheduler
// script, which receives requests from the Primary script.
//
// You have any number of SMTP daemons working for any one scheduler, but note that all scripts
// which are blocked from llEmail() will still count in the object's event queue.
//
// This means that you can't just make 50 copies of this script and expect to be able to send 50
// emails at once without halting the master script. There's a way around that, but if you
// understand how the event structure works, you probably already figured that out by now. ^_^
string mailerid ;

default
{
state_entry()
{
// Really, nothing
// Assume ourselves a unique mailer id, for communication with the Scheduler.
mailerid = (string) (llFrand (7000000) + llGetTime ()) ;
}

link_message(integer sender, integer number, string message, key id)
{
list cmds = llParseString2List (message, ["|"], []) ;
string cmd = llList2String (cmds, 0) ;
// The scheduler is asking if there is any daemon ready to send an email; If we are, send a response with our ID
if (cmd == "SMTP_ISEMAILREADY")
llMessageLinked (LINK_THIS, 0, "SMTP_EMAILREADY|" + mailerid, "") ;
// Incoming request to send an email; Check to see if it's addressed to us, and if so, send it
if (cmd == "SMTP_EMAILSEND")
{
string mid = llList2String (cmds, 1) ;
if (mid == mailerid)
{
string address = llList2String (cmds, 2) ;
string subject = llList2String (cmds, 3) ;
string body = llList2String (cmds, 4) ;
llEmail (address, subject, body) ;
}
}
}
}
Fenrir Reitveld
Crazy? Don't mind if I do
Join date: 20 Apr 2005
Posts: 459
SVN System Part 4
03-24-2006 20:30
Vendor: SVN SMTP scheduler v1.00
CODE
// -[ Licensing and Copyright ]-----------------------
//
// Script copyright by Fenrir Reitveld, 02/10/06
//
// This program is free software; you can redistribute it and/or modify it under the terms of the
// GNU General Public License version 2 as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
// the GNU General Public License for more details.

// You should have received a copy of the GNU General Public License along with this program in a
// notecard; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
// Boston, MA 02110-1301, USA.
//
// GPL license version 2 on the web: http://www.gnu.org/copyleft/gpl.html (Note, URL might change
// once v3 is released.)

// -[ Script Purpose ]--------------------------------
//
// We farm out requests to our mailer daemons. There can be any number, and we will correctly
// dispatch requests to various sub-daemons. This lets us send an email without blocking the
// main script.
//
// This script doesn't do anything, until it is requested to send an email. It then starts a
// timer that checks every 5 seconds to see if a daemon is free, and if so passes off the request.
// This continues until the email queue is empty, where the timer is stopped and the script once
// again sleeps.
//
// This whole system is actually VAST OVERKILL for SVN. The vendors will rarely need to send out
// an email once every few minutes, but I'm just reusing code from another project. So if you
// want to see how to do multi-task scheduling, here's one crude but effective way of doing it.

list emailstosend ;
integer emailtimer ;

default
{
state_entry()
{
// general startup, which uh, is nothing...
}

link_message(integer sender, integer number, string msg, key id)
{
list cmds = llParseString2List (msg, ["|"], []) ;
string cmd = llList2String (cmds, 0) ; // get our command from the message stack

// primary script requests to send an email
if (cmd == "mailer_send")
{
if (llList2String (cmds, 1) != "") // ignore blank addresses
{
emailstosend += [ llList2String (cmds, 1), llList2String (cmds, 2), llList2String (cmds, 3) ] ;
if (!emailtimer)
{
llSetTimerEvent (5) ; // Start checking every 5 seconds for a free daemon
emailtimer = TRUE ;
}
}
}

// One of our daemons has told us we are ready to send
if (cmd == "SMTP_EMAILREADY")
{
// Do we have any emails to send? If so, let's give that unoccupied daemon something to do!
if (llGetListLength (emailstosend) > 0)
{
string id = llList2String (cmds, 1) ;
llMessageLinked (LINK_THIS, 0, "SMTP_EMAILSEND|" + id + "|" + llList2String (emailstosend, 0) + "|" + llList2String (emailstosend, 1) + "|" + llList2String (emailstosend, 2), "") ;
emailstosend = llDeleteSubList (emailstosend, 0, 2) ;
}
}
}


timer ()
{
if (llGetListLength (emailstosend) > 0)
{
// Ask our daemons if there is one free
llMessageLinked (LINK_THIS, 0, "SMTP_ISEMAILREADY", "") ;
} else
{
// Okay, we're done, shut down the active timer.
emailtimer = FALSE ;
llSetTimerEvent (0) ;
}
}

}


Vendor: SVN Vendor Inv Manager v1.00
CODE
// -[ Licensing and Copyright ]-----------------------
//
// Script copyright by Fenrir Reitveld, 02/10/06
//
// This program is free software; you can redistribute it and/or modify it under the terms of the
// GNU General Public License version 2 as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
// the GNU General Public License for more details.

// You should have received a copy of the GNU General Public License along with this program in a
// notecard; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
// Boston, MA 02110-1301, USA.
//
// GPL license version 2 on the web: http://www.gnu.org/copyleft/gpl.html (Note, URL might change
// once v3 is released.)

// -[ Script Purpose ]--------------------------------
//
// Short and sweet: This script simply handles removal of all items in the vendor's inventory.
//
// That's...about all it does. It just waits for a removal request from the Common Functions script
// and then acts upon it.

default
{
state_entry()
{

}
link_message (integer sender, integer num, string msg, key id)
{
if (num == 10000) // remove inventory
{

llOwnerSay ("Removing objects...") ;
// Remove objects, no special logic here, just get rid of 'em all
integer i ;
string objects ;
integer objlen = llGetInventoryNumber (INVENTORY_OBJECT) ;
for (i = 0; i < llGetInventoryNumber (INVENTORY_OBJECT); i++)
objects += llGetInventoryName (INVENTORY_OBJECT, i) + "&" ;

// Remove objects
integer index ;
for (i = 0; i < objlen; i++)
{
index = llSubStringIndex (objects, "&") ;
string name = llGetSubString (objects, 0, index - 1) ;
objects = llDeleteSubString (objects, 0, index);

llRemoveInventory (name) ;
}

string notecards ;
llOwnerSay ("Removing notecards...") ;
integer notelen = llGetInventoryNumber (INVENTORY_NOTECARD) ;
// First build list of notecards
for (i = 0; i < llGetInventoryNumber (INVENTORY_NOTECARD); i++)
notecards += llGetInventoryName (INVENTORY_NOTECARD, i) + "&" ;

// Remove notecards
for (i = 0; i < notelen; i++)
{
index = llSubStringIndex (notecards, "&") ;
string name = llGetSubString (notecards, 0, index - 1) ;
notecards = llDeleteSubString (notecards, 0, index);

// Important: Make sure we don't remove our vendor config notecard!!! This would be very
// bad. If you change the name of the config notecard, be sure to modify this line too,
// otherwise you will have a WTF moment when you find it missing. :)
if (llSubStringIndex (llToLower (name), "mm svn vendor config") == -1)
llRemoveInventory (name) ;
}
llOwnerSay ("Inventory flush done.") ;
llMessageLinked (LINK_THIS, 10001, "", "") ; // Tell Common we're done
}
}
}


Vendor: SVN Texture Manager v1.01
CODE
// -[ Licensing and Copyright ]-----------------------
//
// Script copyright by Fenrir Reitveld, 02/10/06
//
// This program is free software; you can redistribute it and/or modify it under the terms of the
// GNU General Public License version 2 as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
// the GNU General Public License for more details.

// You should have received a copy of the GNU General Public License along with this program in a
// notecard; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
// Boston, MA 02110-1301, USA.
//
// GPL license version 2 on the web: http://www.gnu.org/copyleft/gpl.html (Note, URL might change
// once v3 is released.)

// -[ Script Purpose ]--------------------------------
//
// Short and sweet: This script simply handles removal of all items in the vendor's inventory.
//
// That's...about all it does. It just waits for a removal request from the Common Functions script
// and then acts upon it.
//
// This script needs to go in the prim that will be your texture display! While this CAN be the
// same prim where your inventory and other vendor scripts live, you can also make it different for
// large/complicated vendor designs.

// -[ User Config Section ]---------------------------
// Would you like a different standby texture? Then specify it here!
key STANDBY_TEXTURE = "d64323a7-d8e3-088f-d800-79161cfc8b09" ;
// Want to display the texture on a different side? Then change it here! Use ALL_SIDES to display
// on all sides.
integer SIDE = 1 ;

// -[ Core Program ]----------------------------------
//
// Everything below here isn't considered user-editable. Unless you want to change my code of
// course. :) Just be sure to adhere to the GPL if you do!

default
{
state_entry()
{
llSetTexture (STANDBY_TEXTURE, SIDE) ;
}
link_message (integer sender, integer num, string msg, key id)
{
if (num == 1000) // Request to set texture and make it visible
{
llSetTexture (id, SIDE) ;
llSetAlpha (1, SIDE) ;
}
if (num == 1001) // Request to clear texture to standby and set it invisible
{
llSetTexture (STANDBY_TEXTURE, SIDE) ;
llSetAlpha (0, SIDE) ;
}
}

}


Vendor: MM SVN Titler v1.00
CODE
// -[ Licensing and Copyright ]-----------------------
//
// Script copyright by Fenrir Reitveld, 02/10/06
//
// This program is free software; you can redistribute it and/or modify it under the terms of the
// GNU General Public License version 2 as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
// the GNU General Public License for more details.

// You should have received a copy of the GNU General Public License along with this program in a
// notecard; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
// Boston, MA 02110-1301, USA.
//
// GPL license version 2 on the web: http://www.gnu.org/copyleft/gpl.html (Note, URL might change
// once v3 is released.)

// -[ Script Purpose ]--------------------------------
//
// Not much to it. This script just sets the text above our vendor. If you want to have the
// floating text appear over a prim that is not the root prim (where the inventory and core
// vendor scripts live), then place this in the prim.
//

default
{
state_entry()
{
llSetText ("", ZERO_VECTOR, 0.0) ;
}
link_message (integer sender, integer num, string msg, key id)
{
if (num == 4500) // Set our title
{
vector color = (vector)((string)id) ;
llSetText (msg, color, 1.0) ;
}
}

}


MM SVN Unsitter v1.00
CODE
// -[ Licensing and Copyright ]-----------------------
//
// Script copyright by Fenrir Reitveld, 02/10/06
//
// This program is free software; you can redistribute it and/or modify it under the terms of the
// GNU General Public License version 2 as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
// the GNU General Public License for more details.

// You should have received a copy of the GNU General Public License along with this program in a
// notecard; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
// Boston, MA 02110-1301, USA.
//
// GPL license version 2 on the web: http://www.gnu.org/copyleft/gpl.html (Note, URL might change
// once v3 is released.)

// -[ Script Purpose ]--------------------------------
//
// All this does is prevent someone from sitting on the vendor. Because I have had problems with
// people sitting on my objects. *Long stare at a certain SL resident who wears a blue husky
// avatar*


default
{
state_entry()
{
llSitTarget(<0.0, 0.0, -0.1>, <0,0,0,1>);
}
changed(integer change)
{
if (change & CHANGED_LINK)
{
key agent = llAvatarOnSitTarget();
if (agent != NULL_KEY)
{
llSleep (0.2) ;
// ^_^
llSay (0, "I am not a chair!") ;
llUnSit(agent) ;
}
}
}
}


Vendor: (To go in holo items) MM SVN Holo Controller v1.01
CODE
// -[ Licensing and Copyright ]-----------------------
//
// Script copyright by Fenrir Reitveld, 02/10/06
//
// This program is free software; you can redistribute it and/or modify it under the terms of the
// GNU General Public License version 2 as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
// the GNU General Public License for more details.

// You should have received a copy of the GNU General Public License along with this program in a
// notecard; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
// Boston, MA 02110-1301, USA.
//
// GPL license version 2 on the web: http://www.gnu.org/copyleft/gpl.html (Note, URL might change
// once v3 is released.)

// -[ Script Purpose ]--------------------------------
//
// Holo rotation script. This script should be placed in your holo items. This script will make
// the holo item start to rotate, and make it listen for die commands so it will de-rez when
// appropriate.

// -[ User Config Section ]---------------------------
// Our rotation axis is what the axis the item will rotate around. If you have no idea what
// that means, just leave it as is. (Along Z-Up axis.)
vector rotation_axis = <0, 0, 1> ;

// "HEY MY ITEM IS UPSIDE-DOWN/SIDEWAYS/ETC!!"
// You can fix this by setting the initial rez angle. Due to the way the SVN vendor works, it will always
// rez the item at 0,0,0 rotation. You can override this below if you have some bizarrely made item with
// a root prim which doesn't face Z-Up.
//
// "Uh, what?" Okay, rez your holo item. Rotate it how you want it to appear. Go to the edit menu and
// then write down the X/Y/Z rotation numbers you see there. Plug them into the variable below, so that
// X is the first number, Y is the second, and Z is the last.
vector initial_angle = <0, 0, 0> ; // This sets the inital rotation angle (in DEGREES)

// Speed of rotation (in PI/s)
float speed = 0.5 ;

// -[ Core Program ]----------------------------------
//
// Everything below here isn't considered user-editable. Unless you want to change my code of
// course. :) Just be sure to adhere to the GPL if you do!

integer listenh ;

default
{
state_entry()
{
// This is kind of useless, as TEMP_ON_REZ is applied only when a prim rezzes but -- well.
llSetPrimitiveParams ([PRIM_TEMP_ON_REZ, TRUE]) ;
}

on_rez (integer p)
{
llSleep (0.1) ; // Sleep for a moment, as changing rotation immediately upon rez doesn't seem to be reliable
llSetRot (llEuler2Rot (initial_angle * DEG_TO_RAD)) ; // Set our initial rez angle
llTargetOmega (rotation_axis, speed, 1.0); // Start 'er spinning!
// Recreate our listen event upon rez
if (listenh)
llListenRemove (listenh) ;
listenh = llListen (p, "", "", "") ;
}

listen (integer chan, string name, key id, string msg)
{
// Time to die...
if (llGetOwnerKey (id) == llGetOwner() && msg == "die")
llDie() ;
}


}


Vendor Example Button (View): 2D-3D View Button
CODE
// -[ Licensing and Copyright ]-----------------------
//
// Script copyright by Fenrir Reitveld, 02/10/06
//
// This program is free software; you can redistribute it and/or modify it under the terms of the
// GNU General Public License version 2 as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
// the GNU General Public License for more details.

// You should have received a copy of the GNU General Public License along with this program in a
// notecard; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
// Boston, MA 02110-1301, USA.
//
// GPL license version 2 on the web: http://www.gnu.org/copyleft/gpl.html (Note, URL might change
// once v3 is released.)

default
{
state_entry()
{
llSetStatus(STATUS_BLOCK_GRAB, TRUE) ;
llSetAlpha (0, ALL_SIDES) ;
}

link_message (integer sender, integer num, string msg, key id)
{
if (num == 6004) // Message from Primary: We have a holo/3D item available
llSetAlpha (1, ALL_SIDES) ;
if (num == 6005) // Message from Primary: We do not have a holo/3D item
llSetAlpha (0, ALL_SIDES) ;
if (num == 6000) // Message from Primary: We are currently in 2D mode
llSetTexture ("85d6294e-f7e7-e8aa-dd3d-ca1720030752", 0) ; // Set button to a "View 3D" texture
if (num == 6001) // Message from Primary: We are currently in 3D mode
llSetTexture ("f77cb600-fefb-e1c3-42aa-003ac2dc6232", 0) ; // Set button to a "View 2D" texture
}
}


Vendor Example Button (Notecard): Give Notecard
CODE
// -[ Licensing and Copyright ]-----------------------
//
// Script copyright by Fenrir Reitveld, 02/10/06
//
// This program is free software; you can redistribute it and/or modify it under the terms of the
// GNU General Public License version 2 as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
// the GNU General Public License for more details.

// You should have received a copy of the GNU General Public License along with this program in a
// notecard; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
// Boston, MA 02110-1301, USA.
//
// GPL license version 2 on the web: http://www.gnu.org/copyleft/gpl.html (Note, URL might change
// once v3 is released.)

default
{
state_entry()
{
llSetStatus(STATUS_BLOCK_GRAB, TRUE) ;
}

link_message (integer sender, integer num, string msg, key id)
{
if (num == 6002) // Message from Primary: We have a notecard to give
llSetAlpha (1, ALL_SIDES) ;
if (num == 6003) // Message from Primary: We do not have a notecard...
llSetAlpha (0, ALL_SIDES) ;
}
}


JUST ONE MORE!
Fenrir Reitveld
Crazy? Don't mind if I do
Join date: 20 Apr 2005
Posts: 459
SVN System Part 5
03-24-2006 20:34
And finally, here is the master glue script for the vendor. It's big, so I had to split this up over two posts. X.X

Vendor: MM SVN Primary v1.08 (PART 1 of 2)
CODE
// -[ Licensing and Copyright ]-----------------------
//
// Script copyright by Fenrir Reitveld, 02/10/06
//
// This program is free software; you can redistribute it and/or modify it under the terms of the
// GNU General Public License version 2 as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
// the GNU General Public License for more details.

// You should have received a copy of the GNU General Public License along with this program in a
// notecard; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
// Boston, MA 02110-1301, USA.
//
// GPL license version 2 on the web: http://www.gnu.org/copyleft/gpl.html (Note, URL might change
// once v3 is released.)

// -[ Script Purpose ]--------------------------------
//
// This is the primary vendor controller script. All control and flow logic occurs in this
// script. Also, this script handles all of the user interface, both Dialog and Touch driven.
//
// Typical flow goes as such: Startup->Read Config Notecard->Read Categories Notecard->
// Read Category Items->Run state
//
// This script is probably pretty dang close to blowing up memory limits. I haven't actually
// checked it lately, but I wouldn't be surprised if you loaded up more than 10 categories,
// you might see it error out with my good ole friend, Stack Heap Collision. If so, then...
// Argh. Tired of working on this. One way around this would be to take out the UI handling
// code and place it seperate (perhaps in Common Functions). Things like menu generation and
// event handling could be condensed down into link messages being passed between the UI
// interface and the main program loop. (This is actually how RVN, SVN's bigger brother with
// true network capability, works.)
//
// Another area that could be broken out would be the vendor display code. There's no reason
// that updating the texture display and dealing with holo items couldn't be handled by a sub-
// module.
//
// Anyway; I'll leave that to someone else. Ten or so categories is PLENTY for me.
//
// This vendor is as low lag as I can make it. It only opens chat channels when it absolutely
// needs to, such as when a menu is brought up a by a user. It will close them as soon as it
// is done. While this vendor consists of a lot of scripts, most of them just sit and consume
// a slight amount of sim memory. This vendor doesn't phone home or otherwise try to talk to
// the outside world (except when it has an outgoing email request).

// -[ User Config Section ]---------------------------
// Change this if you want to be 100% secure, though the vendor does do owner checks
integer MENUCHAN = -294124 ;
// It's not actually necessary to change the COMMANDCHAN here, as this is handled as a notecard
// configuration item. This is just the default channel in case no options is specificed in
// the notecard.
integer COMMANDCHAN = -2820004 ;
// Set this to your standby texture. You are free to use this one, however.
key STANDBY_TEXTURE = "d64323a7-d8e3-088f-d800-79161cfc8b09" ;

// -[ Changelog ]----------------------------------

// * Baron Hauptmann fixed a problem with the named button not giving a notecard upon click.

// -[ Core Program ]----------------------------------
//
// Everything below here isn't considered user-editable. Unless you want to change my code of
// course. :) Just be sure to adhere to the GPL if you do!

// category/item state information
integer currcategory ;
integer curritem ;
integer numcategories ;
integer numitems ;

// Current item/category information (returned from database)
string currcategoryname = "None" ;
string curritemname = "None" ;
integer curritemprice = 0 ;
key curritemtex = NULL_KEY ;
integer curritemholo ;
integer curritemnote ;

key requestor ;
integer menuactivate ;
integer viewmode3d ;
integer uniqueid ;

// databases used in this script
string categories ;

// config parameters (as returned from Common Functions)
string email_address ;
vector holo_offset ;
string notecard_sound ;
string purchase_sound ;
string help_message ;
string purchase_message ;

// misc globals, mostly state trackers between states (heh)
integer listenh ;
integer ERROR ;
integer flushing ;
string notecardname ;
integer line ;
key queryhandle ;
string popupkeys ;
integer popups ;
integer runmode = TRUE ;
string error_msg ;

// START OF FUNCTIONS

// do_menu: Purpose is to push a menu to the user "agent", of menu type "menu"
do_menu (key agent, integer menu)
{
if (menu == 0) // top master menu
{
string str = "Use the << Item / Item >> buttons to view items.\nUse the << Category / Category >> buttons to change item categories.\n\nCategories will list all categories available." ;
list buttons ;
if (agent == llGetOwner())
{
buttons += [ "Admin" ] ;
} else
buttons += [ " " ] ;
buttons += [ "HELP!", "Categories" ];
buttons += [ "<< Category" ] ;
if (curritemholo) // if we have a holo available, make the options for switching views available
{
if (viewmode3d)
buttons += [ "2D View" ] ;
else
buttons += [ "3D View" ] ;
} else
buttons += [ " " ] ;
buttons += [ "Category >>", "<< Item" ] ;
if (curritemnote) // As has holo, if we have notecard, give option to retrive it
buttons += [ "Item Info" ] ;
else
buttons += [ " " ] ;
buttons += [ "Item >>" ] ;
llDialog (agent, str, buttons, MENUCHAN) ;
}
if (menu == 1) // admin menu
{
string str = "Admin functions." ;
list buttons = [ "Reset", "Update", "Empty Inv" ] ;
llDialog (agent, str, buttons, MENUCHAN) ;
}
llResetTime() ;
}

// prev_category: Moves to previous category and updates vendor display as appropriate
prev_category ()
{
currcategory-- ;
if (currcategory < 1)
currcategory = numcategories ;
set_category () ;
}

// prev_category: Moves to next category and updates vendor display as appropriate
next_category ()
{
currcategory++ ;
if (currcategory > numcategories)
currcategory = 1 ;
set_category () ;
}

// prev_category: Moves to previous item and updates vendor display as appropriate
prev_item (key id)
{
llMessageLinked (LINK_SET, 4500, "Just a moment...", "<0, 1, 0>") ;
llWhisper (uniqueid, "die") ; // Kill our holo items
viewmode3d = 0 ; // Reset 3D mode
llMessageLinked (LINK_SET, 6000, "", "") ; // tell buttons are going to 2D mode
llMessageLinked (LINK_SET, 6003, "", "") ; // tell buttons we have no notecard
llSetPayPrice (PAY_HIDE, [ PAY_HIDE, PAY_HIDE, PAY_HIDE, PAY_HIDE ]) ;
curritem-- ;
if (curritem < 0)
curritem = numitems - 1 ;
// Now, let's ask the associated category database script to find this item for us
// When it finds it, it will return the data. Only then will the actual vendor display be updated
// Until then, the user gets a "Just a moment..." message.
llMessageLinked (LINK_THIS, 2001, (string)currcategory + "|" + (string)curritem, id) ;
}

// prev_category: Moves to next item and updates vendor display as appropriate
next_item (key id)
{
llMessageLinked (LINK_SET, 4500, "Just a moment...", "<0, 1, 0>") ;
llWhisper (uniqueid, "die") ; // Kill our holo items
viewmode3d = 0 ; // Reset 3D mode
llMessageLinked (LINK_SET, 6000, "", "") ; // tell buttons are going to 2D mode
llMessageLinked (LINK_SET, 6003, "", "") ; // tell buttons we have no notecard
llMessageLinked (LINK_SET, 6005, "", "") ; // tell buttons we have no 3d view
llSetPayPrice (PAY_HIDE, [ PAY_HIDE, PAY_HIDE, PAY_HIDE, PAY_HIDE ]) ;
curritem++ ;
if (curritem > numitems - 1)
curritem = 0 ;
// Now, let's ask the associated category database script to find this item for us
// When it finds it, it will return the data. Only then will the actual vendor display be updated
// Until then, the user gets a "Just a moment..." message.
llMessageLinked (LINK_THIS, 2001, (string)currcategory + "|" + (string)curritem, id) ;
}

// set_category: Find our current category from category database, then update the vendor display
set_category()
{
llMessageLinked (LINK_SET, 4500, "Just a moment...", "<0, 1, 0>") ;
llSetPayPrice (PAY_HIDE, [ PAY_HIDE, PAY_HIDE, PAY_HIDE, PAY_HIDE ]) ;
llMessageLinked (LINK_SET, 6000, "", "") ; // tell buttons are going to 2D mode
llMessageLinked (LINK_SET, 6003, "", "") ; // tell buttons we have no notecard
llMessageLinked (LINK_SET, 6005, "", "") ; // tell buttons we have no 3d view
integer i ;
// Look for our particular category
// This could use a little optimization, but the memory lost likely wouldn't be worth it
for (i = 0; i < numcategories; i++)
{
integer index = llSubStringIndex (categories, "&") ;
string parsed = llGetSubString (categories, 0, index - 1) ;
categories = llDeleteSubString (categories, 0, index);
categories += parsed + "&" ;
if (i == currcategory - 1)
{
list iteml = llParseString2List (parsed, ["|"], []) ;
currcategoryname = llList2String (iteml, 1) ; // Set our current category name
}
}
}

// set_item: Updates the item display functions on the vendor to display currently selected item
set_item()
{
// Now that we have the item data, we can actually update the vendor display.
llSetPayPrice (PAY_HIDE, [ PAY_HIDE, PAY_HIDE, PAY_HIDE, PAY_HIDE ]) ;
if (numitems == 0)
{
// oops, no items in category
curritemname = "None" ;
curritemprice = -1 ;
curritemtex = STANDBY_TEXTURE ; // Set to standby texture
curritemholo = FALSE ;
llMessageLinked (LINK_SET, 1000, "", curritemtex) ;
llMessageLinked (LINK_SET, 4500, "No items in this category!", "<0, 1, 1>") ;
llSetPayPrice (PAY_HIDE, [ PAY_HIDE, PAY_HIDE, PAY_HIDE, PAY_HIDE ]) ;
return ;
}
if (curritemnote)
llMessageLinked (LINK_SET, 6002, "", "") ; // tell buttons we have a notecard
if (curritemholo)
llMessageLinked (LINK_SET, 6004, "", "") ; // tell buttons we have 3d view
llMessageLinked (LINK_SET, 1000, "", curritemtex) ;
llSetPayPrice (PAY_HIDE, [ curritemprice, PAY_HIDE, PAY_HIDE, PAY_HIDE ]) ;
llMessageLinked (LINK_SET, 4500, "-[ " + currcategoryname + " " + (string)(currcategory) + " of " + (string)numcategories + " ]-\n" + curritemname + " (" + (string)(curritem + 1) + " of " + (string)numitems + ")\n" + (string)curritemprice + " L$", "<0, 1, 1>") ;
}

// add_popup_key: Adds the current agent to a pool of agents that need to be shown a menu
integer add_popup_key (key agent)
{
// Okay, let's add the agent's key to our menu popup stack, so whenever we finish our current operation,
// we give them a menu. This will not add the key twice, to minimize menu spam.
//
// This isn't perfect, due to the way SL and llDialog()s are stateless -- Like HTML, we have no idea if the
// user opened 20 browsers, and then closed all but one or still have all open and then plan on clicking on
// a bunch of buttons at once.
//
// This method AT LEAST prevents the user from getting repeatedly sent menus if they click on the vendor
// when it's busy (such as switching categories or items)
integer found ;
integer i ;
for (i = 0; i < popups; i++)
{
integer index = llSubStringIndex (popupkeys, "&") ;
string parsed = llGetSubString (popupkeys, 0, index - 1) ;
popupkeys = llDeleteSubString (popupkeys, 0, index);
popupkeys += parsed + "&" ;
if (agent == (key)parsed)
return FALSE ;
}
if (!found)
{
popups++ ;
popupkeys += (string)agent + "&" ;
return TRUE ;
}
return FALSE ;

}

// do_rez: Rez our holo item and tell the vendor buttons what state we are in
do_rez ()
{
// rez item at current pos
vector pos = llGetPos () + (holo_offset * llGetRot()) ;
llMessageLinked (LINK_SET, 1001, "", "") ; // tell texture display to go blank
llMessageLinked (LINK_SET, 6001, "", "") ; // tell buttons are going to 3D mode
// We always rez at ZERO_ROTATION
// We also pass our uniqueid to the holo item controller script. This will be the chat channel used
// to issue the "die" command when it's time to de-rez the holo item.
llRezObject (curritemname + " Holo", pos, ZERO_VECTOR, ZERO_ROTATION, uniqueid) ;
}

// END OF FUNCTIONS


// ----------------------------------------------
// Beginning load (default) state of script
// Purpose: Waits for "MM SVN Config" to come online with configuration data, and then reads it
// ----------------------------------------------
default
{
state_entry()
{
llSetStatus(STATUS_BLOCK_GRAB, TRUE) ;
llMessageLinked (LINK_SET, 4500, "Just a moment...\nStarting up!", "<1, 0, 0>") ;
llMessageLinked (LINK_SET, 600000, "", "") ; // Tell all prims we are restarting
llMessageLinked (LINK_THIS, 1201, "", "") ; // Ask config module to restart
llSetTimerEvent (3) ; // in 3 seconds, let's ask for config
}
link_message (integer sender, integer num, string msg, key id)
{
if (num == 1300) // config data reply from config module
{
// Load everything
list data = llParseStringKeepNulls (msg, ["|"], []) ;
email_address = llList2String (data, 0) ;
holo_offset = (vector)llList2String (data, 1) ;
notecard_sound = llList2String (data, 2) ;
purchase_sound = llList2String (data, 3) ;
help_message = llList2String (data, 4) ;
purchase_message = llList2String (data, 5) ;
COMMANDCHAN = llList2Integer (data, 6) ;
state initialstart ;
}
}

timer ()
{
llMessageLinked (LINK_THIS, 1200, "", "") ; // Ask config module for configuration
}

}

// ----------------------------------------------
// Initial start of vendor
// Purpose: Loads categories from the "Categories" notecard
// ----------------------------------------------
state initialstart
{
state_entry()
{
// First, determine our unique ID (chat channel, really) that we will use to talk to our holo items
// This information is passed upon rez of the holo item.
uniqueid = (integer) (llFrand (10000) + llGetWallclock () + 2000) ;
// let's read category notecard
line = 0 ;
numcategories = 0 ;
llMessageLinked (LINK_SET, 1000, "", STANDBY_TEXTURE) ; // Set texture to standby
llMessageLinked (LINK_SET, 4500, "Just a moment...\nLoading categories!", "<1, 0, 0>") ;
// Check to make sure we have that notecard in inventory
notecardname = "Categories" ;
if (llGetInventoryType (notecardname) == INVENTORY_NONE)
{
llOwnerSay ("We're missing our category list notecard: " + notecardname) ;
error_msg = "SYSTEM ERROR: Missing category notecard\nTouch to restart." ;
state inactive ;
}
queryhandle = llGetNotecardLine(notecardname, line);
line++ ;
}
changed (integer change)
{
if (change & CHANGED_OWNER)
llResetScript() ;
}
dataserver(key query_id, string data) {
if (query_id == queryhandle) {
if (data != EOF) { // not at the end of the notecard
// first, is it a comment?
if (llGetSubString (data, 0, 0) != "#" && llStringLength (data) > 0)
{
list parsed = llParseString2List (data, ["|"], []) ;
numcategories++ ;
categories += data + "&" ;
if (llGetFreeMemory() < 1024)
llOwnerSay (notecardname + " has too many items!") ;
}
queryhandle = llGetNotecardLine(notecardname, line);
line++;
} else {
curritem = 0 ; // Yes, I know item indexes are 0-based but categories are 1-based.
currcategory = 1 ; // Sorry, but this was due to a sudden design change in the way categories were being loaded.
set_category () ;
state read_items ;
}
}
}
}

// ----------------------------------------------
// Item reader
// Purpose: Instructs the seperate item DB script to load all inventory items
// ----------------------------------------------
state read_items
{
// The purpose of this state is actually greatly diminished. When SVN used to only load items from one notecard
// at a time, a large percentage of the active script processing in Primary would have been spent in this state,
// waiting for the item database to finish reading the notecard. Now that this has changed, we generally won't
// spend long waiting for the category databases to load.
//
// However, this state still IS necessary, as if a master reset will cause the categories to be reloaded, and this
// isn't an instanteous event. (Boy, it sure isn't...) So, we need to cool the primary script's heels until all
// categories are online.
//
// Anyway, once the vendor is all loaded and a category switch occurs, we are sent back to this state. But we
// don't spend long here, as we just send a DB request and then wait for it to come back.
state_entry()
{
llSetPayPrice (PAY_HIDE, [ PAY_HIDE, PAY_HIDE, PAY_HIDE, PAY_HIDE ]) ;
llWhisper (uniqueid, "die") ;
llMessageLinked (LINK_SET, 4500, "Just a moment...\nReading items for " + currcategoryname + ".", "<1, 0, 0>") ;
llMessageLinked (LINK_SET, 1000, "", STANDBY_TEXTURE) ; // Set texture to standby
llMessageLinked (LINK_SET, 6000, "", "") ; // tell buttons are going to 2D mode
llSetTimerEvent (10.0) ;
llResetTime() ;
llMessageLinked (LINK_THIS, 2000, (string)currcategory, currcategoryname) ;
curritem = 0 ; // Reset back to first item
llListen(MENUCHAN, "", "", "") ;
}
changed (integer change)
{
if (change & CHANGED_OWNER)
llResetScript() ;
}
touch_start (integer num)
{
integer i ;
for (i = 0; i < num; i++)
// Make sure impatient people get their turn. ;)
if (add_popup_key (llDetectedKey(i)))
llWhisper (0, "Just a moment " + llDetectedName (i) + ", I am loading items.") ;
}
timer ()
{
// Retry the request to the inventory database...
llMessageLinked (LINK_THIS, 2000, (string)currcategory, currcategoryname) ;
}
listen (integer chan, string name, key id, string in)
{
// A user hasn't dismissed a menu from earlier, and tries to issue a command while we are loading
llWhisper (0, "Ooops! You caught me while I am loading items, " + name + ". Try again in a moment.") ;
}
link_message (integer sender, integer num, string msg, key id)
{
if (num == 3000) // inv load done
{
numitems = (integer)msg ;
// lookup first item
llMessageLinked (LINK_THIS, 2001, (string)currcategory + "|" + (string)curritem, requestor) ;
requestor = NULL_KEY ; // clear out last requestor
state running ;
}
if (num == 9999)
{
error_msg = "SYSTEM ERROR: Inv load failed\nTouch to restart." ;
state inactive ;
}
}
}

// ----------------------------------------------
// Primary running loop
// Purpose: The vendor is in run mode and accepts user input now
// ----------------------------------------------
state running
{
state_entry()
{
listenh = 0 ;
viewmode3d = 0 ;
llSetTimerEvent (0) ;
llWhisper (uniqueid, "die") ; // Tell any linger holos ti die off

// popup requests have queued up while we were switching categories
integer i ;
for (i = 0; i < popups; i++)
{
integer index = llSubStringIndex (popupkeys, "&") ;
string parsed = llGetSubString (popupkeys, 0, index - 1) ;
popupkeys = llDeleteSubString (popupkeys, 0, index);
do_menu ((key)parsed, 0) ; // Give the user their menu
menuactivate = TRUE ;
}
popups = 0 ;
popupkeys = "" ;
if (menuactivate)
{
// Start the menu listen
menuactivate = FALSE ;
if (!listenh)
{
listenh = llListen(MENUCHAN, "", "", "") ;
llSetTimerEvent (30) ;
}
llResetTime() ;
}
}
state_exit ()
{
// Keep our global state consistant by clearing out listen handle, as listens are removed on state changes
listenh = 0 ;
}

touch_start(integer total)
{
integer i ;
for (i = 0; i < total; i++)
{
// Originally, I used scripts with link msgs, but really this is somewhat inefficient considering SL allocates
// a full 16k for each script, even if they just contain one line. So, modified it to use "scriptless" buttons,
// however left in msg parsing just in case you want to do something odd...
if (llToLower (llGetLinkName (llDetectedLinkNumber(i))) == "prev item")
llMessageLinked (LINK_THIS, 5005, "", "") ;
else if (llToLower (llGetLinkName (llDetectedLinkNumber(i))) == "next item")
llMessageLinked (LINK_THIS, 5006, "", "") ;
else if (llToLower (llGetLinkName (llDetectedLinkNumber(i))) == "prev cat")
llMessageLinked (LINK_THIS, 5003, "", "") ;
else if (llToLower (llGetLinkName (llDetectedLinkNumber(i))) == "next cat")
llMessageLinked (LINK_THIS, 5004, "", "") ;
else if (llToLower (llGetLinkName (llDetectedLinkNumber(i))) == "notecard")
llMessageLinked (LINK_THIS, 5002, "", llDetectedKey(i)) ;
else if (llToLower (llGetLinkName (llDetectedLinkNumber(i))) == "view")
llMessageLinked (LINK_THIS, 5001, "", "") ;
else if (llToLower (llGetSubString (llGetLinkName (llDetectedLinkNumber (i)), 0, 3)) == "cat ")
{
// We are switching to a direct category, so...
// grab the actual category # from description
llMessageLinked (LINK_THIS, 5000, llGetSubString (llGetLinkName (llDetectedLinkNumber (i)), 4, -1), "") ;
}
else
{
if (listenh)
llListenRemove (listenh) ;
listenh = llListen(MENUCHAN, "", "", "") ;
llSetTimerEvent (30) ;
do_menu (llDetectedKey(i), 0) ;
}
}
}
>>> CONTINUED
Fenrir Reitveld
Crazy? Don't mind if I do
Join date: 20 Apr 2005
Posts: 459
SVN System Part 6 (last one, honest!)
03-24-2006 20:35
Vendor: MM SVN Primary v1.08 (PART 2 of 2)
CODE
    
money (key agent, integer amount)
{
if(amount != curritemprice)
{
// Log the attempt
llSay(0,"Ooops! The correct price is L$" + (string)curritemprice + ". I've send an email to my owner, " + llKey2Name (agent) + "!");
llMessageLinked (LINK_THIS, 0, "mailer_send|" + email_address + "|[NEED REFUND] " + curritemname + "|Item: " + curritemname + "\nPurchaser " + llKey2Name (agent) + " attempted to pay for item with " + (string)amount + ", but item costs " + (string)curritemprice + "\nSuspect item was changed while they were buying. Please give them a refund.\n", "") ;
// If you want to give back a refund in case of improper amount, then enable the next command below
// Note: You need to also do llRequestPermissions (llGetOwner(), PERMISSIONS_DEBIT) ; to get the
// ability to hand back money from the script. Why isn't this enabled by default? I want my
// vendors to be able to do unattended reboots without worrying about things like debit permissions.

// Now, why would the user give us an improper amount? Well, due to the quasi-multithreading nature of
// SL, it IS possible that someone might click on the Next Item button WHILE someone else has the Pay
// dialog open. Since the Pay dialog doesn't get updated, they will pay the previous item's amount.

// I am too tired to see an easy way around this. :)

//llGiveMoney(giver,amount);
} else
{
llMessageLinked (LINK_THIS, 4000, purchase_message, agent) ;
if (purchase_sound != "")
llPlaySound (purchase_sound, 1.0) ;
// Give the person their item!
llGiveInventory (agent, curritemname) ;
// Send an email with the payment event
llMessageLinked (LINK_THIS, 0, "mailer_send|" + email_address + "|[PAY] " + curritemname + "|Item: " + curritemname + "\nPurchaser: " + llKey2Name (agent) + "\nAmount given: " + (string)amount, "") ;
}
}

link_message (integer sender, integer num, string msg, key id)
{
if (num == 3001) // inv lookup reply
{
// One of the inventory database scripts replied, so let's load the info
list data = llParseString2List (msg, ["|"], []) ;
curritem = llList2Integer (data, 0) ;
curritemname = llList2String (data, 1) ;
curritemprice = llList2Integer (data, 2) ;
curritemtex = (key)llList2String (data, 3) ;
curritemholo = llList2Integer (data, 4) ;
curritemnote = llList2Integer (data, 5) ;
set_item () ;
// Did we get passed back an ID? If so, that means this request has a person's key attached to it, so give
// them a menu.
if (id != "")
do_menu (id, 0) ;
}
if (num == 3002) // inv lookup fail
{
// Hmm. Should never happen... If you did your inventory consistancy checks BEFORE loading vendors.
curritemprice = -1 ;
set_item () ;
// Did we get passed back an ID? If so, that means this request has a person's key attached to it, so give
// them a menu.
if (id != "")
do_menu (id, 0) ;
return ;
}
if (num == 5000) // A category select button was used
{
currcategory = ((integer)msg) ;
set_category() ;
state read_items ;
}
if (num == 5001 && curritemholo) // A button was used to flip to 2D/3D view
{
if (!viewmode3d)
{
viewmode3d = TRUE ;
do_rez () ;
llMessageLinked (LINK_SET, 6001, "", "") ;
llSetTimerEvent (30) ; // Start timer to reset back to 2D mode
}
else
{
viewmode3d = FALSE ;
llWhisper (uniqueid, "die") ; // kill our holos
llMessageLinked (LINK_SET, 6000, "", "") ;
set_item () ;
}
return ;
}
if (num == 5002 && curritemnote) // A button was used for information
{
if (notecard_sound != "")
llPlaySound (notecard_sound, 0.65) ;
llGiveInventory (id, curritemname + " Info") ;
return ;
}
if (num == 5003) // prev cat button
{
prev_category () ;
state read_items ;
}
if (num == 5004) // next cat button
{
next_category () ;
state read_items ;
}
if (num == 5005) // prev item button
{
prev_item ("") ;
return ;
}
if (num == 5006) // next item button
{
next_item ("") ;
return ;
}
}
listen (integer chan, string name, key id, string in)
{
// reset listen timer
if (listenh)
llListenRemove (listenh) ;
listenh = llListen(MENUCHAN, "", "", "") ;
llSetTimerEvent (30) ;

// first do category check, ie: We've been told to load a particular category #
list cmds = llParseString2List (in, [" "], []) ;
if (llList2String (cmds, 0) == "Cat")
{
currcategory = (integer)(llDeleteSubString (llList2String (cmds, 1), 0, 0)) ;
set_category() ;
requestor = id ;
menuactivate = TRUE ;
state read_items ;
}
cmds = llParseString2List (in, ["|"], []) ;
string cmd = llList2String (cmds, 0) ;

if (cmd == "Admin" && id == llGetOwner()) // Request for admin menu
{
do_menu (id, 1) ;
return ;
}
if (cmd == "Reset" && id == llGetOwner()) // Request to reset script
{
llWhisper (uniqueid, "die") ; // Kill any holos, just to clean up
llResetScript() ;
}
if (cmd == "Update" && id == llGetOwner()) // Request to do an update event
{
llMessageLinked (LINK_THIS, 8000, "", "") ; // Let loader scripts know we want to start loading
error_msg = "Inv loading process started" ;
state inactive ;
}
if (cmd == "Empty Inv" && id == llGetOwner()) // Request to clear out all of inventory
{
llMessageLinked (LINK_THIS, 10000, "", "") ; // Tell the inventory loader to flush it all
error_msg = "Inv flush started" ;
flushing = TRUE ;
state inactive ;
}
if (cmd == "3D View") // Request to go to 3D mode
{
viewmode3d = TRUE ;
do_rez () ;
return ;
}
if (cmd == "2D View") // Request to go to 2D mode
{
viewmode3d = FALSE ;
llWhisper (uniqueid, "die") ;
set_item () ;
return ;
}
if (cmd == "Item Info") // Request to view item notecard
{
if (notecard_sound != "")
llPlaySound (notecard_sound, 0.65) ;
llGiveInventory (id, curritemname + " Info") ;
return ;
}
if (cmd == "HELP!") // Request for help! (Denied?)
{
llMessageLinked (LINK_THIS, 4001, help_message, id) ;
return ;
}
if (cmd == "<< Category") // Request to load previous category
{
prev_category () ;
requestor = id ;
menuactivate = TRUE ;
state read_items ;
}
if (cmd == "Category >>") // Request to load next category
{
next_category () ;
requestor = id ;
menuactivate = TRUE ;
state read_items ;
}
if (cmd == "<< Item") // Request to load previous item
{
prev_item (id) ;
return ;
}
if (cmd == "Item >>") // Request to load next item
{
next_item (id) ;
return ;
}
if (cmd == "Main Menu") // Request to return to main menu
{
do_menu (id, 0) ;
return ;
}
if (cmd == "Categories") // Request to switch to direct category #
{
// Okay, emit dialog with categories, and a next button
string out = "Categories:\n" ;
integer i ;
list buttons ;
// Cop out, this can only handle up to 12 categories, because that's all that can fit on a single llDialog entry
// If you want to be able to list more than twelve, that is considered an exercise best left to the user. :)
for (i = 0; i < numcategories; i++)
{
integer index = llSubStringIndex (categories, "&") ;
string parsed = llGetSubString (categories, 0, index - 1) ;
categories = llDeleteSubString (categories, 0, index);
categories += parsed + "&" ;
list iteml = llParseString2List (parsed, ["|"], []) ;
out += (string)(i + 1) + ". " + llList2String (iteml, 1) + "\n" ;
buttons += [ "Cat #" + (string)(i + 1) ] ;
}
llDialog (id, out, buttons, MENUCHAN) ;
}
}


timer ()
{
if (listenh)
llListenRemove (listenh) ;
listenh = 0 ;
llSetTimerEvent (0) ;
if (viewmode3d)
{
// Reset back to 2D mode after 30 seconds, just so someone doesn't walk up to the vendor and see it empty with
// a blank texture window because someone left it in 3D mode and the temp_on_rez holo item has disappeared...
viewmode3d = FALSE ;
llMessageLinked (LINK_SET, 6000, "", "") ; // tell buttons are going to 2D mode
llWhisper (uniqueid, "die") ;
set_item () ;
}
}
}

// ----------------------------------------------
// Inactive state
// Purpose: The vendor has errored out and will not longer accept user input. Also put in this state when doing inventory updates.
// ----------------------------------------------
state inactive
{
state_entry()
{
ERROR = TRUE ;
llListen (COMMANDCHAN, "", "", "") ;
llSetPayPrice (PAY_HIDE, [ PAY_HIDE, PAY_HIDE, PAY_HIDE, PAY_HIDE ]) ;
llMessageLinked (LINK_SET, 4500, error_msg, "<1, 0, 0>") ;
llMessageLinked (LINK_SET, 1000, "", STANDBY_TEXTURE) ; // Set to standby texture
}
touch_start (integer num)
{
if (llDetectedKey(0) == llGetOwner()) // If our owner, then reboot, just in case something hung
llResetScript() ;
}
link_message (integer sender, integer num, string msg, key id)
{
if (num == 3002) // inv check done
{
// Dead code, consistancy check now in Master Inventory Object
}
if (num == 8500) // Restart script requested
llResetScript() ;
if (num == 10001 && flushing) // Inv empty request done
llResetScript() ;
}
listen (integer chan, string name, key id, string in)
{
if (llGetOwnerKey (id) == llGetOwner())
{
list cmds = llParseString2List (in, ["|"], []) ;
if (llList2String (cmds, 0) == "ril" && (key)llList2String (cmds, 1) == llGetKey())
llMessageLinked (LINK_THIS, 8000, "", "") ; // Well, we've been asked to do an inventory load, so tell the sub-script
}
}
}


Read the documentation for usage, configuration notecards, and setup! I highly recommend you either swing by my place and pick up a copy of the whole system with pre-configured vendors, just to get the hang of it without a ton of cut and pasting. :)
Nada Epoch
The Librarian
Join date: 4 Nov 2002
Posts: 1,423
Original Thread
03-24-2006 20:46
/15/8b/95865/1.html
_____________________
i've got nothing. ;)
Silje Russell
lsl geek
Join date: 2 Oct 2005
Posts: 63
03-27-2006 01:22
its nice done. and ya it can contains many objects.
But its reads from the notecard at every object change. (next and prev button events)
Somting i feel wud make lots of lag and becom bit unstabel at times.
Spec at laged placed..

But other then that i must say nice jobb :)
_____________________
Yes i know i got typos..
I got writing and reading problems.
but i am still a humen!!
Fenrir Reitveld
Crazy? Don't mind if I do
Join date: 20 Apr 2005
Posts: 459
03-27-2006 03:25
From: Silje Russell
its nice done. and ya it can contains many objects.
But its reads from the notecard at every object change. (next and prev button events)
Somting i feel wud make lots of lag and becom bit unstabel at times.
Spec at laged placed..

But other then that i must say nice jobb :)


Thanks, glad you like it.

But I am not sure what you mean about it loading items... The version before this one did read the items every time you switched categories.

That was just too slow in sims that didn't have a consistant time dilation of 1.0, so I scrapped the whole conservative memory system (that just used one inventory script) and switched it to one that reads items concurrently from all categories at startup. (That's what the Item Database vX.X Cat #1 thru Cat #Whatever scripts are for.)

After that, any next/prev category events just ask for that item data -- and then the appropriate database script honors the requestor.

The only time the vendor reads from notecards is when you reboot it by hand or it gets rebooted due to a sim crash. It then loads the notecards into the databases and its config notecard, then simply goes quiet and waits for touch events.

The email scheduler and dialog handling system keeps active events (a listen and a timer) open only as long as they absolutely have to. Other than that, my vendor simply consumes prim/script space, and no active queue processing time.
Baron Hauptmann
Just Designs / Scripter
Join date: 29 Oct 2005
Posts: 358
problem with notecards
05-20-2006 09:37
Not sure if anyone else has tried using this. Fenrir, kudos. I love this! But I've found a couple of minor problems.

1. Using your example vendor, people had a hard time figuring out why two sets of arros on the stand version. They did not change categories and complained that I did not have all my inventory in it. So I just put everying in one category and eliminated those arrows. If I chose to work on it some, they might understand better . . .

2. The biggest problem is that people don't get a notecard when they click on the button. I looked at the script, and as far as I can tell, the link_message in the primary script to send a notecard specifies an "id" which was not passed to it through llMessageLinked.

Under state running, touch_start event,
CODE

else if (llToLower (llGetLinkName (llDetectedLinkNumber(i))) == "notecard")
llMessageLinked (LINK_THIS, 5002, "", "") ;


should be . . .

CODE

else if (llToLower (llGetLinkName (llDetectedLinkNumber(i))) == "notecard")
llMessageLinked (LINK_THIS, 5002, "", llDetectedKey(i)) ;


Hope that helps anyone else using the vendor. Otherwise, like I said, it is great work, and thanks for making it free, too!!!!

Baron Hauptmann
Zodiakos Absolute
With a a dash of lemon.
Join date: 6 Jun 2005
Posts: 282
05-20-2006 20:08
It really is a nice vendor - I've tried it out with a few of my products, although I've decided that I want something a little different in the long run. I do like the idea of a 'quasi-networked' vendor... it has a few of the nice qualities of standard vendors (very, very few inventory problems, such as inventory not being sent by a server), as well as some of the nice qualities of 'networked' servers (easier inventory management, although admittedly, you still have to teleport to your vendors to do this with the SVN). Definitely a nice alternative to primitive standard vendors if you have the patience to read the manual. :D
Fenrir Reitveld
Crazy? Don't mind if I do
Join date: 20 Apr 2005
Posts: 459
06-12-2006 19:38
Wow, thanks for helping me by checking this sytem out. Sorry I didn't reply sooner on this. I gotta remember to check the forums more often. :)

From: Baron Hauptmann
1. Using your example vendor, people had a hard time figuring out why two sets of arros on the stand version. They did not change categories and complained that I did not have all my inventory in it. So I just put everying in one category and eliminated those arrows. If I chose to work on it some, they might understand better . . .

This can be a problem! My current vendor system has a row of buttons along the sides, which directly switches between the major categories. People seem to have no trouble with that. It's hard to work in those arrow-based category switching buttons, though.

Admittedly, I have side stepped the whole problem with the direct-to-category buttons...

From: Baron Hauptmann
2. The biggest problem is that people don't get a notecard when they click on the button. I looked at the script, and as far as I can tell, the link_message in the primary script to send a notecard specifies an "id" which was not passed to it through llMessageLinked.

Oooh! Nice catch! I added the named-button support in pretty much at the very end, and I appearently overlooked this.

The vendor that I primarily use this with does not used named-buttons for the notecard giving. It uses the script method as the vendor does other actions when you press that button. So I never noticed this on my primary testing vendor...
Gistya Eusebio
Registered User
Join date: 14 Mar 2006
Posts: 112
03-14-2007 11:43
From: Zodiakos Absolute
although admittedly, you still have to teleport to your vendors to do this with the SVN


Couldn't the SVN be easily modified to not require teleporting to reset the local vendors? I mean couldn't they have a reset command triggered by email at the times of updates easily added in?

- Gistya
Oliviero Ranimo
Registered User
Join date: 19 Jan 2009
Posts: 13
09-03-2009 09:15
Hi guys I know this is a bit of an old thread but I've been having a bit of trouble using the vendor and can't for the life of me figure out where I'm going wrong.

The problem I'm having is that when I try to get a notecard from the vendor it tells me that it is already in use, even though it is me that is using it. This also happens on the holo rezzer.

I don't really understand link messages unless they are reall really simple and this one is way over my head. I looked at the code mentioned above but mine seems to be somewhat different.

This is what I have:

else if (llToLower (llGetLinkName (llDetectedLinkNumber(0))) == "notecard";)
llMessageLinked (LINK_THIS, 5002, "", llDetectedKey(0)) ;

If anyone could help me out, I'd really appreciate it. Big props to you Fenrir for creating such a fantastic vendor and allowing us to use it for free. Everything else works like a dream.

Thanks again.