Moe's Mushkode Manual - Finishing a multiple-item vendor.

MUSHCode for Moe's Mushkode Manual - Finishing a multiple-item vendor.

~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*
|\ /| |\ /| |\ /|
| \ / | | \ / | | \ / |
| \/ |oe's | \/ |ushkode | \/ |anual
~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*

LESSON 5: PAY NO ATTENTION TO THE MAN BEHIND THE CURTAIN!

In our previous lesson, we dealt with the outline of a
multiple-item vendor. This built on what we had learned in Lessons 1, 2,
and 3, regarding how to make commands and approach coding problems. In
Lesson 4, we decided how we were going to store our data, how we were
going to set new items, and allowed ourselves the beginnings of a command
to edit the data we had set.
In this lesson, we're going to finish our multiple item vendor, by
finishing off all of the commands that we began in Lesson 4. You thought I
was nuts, leaving them all half-finished, but in truth, I really did know
what I was doing. I needed to show you all of the beginnings of those
commands, so that you would have a much better understanding of where we
were going and what we were doing.

LIST? WE DON'T NEED NO STEENKIN' LIST!

Actually, yes we do. We began making our list, and we set up the
theory for it, without the actual code. I told you in Lesson 4 that you
had something to look forward to, as regarded iter() parsing, and so here
it goes. We learned what #@ meant, which to remind you quickly is the
substitution in iter() that tells us what numerical member of our list
we're working on.
We're going to read the list of attributes on our vendor that
start with item- and turn that into a displayed list of items sold in our
shop. The lattr() function is what we shall use to do that. lattr(<obj>)
will return to us the full list of attributes on an object. However, it
also gives us the opportunity to specify a subset of attributes, by
providing a pattern to match. Yes, in case you haven't figured it out yet,
a hefty portion of how a mush works is based on pattern-matching.
lattr(<obj>/<attr-pattern>) is the syntax for matching only
certain attribute names from an object. For this application, the lattr()
is going to be run by our vendor, and looking for attributes that start
with the pattern item-. So, that code looks like:

lattr(me/item-*)

Yes, it really is that simple. This will return the list we want.
And we know we want to move through this list, so we will be using iter().
Each time we pass through the list, we want to take the value of the
attribute, display certain aspects of it, namely the fullname and the
cost, and the close off our display with a graphical footer that will
offset our graphical header we coded previously.
To recap, here is the code for our graphical header, and the way
our data is stored:

&header Vendor=[repeat(=,78)]%R|[center(%0,76)]|%R[repeat(=,78)]
&item-red~ball Vendor=10|red ball|A red ball

Remember that these are default values. Cost, fullname, and
description, respectively. Our item-edit command will edit these to more
descriptive values later. Now, let's fire up our code brains, and put
together our list command. I'll give you some code, and then we'll talk
our way through it.

&cmd_list Vendor=$list:@pemit %#=u(header,List of
Items)%R%b[ljust(Item,25)]Cost%R%b[ljust(----,25)]----%R[iter(lattr(me/item-*),%b[ljust(#@>
[extract(setr(0,v(##)),2,1,|)],25)][first(%q0,|)],,%r)]%R=[repeat(-,76)]=

Most of this should be comprehensible to you, particularly if you
have read the sections of Lesson 3 that deal with cosmetic coding. If you
need a refresher on those, I suggest you pause here, and re-read that
Lesson. For those of you that are ready to plow on ahead, here's a brief
recap of what that code does.
The command name is 'list', and we will pemit a display to the
user. Our first item is the graphical header, which puts our text 'List of
Items', in an ascii box. After that, we put some column headers, for Item
and Cost. We put a little divider line under those, and then we get down
to the listing of our items. We list the attributes on our vendor that
start with item-. For a nice display, we are numbering the display so that
it looks good, and we can incorporate this into our buying command later.
After that, we take the value of our attribute, set it to a
temporary memory register(%q0), and then extract the second element of the
|-separated list. This will be the fullname of the item. That text is
left-justified to a cell of 25 spaces. This can be increased or decreased
as necessary. After that bit of text has been justified, our cost is
displayed. The cost element of our data attribute is the first element in
the list, so we use the first() function. Note that we specified that
we're using the | delimiter. Our iter() will output with a %r carriage
return as the separator, which will put everything on its own line. After
our display, I add a very simple ascii footer to the end.
This is generic code that can be used in any vendor situation, and
easily modified to display a list of most anything. If you wanted to
personalize it further, you could add elements like the name of the
location to the header, since it will likely be your shop or restaraunt's
name. You can do ansi on the names, the costs, or in the header itself.

IT'S BROKEN! IT DOESN'T DISPLAY ANYTHING! YOU'RE LYING TO ME!

No, it's not broken. Yes, it displays precious little at the
moment, and no, I'm not lying to you. What's causing the difficulty is
that we haven't added our items to our vendor's memory yet. We'd decided
to sell a Bell, a Book, and a Candle, in the spirit of banishing mushkode
demons.
In our previous lesson, we had coded our item-add command, that
checked to see if the player using the command had privileges on our
vendor, and a few other tests. To refresh your memory, it wound up looking
something like this:

&cmd_itemadd Vendor=$item-add *:@pemit
%#=switch(u(fn-priv):[hasattr(me,setr(0,item-[u(space2tilde,%0)]))],0:*,You
don't have permission to do that.,*:1,There is already an item stored with
that name.,set(me,item-%q0:[v(default_value)]|%0|A %0)Your item has been
added. Use the item-edit command to change any of the values.)

&fn-priv Vendor=[or(orflags(%#,Wr),controls(%#,me))]

&space2tilde Vendor=[edit(%0,%b,~)]

To review any of the logic that went into building that code,
please see Lesson 4. With this code in place, let's use it to add our
Bell, Book, and Candle. The commands should go something like this:

item-add Bell
item-add Book
item-add Candle

Now that we have those, you should be able to use your 'list'
command, and get a display that looks something like this:

==============================================================================
| List of Items |
==============================================================================
Item Cost
---- ----
1> Bell 10
2> Book 10
3> Candle 10
=----------------------------------------------------------------------------=

Now, we need to work on the item-edit command a bit more. We did
leave it dangling about half-finished in our last lesson, but that was
because we needed to establish more than a small amount of theory before
we could go on. When last we left our item-edit code, it looked something
like this:

&cmd_itemedit Vendor=$item-edit *=*/*:@pemit
%#=switch(u(fn-priv):[hasattr(me,setr(0,item-[u(space2tilde,%0)]))]:[setr(1,memb
er(cost fullname description,lcstr(%1)))],0:*:*,You can't do that.,*:0:*,I
don't have that item.,*:*:0,You must specify one of the following
categories: Cost Fullname Description,<code to edit list>)

We'd discussed using match() vs. member(), but because we would
like to make the user be specific in what they enter, I suggest we leave
it with member() for now. Another case for using match() would be if we
wanted to do partial name-matching on the list of available items to edit.
The ability to do 'item-edit red=cost/20' instead of typing out 'red ball'
for the item's name. Again, this can cause some difficulties of multiple
matches, and perhaps we will explore how to handle that in a later lesson.
For now, let's keep it simple.
Again, if any of the logic or illogic of the previous code needs
explaining, please refer back to Lesson 4. For now, let's focus on editing
the list, assuming that all of the tests for validity that we established
above have been passed. The basic theory for editing the list is going to
go something like this:

1) Get the list from the attribute on the vendor.
2) Replace the old data with the new data.
3) Re-set the attribute on the vendor.
4) Provide a message of confirmation to the user that the task has been
done.

Overall, not too tough. We'll get to learn a new list function as
well. Let's start with these, in order. To begin with, we need to get the
list from the attribute. When I keep saying list, I mean the data that is
stored in our item-<name> attribute. It is a |-separated list. Everything
in mushcode is a list, remember? Rule #1! Everything can be viewed as a
list. So, this is the list to which I refer, frequently. Since this code
is all on our vendor, we can use v(), instead of get() or xget().
We can begin our <code> section with: setq(2,v(%q0)). This pulls our
space2tilde-edited attribute name into the v(), which gets the value of that
attribute, and then sets it into %q2, without displaying it. Now that we have
this data, we can begin to manipulate it. As I said above, step #2 is to
replace the old data with the new data. Believe it or not, there is a
replace() function that will let us do just that. And it is because of this
function that I made sure we set into a q-register the results of our
member() test at the beginning.
The replace() function works like this:
replace(<list>,<position>,<word>,[<delimiter>]). So, we have our list,
stored in %q2. We have our position, stored in %q1. And we have our new
data, as %2. We know our delimiter is |. The work is simplicity itself.
replace(%q2,%q1,%2,|) is all the code we need to put our new data into our
existing list. Now, step #3 of our code is to set this into the attribute
again, so that it can be used by our vendor. We will use the tried and
true set() function that we have used many times before. So that now, our
code is: set(me,%q0:[replace(%q2,%q1,%2,|)]). After that, we give a
confirmation message that we have set the new data, and we are done. Our
code becomes:

&cmd_itemedit Vendor=$item-edit *=*/*:@pemit
%#=switch(u(fn-priv):[hasattr(me,setr(0,item-[u(space2tilde,%0)]))]:[setr(1,memb
er(cost fullname description,lcstr(%1)))],0:*:*,You can't do that.,*:0:*,I
don't have that item.,*:*:0,You must specify one of the following
categories: Cost Fullname Description,setq(2,v(%q0))
[set(me,%q0:[replace(%q2,%q1,%2,|)])]The new [capstr(%1)] for %0 has
been set to: %2)

Yes, I beautified the text display to our user a bit, by
capitalizing the element we were editing, but that's purely personal
preference. Again, you could do it in flashing ansi, if you wanted(please
don't), or any other series of options. Many variations on code such as
this will display the old and the new data.

BUT WHAT IS IT??

I could spend some time with you, doing various edits on our Bell,
Book, and Candle that we have set, but I would like to move on. With the
command coded and working, it should be just fine and you can do with them
as you wish. For now, I want to move onto what should be the last two
'commands' we need to code. One of them is quite simple, and that is a
command to get a bit more info about each item on our list. We have the
Description element of our data, and we haven't used that yet.
So, let's make a 'study <item>' command. I choose this name
because look, examine, and view are all standard commands for mushes. view
being softcoded and often +view, but I want something that isn't going to
conflict with anything else on the game. The shop this vendor is in may
have views set, and we don't want to interrupt them.
This command will be really simple, and instead of going through
the step-by-step development, I shall show you the code, discuss the more
technical aspects of it, and then we shall move on to our last command,
the one that allows us to order an item from the vendor.

&cmd-study Vendor=$study *:@pemit
%#=ifelse(hasattr(me,setr(0,item-[u(space2tilde,%0)])),You study the
[extract(setr(1,v(%q0)),2,1,|)]:%R%r[last(%q1,|)]%RCost:
[first(%q1,|)],I'm sorry. There is no item by that name. Please type
'list' to see what is sold.)

This time, if the attribute exists, we tell them they're looking at
the item, giving the fullname. We then present the last element of the list,
which is our description. After that, we remind them of what it costs. If the
attribute does not exist, we kindly tell them they have entered incorrect
information, and direct them where to find the proper item name.

YOU WANT FRIES WITH THAT?

Okay, now the moment we've all been waiting for. No, I don't mean
when I'm done talking, I mean the point where we write the command to buy
an item from our vendor. In truth, we already know a fair amount of the
code we need to write this command. We need to test to see if we have the
item they're ordering. Then we need to set the cost for the item, and set
it up so that we have a temporary attribute which holds who the current
customer is, and what they have ordered. For the purposes of this vendor,
we will only serve one customer at a time. It's easier, and teaches us
some more about error-trapping in our code.
Something else that we will include here is that we gave ourselves
our listing by number, as well as by name. So, we should allow ourselves
the ability to purchase an item by name, or by number. This adds one more
test to our code, but is easily implemented.
We could name our command any number of things. Buy, Order,
Purchase, Select, Choose, or any other synonym of what we're trying to
achieve. I'm going to choose 'buy', again just because it's easiest. So,
our attribute will be named cmd-buy, and our command will have the syntax:
buy *. We will only have to test for two things before getting to the meat
of our code. Do we have the item, and are we serving a customer currently?

&cmd-buy Vendor=$buy *:@pemit
%#=ifelse(isnum(%0),u(do-number,%0),switch(hasattr(me,setr(0,item-[u(space2tilde,%0)])):[hasattr(me,current)],0:*,That
isn't an item that we sell here. Please type 'list' to see what is
available.,*:1,I'm sorry. I'm working on another order right now.,<code>))

This is the first half of our buy code. This checks to see if the
item specified was given by numerical value first. isnum() returns 1 or 0
if the data is numerical. This adds a ufun to our code, which we will
construct in just a moment. If it is not a number, we test to see if we
have the item, and checks to see if we have an attribute called current.
This will be set in the later part of this command when we establish that
our customer has selected a valid item and we aren't serving anyone else.
Like I said, we should save the information of who our customer is, and
what item they have ordered. Using set() again, we would do:

set(me,current:%#|%q0)

Yeah, pretty simple. This saves the dbref of our customer(just in
case. It's always better to work with dbrefs for identification purposes
than names), and the attribute name of the item they have selected. The
next thing we need to do is set the cost attribute on our vendor to be the
cost of the item. We will take the first value in the list, stored in the
attribute. This code is:

set(me,cost:[first(v(%q0),|)])

Again, a simple implementation of the first() and set() functions.
Now we are going to need to give some output to our customer, informing
them of the need to pay for the item. We could save the cost value in a
memory register, but it is just as simple to read it off of the Vendor
when it is needed.
Let's build our do-number ufun now. It will behave almost
identically to the text-only version of the command, but must be dealt
with separately. Within this, we need to test to see if the number they
entered is larger than the number of items we have on our list. If it is,
we must inform them of their incorrect choice, and if not, we must select
the proper item and do all of our appropriate code to it.

&do-number Vendor=[ifelse(gt(%0,words(setr(9,lattr(me/item-*)))),I'm
sorry. There aren't that many items for sale.,ifelse(hasattr(me,current),I'm
sorry. I'm working on another order right now.,u(sell-item,extract(%q9,%0,1))))]

&sell-item
Vendor=[set(me,current:%#|%0)][set(me,cost:[first(v(%0),|)])]Thank you for
your purchase. Please 'give [name(me)]=[v(cost)]' to get your item.

Now, why did I do two ufuns you ask? Well, look at the code that
is in the second ufun. This is code that we can use for the text-version
of our buy command as well. Why do it twice, when we can do it once, and
just pass the information to reusable code?
So, now our command looks like:

&cmd-buy Vendor=$buy *:@pemit
%#=ifelse(isnum(%0),u(do-number,%0),switch(hasattr(me,setr(0,item-[u(space2tilde,%0)])):[hasattr(me,current)],0:*,I'm
sorry. That isn't an item that we sell here. Please type 'list' to see what
is available.,*:1,I'm sorry. I'm working on another order right
now.,u(sell-item,%q0)))

You could, if you wanted to, go back and put the number
functionality into our study command, using very similar principles. I
leave this to you to do, on your own. It would work much the same way as
this buy command has.
Now, you'll notice that none of this code actually gives the item
to our customer. This is absolutely correct, and very astute on your part
for noticing. That is handled by the standard hardcoded methods for
vendors that I discussed at the beginning of Lesson 4, and is why we set
the cost attribute. When our customer pays our vendor the correct number
of coins, they will trigger both the @pay and @apay attributes, which will
provide the item needed. So, now we need to write those two attributes.

WHAT?? I THOUGHT WE WERE DONE?

Ha, tricked you. Well, not quite. We're done writing commands.
We're not done writing code. The @pay attribute is quite simple. It serves
as the message that the player receives when they have paid the amount of
pennies indicated in the @cost. We need to embed one small bit of code in
there to extract the fullname of the item they are buying. We don't even
need to do this, but it is a cosmetic and personal touch that some users
find worthwhile, and it gives us another opportunity to interact with our
current attribute. Within the @pay, though, is where we indicate to the
user that they should 'get' their item(@clone only puts a copy in the same
room as the cloner, not in the inventory), and @chown, and @set !halt.

@pay Vendor=Upon receiving your coin, [name(me)] sets
[setr(0,extract(v(last(v(current),|)),2,1,|))] on the counter. Please 'get
%q0', then '@chown %q0' and '@set %q0=!halt'. Thank you for shopping!

There is nothing here that we haven't seen before. The most
complex part is the v(last(v(current),|)). Remember that our current
attribute has two parts. The dbref and the attribute name of the item
ordered. We take the last part of that, after the |. This is the attribute
name. We put that within a v(), which returns the value of that attribute.
From that list, we extract the fullname.
Now on to the @apay. Remember back from the beginning of our
Vendor's construction, we created our Bell, Book, and Candle, and put the
items into the Vendor's inventory. This is important, because the Vendor
has to be able to see them in order to @clone them. All of the items,
including the vendor, have to be owned by the same PC to have privileges
to @clone things, as well.

@apay Vendor=@clone
edit(after(last(setr(0,v(current)),|),item-),~,%b);@emit name(me) puts
[name(first(%q0,|))]'s order on the counter.;@wipe me/current

Because we have taken care of reminding our customer to get,
chown, and unhalt their object in our @pay attribute, we don't need to do
it here. We use our stored dbref in the current attribute to personalize
an emit for the benefit of anyone else in the store. Then, we wipe the
current attribute so that we can serve another customer.

SUMMARY

We stored our data for our items, using the item-add command. We
personalized it, using the item-edit command. Both of these commands were
developed so that they could only be used by those with the privilege to
use them. From there, we developed a method by which we could track who
our current customer was, through a 'buy' command. This command was the
culmination of our multiple-item vendor, because it set the cost and
current item being sold. We have taken the functionality of MUSH's
hardcoded vendor code and expanded it. We're still selling one item at a
time, but we are able to select from a larger number of possible items.
Our vendor clones from a set of master objects in its inventory,
and prompts our user to take care of the ownership and flag maintenance on
the object. And in the end, they get what they wanted, and we get money.
What's better than that?
Lesson 6 is going to go a wholly different direction, and is going
to be about the development of an IC time system, using vector math. We'll
discuss a fair amount of division, in dividing up our time, and we'll
cover how to do both 2:1 and 3:1 timescales.