Money System, v1.4


Copy and paste the below code into a compatible MUSH or MUX.

MUSHCode for Money System, v1.4

@@ Money System, v1.4
@@ By Joshua Bell
@@ Credits: Tash @ NarniaMUSH (code)
@@ Onyx, Cor, Puddleglum, Fumblethumb @ NarniaMUSH (ideas)

@@ Revision History

@@ 0.5 - First scripted version

@@ 0.8 - Command stubs added
@@ - Decimal -> Text functions added
@@ - V-Register usage & documentation added
@@ - $PAY replaced w/ $GIVE
@@ - Help for MPAY fixed
@@ - NO_COMMAND on non-commands added

@@ 1.0 - Autoconfiguration (AUTO_CONF) added
@@ - Multiple money system support added
@@ - ** FIRST RELEASE **

@@ 1.1 - AUTO_CONF fixed
@@ - Major rewrite of subroutines
@@ - Semaphore rewrite to *double* speed
@@ - Stubs eleminated where useless
@@ - Value register (VV) added
@@ - &APAY is passed amount in %0
@@ - SUB_ERROR handler added
@@ - Functions made TinyMUSH-pure
@@ - Lots and lots of tiny bugfixes
@@ - GIVE-verb for machine synch added
@@ - &MCOST allows money strings
@@ - $COLLECT hole ($COLLECT me) patched

@@ 1.2 - $PAY ($GIVE alias) added
@@ - Negative amount cap for donations
@@ - $COLLECT hole patched more thoroughly
@@ - Patched to work with TinyMUSH 2.0.10p5

@@ 1.3 - Fixed message for taking (thanks to Silenus@Narnia)

@@ 1.4 - Can't buy from HALTED things (thanks to Valeria & Amberstar@Narnia)

@@ Initial Setup
@@ ~~~~~~~~~~~~~

@@ Create the object. Why ATM? Automated Teller Machine :)
@@ You'll want to put this in your Master Room.

@create ATM
@lock ATM=#0
&CREDITS ATM=By Joshua Bell <> aka Tash@NarniaMUSH

@@ We need to define an attribute, and then make it Wizard-only.

@@ NOTE: This may need to be run by your God(#1) character, depending on
@@ local permissions. The "hidden" parameter is up to you - it is polite
@@ to let people use their own cash in programs - money(me) becomes
@@ v(MONEY), for example.

@@ NOTE: If in the configuration section you change the attribute name,
@@ from MONEY to something else, you will have to manually edit these
@@ next three commands.

@@ @attribute/access MONEY=wizard
@@ @attribute/access MONEY=no_inherit
@@ @attribute/access MONEY=hidden

@@ Initialize semaphore protection on each startup, and immediately.

@Startup ATM=@drain me;@notify me
@drain ATM
@notify ATM

@@ Configuration Section
@@ ~~~~~~~~~~~~~~~~~~~~~

@@ Name of the money system. Might be "US Treasury", or something
@@ equally meaningless.

&SYSTEM_NAME ATM=Royal Narnian

@@ Name of the attribute to hold the money value. If you change this,
@@ look in the Initial Setup section, and change some of the commands
@@ listed there.


@@ Names of the attributes to hold the Cost, Pay, Opay and Apay messages:


@@ Names of the attribs to hold attributes called when giving money,
@@ for command synchronization.


@@ For the record, @VS is used to store the amount stolen, and @VL is
@@ used to store the result of a locate(), so they shouldn't be reused.
@@ @VV is used to store the value of a money string.
@@ The free register list (mostly for my benefit) is:

@@ Define the commands. These are the defaults. DO NOT CHANGE!


@@ Change *these* next ones if you want. Keep the order the same.
@@ Even if you don't want $STEAL used, leave it in the list. Keeping
@@ them upper case isn't vital (it doesn't matter to the users) but
@@ it shows up more clearly in the help.


@@ Define the coinage. The default is Narnian currency. The attributes
@@ are values, singular names, plural names, and abbreviations.


@@ Here's an example of how you'd define North American money,
@@ albeit taken to an extreme:

@@ &COIN_VLIST ATM=50 20 10 5 2 1 0.25 0.10 0.05 0.01
@@ &SCOIN_NAME ATM=Fifty Twenty Ten Five Two Dollar Quarter Dime Nickel Penny
@@ &PCOIN_NAME ATM=Fifties Twenties Tens Fives Twos Dollars Quarters Dimes
@@ Nickels Pennies
@@ &ACOIN_NAME ATM=. . . . . $ . . . c
@@ (The abbreviations don't work too well in this example.)

@@ Any comments about what the money is worth should go here.

&CONF_COMMENT ATM=A full meal, with drinks and dessert, costs about 5 Trees at the%r MouseTrap Tavern. Please set your costs appropriately.

@@ Do we want stealing? 1=Yes, 0=No


@@ Do a little bit of automatic configuration.


@@ Command Code
@@ ~~~~~~~~~~~~~
@@ Some of these are the full command code. Some are just stubs
@@ which call SUB_ attributes which do the dirty work.

@@ Help - no argument.

@@ Help, with one argument.
&CMD_HELP2 ATM=$$HELP *:@switch match(v(LIST_TOPICS),%0*)=0,{@pemit %#=u(HELP_MONEY)},{@pemit %#=u(HELP_[extract(v(LIST_TOPICS),match(v(LIST_TOPICS),%0*),1)])}

@@ Display the coinage table for this ATM
&CMD_COINAGE ATM=$$COINAGE:@pemit %#=[MUDname()] - [v(SYSTEM_NAME)] Coinage:%r[space(5)][ljust(Coin:,12)][ljust(Plural:,12)][ljust(Abbrev:,12)][ljust(Value:,12)][iter(iter(lnum(words(v(COIN_VLIST))),add(##,1)),%r[space(7)][ljust(extract(v(SCOIN_NAME),##,1),12)][ljust(extract(v(PCOIN_NAME),##,1),12)][ljust(extract(v(ACOIN_NAME),##,1),12)][ljust(u(FN_FRACT,extract(v(COIN_VLIST),##,1)),12)])]%r%r[u(CONF_COMMENT)]

@@ Report the amount of money you have.
&CMD_CASH ATM=$$CASH:@switch/first 1=eq(get(%#/%vm),0),{@pemit %#=You have no [v(SYSTEM_NAME)] currency!},{@pemit %#=You have [u(FN_MONEYNAME,get(%#/%vm))].}

@@ Report the amount of money something else has.
&CMD_CASH2 ATM=$$CASH *:@wait me={@VL me=locate(%#,%0,*);@switch/first 1=strmatch(%vl,#-1),{@pemit %#=I don't see that here.},strmatch(%vl,#-2),{@pemit %#=Which "%0" do you mean?},eq(controls(%#,%vl),0),{@pemit %#=You do not control that!},eq(get(%vl/%vm),0),{@pemit %#=[name(%vl)] has no [v(SYSTEM_NAME)] currency.},{@pemit %#=[name(%vl)] has [u(FN_MONEYNAME,get(%vl/%vm))].};@notify me}

@@ Deposit money on an object you control.
&CMD_DEPOSIT ATM=$$DEPOSIT *=*:@wait me={@VL me=locate(%#,%0,*);@VV me=u(FN_VALUE,%1);@switch/first 1=strmatch(%vl,#-1),{@pemit %#=I don't see that here.},strmatch(%vl,#-2),{@pemit %#=Which "%0" do you mean?},eq(controls(%#,%vl),0),{@pemit %#=You do not control that!},lt(%vv,0),{@pemit %#=You can't deposit a negative amount of money.},eq(%vv,0),{@pemit %#=You must specify a positive amount.},eq(get(%#/%vm),0),{@pemit %#=You have no [v(SYSTEM_NAME)] currency to deposit.},gt(%vv,get(%#/%vm)),{@pemit %#=Thats more money than you have.},{@pemit %#=You deposit [u(FN_MONEYNAME,%vv)] in [name(%vl)].;&%vm %#=sub(get(%#/%vm),%vv);&%vm %vl=add(get(%vl/%vm),%vv);@pemit %#=You now have [u(FN_MONEYNAME,get(%#/%vm))].};@notify me}

@@ Steal money from another player.
&CMD_STEAL ATM=$$STEAL *:@wait me={@VL me=locate(%#,%0,nP);@switch/first 1=not(v(CONF_STEALING)),{@pemit %#=Permission denied.},strmatch(%vl,#-1),{@pemit %#=I don't see that here.},strmatch(%vl,#-2),{@pemit %#=Which "%0" do you mean?},not(strmatch(type(%vl),PLAYER)),{@pemit %#=Thats not a player!},hasflag(%#,HAVEN),{@pemit %#=You are set HAVEN.},hasflag(%vl,HAVEN),{@pemit %#=That player is set HAVEN.},not(hasflag(%vl,CONNECTED)),{@pemit %#=That player is not connected.},eq(get(%vl/%vm),0),{@pemit %#=You try and steal money from [name(%vl)], but can't find any to take!},gt(rand(100),35),{@pemit %#=You try and steal money from [name(%vl)], but bungle the attempt.;@pemit switch(1,gt(rand(100),40),%vl,me)=[name(%#)] tries to steal money from you, but bungles in the attempt!},{@VS me=mul(rand(trunc(fdiv(get(%vl/%vm),first(revwords(v(COIN_VLIST)))))),first(revwords(v(COIN_VLIST))));@pemit %#=You manage to steal [u(FN_MONEYNAME,%vs)] from [name(%vl)].;&%vm %#=add(get(%#/%vm),%vs);&%vm %vl=sub(get(%vl/%vm),%vs);@pemit %#=You now have [u(FN_MONEYNAME,get(%#/%vm))].};@notify me}

@@ Collect money, with either one argument or two.
&CMD_COLLECT ATM=$$COLLECT *:@VL me=locate(%#,before(%0,=),*);@trigger me/SUB_[switch(1,strmatch(%vl,#-*),ERROR,strmatch(%vl,%#),ERROR,strmatch(%0,*=*),COLLECT2,COLLECT)]=%#,%vl,u(FN_VALUE,after(%0,=))

@@ Give money to almost anything.
&CMD_GIVE ATM=$$GIVE *:@VL me=locate(%#,before(%0,=),*);@trigger me/SUB_[switch(1,strmatch(%vl,#-*),ERROR,not(strmatch(%0,*=*)),GIVE,strmatch(type(%vl),PLAYER),GIVE3,strmatch(type(%vl),THING),GIVE2,ERROR)]=%#,%vl,u(FN_VALUE,after(%0,=))

@@ Give money to almost anything - just an alias.
&CMD_PAY ATM=$$PAY *:@VL me=locate(%#,before(%0,=),*);@trigger me/SUB_[switch(1,strmatch(%vl,#-*),ERROR,not(strmatch(%0,*=*)),GIVE,strmatch(type(%vl),PLAYER),GIVE3,strmatch(type(%vl),THING),GIVE2,ERROR)]=%#,%vl,u(FN_VALUE,after(%0,=))

@@ Buy from a vendor.
&CMD_BUY ATM=$$BUY *=*:@VL me=locate(%#,%0,*);@trigger me/SUB_[switch(1,strmatch(%vl,#-*),ERROR,not(strmatch(type(%vl),THING)),ERROR,not(strmatch(%1,*\,*)),BUY,BUY2)]=%#,%vl,before(%1,\,),u(FN_VALUE,after(%1,\,))

@@ Help Entries
@@ ~~~~~~~~~~~~
@@ Its best to leave these on the same object - they refer in places to
@@ configuration settings, and you'd have to copy those over as well.

&HELP_MONEY ATM=%b %t%t[v(SYSTEM_NAME)] Money Commands:%r%r %b[ljust($GIVE <thing>\[=<amount>\] %b\(or $PAY\),38)][ljust($BUY <vendor>=<product>\[\,<amount>\],38)]%r %b[ljust($DEPOSIT <object>=<amount>,38)][ljust($COLLECT <thing>\[=<amount>\],38)]%r %b[ljust($CASH \[<thing>\],38)][ljust($COINAGE,38)]%r %b[switch(v(CONF_STEALING),1,$STEAL <person>%r)]%r %b[ljust(&%vc\[-<product>\] <object>=<amount>,38)][ljust(&%vp\[-<product>\] <object>=<message>,38)]%r %b[ljust(&%vo\[-<product>\] <object>=<message>,38)][ljust(&%va\[-<product>\] <object>=<commands>,38)]%r %b[ljust(&%vg <object>=<message>,38)][ljust(&%vh <object>=<message>,38)]%r %b[ljust(&%vi <object>=<commands>,38)]%r%r %b<amount> can be specified as a decimal number or as a coinage%r %bexpression. See $HELP AMOUNTS for details.%r%r %bUse $HELP <topic> for more help on one of: [v(LIST_TOPICS)]%r %bNote: No command prefixes are used in the <topic> field for $HELP.%r

&HELP_AMOUNTS ATM=%b Money Amounts:%r%r %bThe <amount> field of commands can be specified one of two%r %bways.%r%r %bThe first is to use a decimal number, in terms of the primary%r %bcoin of the monetary system, eg. 1.5 meaning [u(FN_MONEYNAME,1.5)].%r%r %bThe second way is to use the coin names or abbreviations, which%r %bare listed in $COINAGE. For example, [u(FN_MONEYNAME,1)], or [u(FN_MONEYNAME,0.5)], or%r %b[u(FN_MONEYNAME,1.5)], or any combination. You can use the singular,%r %bplural, or abbreviation names listed in $COINAGE. If you type%r %bsomething which cannot be resolved, it will be treated as 0.%r

&HELP_GIVE ATM=%b Command: GIVE%r %bUsage: $GIVE <thing>\[=<amount>\]%r %b %b or: $PAY <thing>\[=<amount>\]%r%r %bThis command is used to give players and objects some of your coins.%r %bFor players, <amount> must be specified.%r%r %bFor objects, the <thing> must have an &%vc set, and you must pass %r %b<thing>'s UseLock. If <thing>'s &%vc is less than zero, it accepts%r %bdonations of any size, so you must specify an <amount>. If the &%vc%r %bis greater than zero, only the proper <amount> is accepted - any less%r %bis not enough, and any extra will be returned as change.%r%r %bExample:%r %b> $GIVE Bartender=[u(FN_MONEYNAME,2)]%r %bYou pay the Bartender.%r %bYou get [u(FN_MONEYNAME,1.5)] in change.%r%r %bSee $HELP GIVE2 for help with the &%vg attribute.%r

&HELP_BUY ATM=%b Command: BUY%r %bUsage: $BUY <thing>=<product>\[,<amount>\]%r%r %bThis command is used to purchase a specific item from a vendor,%r %band allow you to specify the price paid. The &%vc-<product>%r %bof the vendor must be set, and you must pass the machine's %r %bUseLock. The <amount> argument is optional - you don't have to%r %bsupply it, and it is just a safety check to make sure you don't%r %bspend more than you are anticipating. If supplied, the same%r %brules as for $GIVE to a machine apply.%r%r %bExample:%r %b> $BUY Bartender=Aspirin,[u(FN_MONEYNAME,5)]%r %bYou buy some aspirin from the Bartender.%r %bYou get [u(FN_MONEYNAME,0.5)] in change.%r

&HELP_DEPOSIT ATM=%b Command: DEPOSIT%r %bUsage: $DEPOSIT <thing>=<amount>%r%r %bThis command is used to store money on an object, like a wallet, or%r %bother secure place (see $HELP STEAL). You must control <thing> %r %b(see Help Control). This is useful for avoiding theft, as well as%r %bkeeping large amounts of money in a vault, etc.%r%r %bExample:%r %b> $DEPOSIT Wallet=2.5%r %bYou deposit [u(FN_MONEYNAME,2.5)] in Wallet.%r

&HELP_STEAL ATM=%b Command: STEAL %r %bUsage: $STEAL <person>%r%r %bThis command is used to attempt to steal money from another person.%r %bThe amount is random - it will be some randomly chosen percentage%r %bof what the other is carrying, with a moderate probability of getting%r %banything at all, and a slightly higher chance of getting caught in%r %bthe attempt. You can only do this to connected players, and if they%r %bare set HAVEN (a typical convention for OOC) it will not work.%r%r %bNOTE: This command may be disabled on some MUSHes.%r%r %bExample:%r %b> $STEAL Reepicheep%r %bYou steal [u(FN_MONEYNAME,0.5)] from [name(owner(me))]!%r

&HELP_COLLECT ATM=%b Command: COLLECT%r %bUsage: $COLLECT <thing>\[=<amount>\]%r%r %bThis command is used to retrieve money stored on an object or player.%r %bYou must control <thing> (see Help Control). This is useful for %r %bcollecting the profits garnered by a vendor, or retrieving money%r %bdeposited in a wallet or vault. If <amount> is not specified,%r %ball of the money <thing> has will be collected.%r%r %bExample:%r %b> $COLLECT Bartender%r %bYou collect [u(FN_MONEYNAME,7.5)] from Bartender.%r

&HELP_CASH ATM=%b Command: CASH%r %bUsage: $CASH \[<thing>\]%r%r %bIf <thing> is not specified, the command reports how much money%r %byou have with you. If <thing> is specified, and you control it%r %b(see Help Control) then the amount of money <thing> has is%r %breported.%r%r %bExample:%r %b> $CASH%r %bYou have [u(FN_MONEYNAME,0.5)].%r %b> $CASH Wallet%r %bWallet has [u(FN_MONEYNAME,5.0)].%r

&HELP_COINAGE ATM=%b Command: COINAGE%r %bUsage: $COINAGE%r%r %bThis command will display a table of the MUSH's coins, and their %r %bdifferent values, as well as a short description of what that %r %btranslates into in buying power.%r

&HELP_COST ATM=%b Command: %vc%r %bUsage: &%vc\[-<product>\] <thing>=<amount>%r%r %bWithout the <product> specification, this is like the standard MUSH %r %bcommand @cost, but the Money System allows you to specify fractional%r %bamounts for the cost of operating a machine. You should also set an %r %b&%vp, &%vo and &%va on an object, to respond when the object is %r %bgiven money, like the @Pay, @Opay, and @Apay equivalents.%r%r %bIf <amount> is negative, <thing> will accept donations of up to the%r %babsolute (positive) value of <amount>.%r %b%r %bWith a <product> named, the object, the $BUY command may be used.%r %bThis makes it much easier for a vendor to sell a number of different%r %bitems.%r%r %bExample:%r %b> &%vc Bartender=[u(FN_MONEYNAME,1.5)]%r %bSet.%r %b> &%vc-Aspirin Bartender=[u(FN_MONEYNAME,4.5)]%r %bSet.%r

&HELP_PAY ATM=%b Command: %vp%r %bUsage: &%vp\[-<product>\] <thing>=<message>%r%r %bThis is like the standard MUSH command @Pay, but is used when the %r %bobject is paid using the Money System. The person giving <thing>%r %bmoney sees <message>. If <product> is specified, then <message>%r %bis seen when someone uses $BUY and specifies that product.%r%r %bExample:%r %b> &%vp Bartender=You pay the bartender, who quickly mixes you a drink.%r %bSet.%r %b> &%vp-Aspirin Bartender=You buy some aspirin from the Bartender.%r %bSet.%r%r $PAY is just an alias for $GIVE. See $HELP GIVE for help.

&HELP_OPAY ATM=%b Command: %vo%r %bUsage: &%vo\[-<product>\] <thing>=<message>%r%r %bThis is like the standard MUSH command @OPay, but is used when the %r %bobject is paid using the Money System. The everyone in the room%r %bexcept the person paying <thing> sees <message>, prepended with%r %bthe payers name and a space. %bIf <product> is specified, then %r %b<message> %b is seen when someone uses $BUY and specifies that %r %bproduct.%r%r %bExample:%r %b> &%vo Bartender=pays the bartender, who quickly mixes %%o a drink.%r %bSet.%r %b> &%vo-Aspirin Bartender=buys some aspirin from the Bartender.%r %bSet.%r

&HELP_APAY ATM=%b Command: %va%r %bUsage: &%va\[-<product>\] <thing>=<command list>%r%r %bThis is like the standard MUSH command @APay, but is used when the %r %bobject is paid using the Money System. When <thing> is paid,%r %bthe command list is run. %bIf <product> is specified, then the%r %b<command list> is run when someone uses $BUY and specifies that %r %bproduct. The %%0 register is set to the decimal value paid.%r%r %bExample:%r %b> &%va Bartender=@create Glass of Mead; give %%N=Glass of Mead%r %bSet.%r %b> &%va-Aspirin Bartender=@create Two Aspirin; give %%N=Aspirin%r %bSet.%r

&HELP_GIVE2 ATM=%b Command: %vg%r %bUsage: &%vg <thing>=<message>%r%r %bThe %vg attribute holds <message>, which only the recipient of a %r %b$GIVE is shown. %r%r %bExample:%r %b> &%vg King=The King hands you some coins.%r %bSet.%r

&HELP_OGIVE ATM=%b Command: %vh%r %bUsage: &%vh <thing>=<message>%r%r %bThis <message> is shown to others in the same location as the%r %brecipient of a $GIVE. For the purposes of code, %%N is the name%r %bof the recipient, %%# is the dbref# of the recipient, etc.%r%r %bExample:%r %b> &%vh King=is given a handful of coins by the King.%r

&HELP_AGIVE ATM=%b Command: %vi%r %bUsage: &%vi <thing>=<command list>%r%r %bThis attribute is used to hold commands to be run upon the %r %bexecution of a $GIVE command. Since the Money System is coded%r %bin MUSHcode, a $GIVE does not run in one "tick" of the queue%r %bas other commands do, and hence there is some need for %r %bsynchronization when dealing with complex vendors. The %%# %r %bregister is set to the dbref# of the recipient, and the usual%r %b%%-registers are similarly set. The %%0 register holds the%r %bvalue given.%r%r %bExample:%r %b> &%vi King=@trigger Grand Vizier/TAX_PERSON=%%#,add(%%0,1)%r

@@ Command Configuration
@@ ~~~~~~~~~~~~~~~~~~~~~
@@ If you changed any of the command names, this code will make
@@ the necessary edits to the command stubs and help entries.

&CMD_CMDCONF ATM=$auto_conf2:@switch v(LIST_DEFCMDS)=v(LIST_CMDS),,{@dolist iter(lnum(words(v(LIST_DEFCMDS))),add(##,1))={@edit me/HELP_*=[extract(v(LIST_DEFCMDS),##,1)],[extract(v(LIST_CMDS),##,1)];@edit me/CMD_*=[extract(v(LIST_DEFCMDS),##,1)],[extract(v(LIST_CMDS),##,1)]}};&CMD_CMDCONF me

@@ NOTE: If this is going to be the second copy of this code on
@@ a MUSH, you can chop everything below this line and just @parent
@@ the new one to the old one. If you do that, uncomment the next line
@@ (it's present at the end of this script, but needs to be run last).

@@ @dolist setdiff(lattr(ATM),lattr(ATM/CMD_*))=@set ATM/##=NO_COMMAND

@@ ----- >8 ---------- >8 ---------- >8 ---------- >8 ---------- >8 -----

@@ Functions
@@ ~~~~~~~~~

@@ abs() is broken for decimal values in 2.0.9p1.
&FN_ABS ATM=mul(sign(%0),%0)

@@ Return a name given a decimal value.
&FN_MONEYNAME ATM=switch(1,eq(%0,0),nothing,mid(u(FN_RNAME,%0,iter(lnum(words(v(COIN_VLIST))),add(##,1))),1,999))

@@ Recurses through the coinage lists, and figure out how many of each.
&FN_RNAME ATM=[setq(0,extract(v(COIN_VLIST),first(%1),1))][switch(1,eq(%0,0),,words(%1),%b[u(FN_PSNAME,trunc(fdiv(%0,r(0))),%1)],gte(%0,r(0)),%b[u(FN_PSNAME,trunc(fdiv(%0,r(0))),first(%1))][u(FN_RNAME,u(FN_REMAINDER,%0,r(0)),rest(%1))],u(FN_RNAME,%0,rest(%1)))]

@@ Returns the plural or singular name, where appropriate.
&FN_PSNAME ATM=switch(1,eq(%0,1),1 [extract(v(SCOIN_NAME),%1,1)],[add(%0,0)] [extract(v(PCOIN_NAME),%1,1)])

@@ The eqivalent of mod() for decimal values.
&FN_REMAINDER ATM=sub(%0,mul(trunc(fdiv(%0,%1)),%1))

@@ This returns "1/X" for a decimal number.
&FN_FRACT ATM=switch(1,eq(%0,0),,gte(%0,1),[trunc(%0)] [u(FN_FRACT,sub(%0,trunc(%0)))],1/[fdiv(1,%0)])

@@ Convert things like "1 Lion 2 Trees" into a decimal. Deal with negatives.
&FN_VALUE ATM=switch(1,strmatch(%0,-*),mul(-1,u(FN_STARTREC,mid(%0,1,99))),u(FN_STARTREC,%0))

@@ Splice the string and then start the recursion.
&FN_STARTREC ATM=[setq(9,map(FN_SPLICE,%0))][u(FN_RUNTOT,first(r(9)),first(rest(r(9))),rest(rest(r(9))))]

@@ Break up strings like "123abc456def" into "123 abc 456 def".
&FN_SPLICE ATM=edit(edit(iter(lnum(strlen(%0)),[setq(0,mid(%0,##,1))][switch(1,xor(isnum(0[r(0)]0),isnum(0[mid(%0,add(##,1),1)]0)),r(0)-,r(0))]),%b,),-,%b)

@@ This recurses through the list and makes a running total.
&FN_RUNTOT ATM=switch(1,strmatch(%1,),%0,strmatch(%2,),u(FN_CALCVAL,%0,%1),add(u(FN_CALCVAL,%0,%1),u(fn_RUNTOT,first(%2),first(rest(%2)),rest(rest(%2)))))

@@ This figures out the value of a number/word pair.
&FN_CALCVAL ATM=fold(FN_ADD,iter(iter(lnum(words(v(COIN_VLIST))),add(##,1)),switch(1,strmatch(%1,extract(v(PCOIN_NAME),##,1)),mul(%0,extract(v(COIN_VLIST),##,1)),strmatch(%1,extract(v(SCOIN_NAME),##,1)),mul(%0,extract(v(COIN_VLIST),##,1)),strmatch(%1,extract(v(ACOIN_NAME),##,1)),mul(%0,extract(v(COIN_VLIST),##,1)),0)))

@@ For fold()ing through a list of numbers to sum.
&FN_ADD ATM=add(%0,%1)

@@ Command Subroutines
@@ ~~~~~~~~~~~~~~~~~~~
@@ Some of the more complex commands have a few varriants. To
@@ keep the code small enough to fit in a MUSH buffer, and
@@ to ease processing, they call these subroutines to do most
@@ of the work. Some of these are now pushing the buffer length
@@ themselves dealing w/ special cases.

@@ Collect all of the money from an object you control.
&SUB_COLLECT ATM=@wait me={@VV me=get(%1/%vm);@switch/first 1=eq(controls(%0,%1),0),{@pemit %0=You do not control that!},eq(%vv,0),{@pemit %0=[name(%1)] has no [v(SYSTEM_NAME)] currency to collect.},{@pemit %0=[u(FN_MONEYNAME,get(%1/%vm))] collected from [name(%1)].;&%vm %0=add(get(%0/%vm),%vv);&%vm %1=sub(get(%1/%vm),%vv);@pemit %0=You now have [u(FN_MONEYNAME,get(%0/%vm))].};@notify me}

@@ Collect some of the money from an object you control.
&SUB_COLLECT2 ATM=@wait me={@switch/first 1=eq(controls(%0,%1),0),{@pemit %0=You do not control that!},lt(%2,0),{@pemit %0=You can't collect a negative amount of money.},eq(%2,0),{@pemit %0=You must specify a positive amount.},eq(get(%1/%vm),0),{@pemit %0=[name(%1)] has no [v(SYSTEM_NAME)] currency to collect.},gt(%2,get(%1/%vm)),{@pemit %0=Thats more money than [name(%1)] has.},{@pemit %0=[u(FN_MONEYNAME,%2)] collected from [name(%1)].;&%vm %0=add(get(%0/%vm),%2);&%vm %1=sub(get(%1/%vm),%2);@pemit %0=You now have [u(FN_MONEYNAME,get(%0/%vm))].};@notify me}

@@ Pay command - no amount given.
&SUB_GIVE ATM=@wait me={@VV me=u(FN_VALUE,get(%1/%vc));@switch/first 1=or(not(elock(%1/Use,%0)),hasflag(%1,HALTED)),{@pemit %0=You cannot pay that.},strmatch(get(%1/%vc),),{@pemit %0=[name(%1)] will not take your money.},lt(%vv,0),{@pemit %0=You must specify an amout to give to [name(%1)].},eq(%vv,0),{@pemit %0=[name(%1)] doesn't charge you.;@verb %1=%0,%vp,You pay [name(%1)].,%vo,,%va,%vv;@verb %0=%1,%vg,,%vh,,%vi,%vv},gt(%vv,get(%0/%vm)),{@pemit %0=[name(%1)] costs more than you have.},{@pemit %0=You pay [u(FN_MONEYNAME,%vv)] to [name(%1)];&%vm %0=sub(get(%0/%vm),%vv);&%vm %1=add(get(%1/%vm),%vv);@verb %1=%0,%vp,You pay [name(%1)].,%vo,,%va,%vv;@verb %0=%1,%vg,,%vh,,%vi,%vv};@notify me}

@@ Pay command - amount given.
&SUB_GIVE2 ATM=@wait me={@VV me=u(FN_VALUE,get(%1/%vc));@switch/first 1=not(elock(%1/Use,%0)),{@pemit %0=You cannot pay that.},lt(%2,0),{@pemit %0=You can't pay a negative amount!},gt(%2,get(%0/%vm)),{@pemit %0=Thats more money than you have.},strmatch(get(%1/%vc),),{@pemit %0=[name(%1)] will not take your money.},and(lt(%vv,0),eq(%2,0)),{@pemit %0=You cannot donate nothing!},and(lt(%vv,0),lte(%2,u(FN_ABS,%vv))),{@pemit %0=You give [u(FN_MONEYNAME,%2)] to [name(%1)];&%vm %0=sub(get(%0/%vm),%2);&%vm %1=add(get(%1/%vm),%2);@verb %1=%0,%vp,,%vo,,%va,%2;@verb %0=%1,%vg,,%vh,,%vi,%2},lt(%vv,0),{@pemit %0=You pay [u(FN_MONEYNAME,%2)] to [name(%1)];@VV me=u(FN_ABS,%vv);@pemit %0=You get [u(FN_MONEYNAME,sub(%2,%vv))] back in change.;&%vm %0=sub(get(%0/%vm),%vv);&%vm %1=add(get(%1/%vm),%vv);@verb %1=%0,%vp,,%vo,,%va,%vv;@verb %0=%1,%vg,,%vh,,%vi,%vv},lt(%2,%vv),{@pemit %0=Feeling poor today?},eq(%vv,0),{@pemit %0=[name(%1)] is free.;@verb %1=%0,%vp,,%vo,,%va,%vv;@verb %0=%1,%vg,,%vh,,%vi,%vv},gt(%2,%vv),{@pemit %0=You pay [u(FN_MONEYNAME,%2)] to [name(%1)].;@pemit %0=You get [u(FN_MONEYNAME,sub(%2,%vv))] back in change.;&%vm %0=sub(get(%0/%vm),%vv);&%vm %1=add(get(%1/%vm),%vv);@verb %1=%0,%vp,You pay [name(%1)].,%vo,,%va,%vv;@verb %0=%1,%vg,,%vh,,%vi,%vv},{@pemit %0=You pay [u(FN_MONEYNAME,%vv)] to [name(%1)].;&%vm %0=sub(get(%0/%vm),%vv);&%vm %1=add(get(%1/%vm),%vv);@verb %1=%0,%vp,You pay [name(%1)].,%vo,,%va,%vv;@verb %0=%1,%vg,,%vh,,%vi,%vv};@notify me}

@@ Pay command - paying a person.
&SUB_GIVE3 ATM=@wait me={@switch/first 1=and(hasflag(owner(%0),WIZARD),gt(%2,0)),{@pemit %0=You give [u(FN_MONEYNAME,%2)] to [name(%1)].;&%vm %1=add(get(%1/%vm),%2);@pemit %1=[name(%0)] gifts you with [u(FN_MONEYNAME,%2)].;@verb %0=%1,%vg,,%vh,,%vi,%2},and(hasflag(owner(%0),WIZARD),eq(%2,0)),{@pemit %0=You must specify a non-zero amount.},and(and(hasflag(owner(%0),WIZARD),lt(%2,0)),gt(u(FN_ABS,%2),get(%1/%vm))),{@pemit %0=Thats more money than [name(%1)] has.},and(hasflag(owner(%0),WIZARD),lt(%2,0)),{@pemit %0=You take [u(FN_MONEYNAME,u(FN_ABS,%2))] from [name(%1)].;&%vm %1=add(get(%1/%vm),%2);@pemit %1=[name(%0)] takes [u(FN_MONEYNAME,u(FN_ABS,%2))] from you.},gt(%2,get(%0/%vm)),{@pemit %0=Thats more money than you have.},lte(%2,0),{@pemit %0=You can't give [name(%1)] that amount!},{@pemit %0=You give [u(FN_MONEYNAME,%2)] to [name(%1)].;&%vm %1=add(get(%1/%vm),%2);&%vm %0=sub(get(%0/%vm),%2);@pemit %1=[name(%0)] gives you [u(FN_MONEYNAME,%2)].;@verb %0=%1,%vg,,%vh,,%vi,%2};@notify me}

@@ Buy something from a machine - no amount given.
&SUB_BUY ATM=@wait me={@VV me=u(FN_VALUE,get(%1/%vc-%2));@switch/first 1=not(elock(%1/Use,%0)),{@pemit %0=You are not allowed to buy from that.},strmatch(get(%1/%vc-2),),{@pemit %0=[name(%1)] has no such product for sale.},lt(%vv,0),{@pemit %0=[name(%1)] has a negative cost set!},gt(%vv,get(%0/%vm)),{@pemit %0=That costs more than you have.},eq(%vv,0),{@pemit %0=[name(%1)] doesn't charge you for that.;@verb %1=%0,%vp-%2,You pay [name(%1)].,%vo-%2,,%va-%2,%vv;@verb %0=%1,%vg,,%vh,,%vi,%vv},{@pemit %0=You pay [u(FN_MONEYNAME,%vv)] to [name(%1)];&%vm %0=sub(get(%0/%vm),%vv);&%vm %1=add(get(%1/%vm),%vv);@verb %1=%0,%vp-%2,You pay [name(%1)].,%vo-%2,,%va-%2,%vv;@verb %0=%1,%vg,,%vh,,%vi,%vv};@notify me}

@@ Buy something from a machine - amount given.
&SUB_BUY2 ATM=@wait me={@VV me=u(FN_VALUE,%vv);@switch/first 1=not(elock(%1/Use,%0)),{@pemit %0=You are not allowed to buy from that.},strmatch(get(%1/%vc-2),),{@pemit %0=[name(%1)] has no such product for sale.},gt(%3,get(%0/%vm)),{@pemit %0=Thats more than you have.},lt(%3,%vv),{@pemit %0=Feeling poor today?},gt(%3,%vv),{@pemit %0=You pay [u(FN_MONEYNAME,%3)] to [name(%1)];@pemit %0=You get [u(FN_MONEYNAME,sub(%3,%vv))] back in change.;&%vm %0=sub(get(%0/%vm),%vv);&%vm %1=add(get(%1/%vm),%vv);@verb %1=%0,%vp-%2,You pay [name(%1)].,%vo-%2,,%va-%2,%vv;@verb %0=%1,%vg,,%vh,,%vi,%vv},{@pemit %0=You pay [u(FN_MONEYNAME,%vv)] to [name(%1)];&%vm %0=sub(get(%0/%vm),%vv);&%vm %1=add(get(%1/%vm),%vv);@verb %1=%0,%vp-%2,You pay [name(%1)].,%vo-%2,,%va-%2,%vv;@verb %0=%1,%vg,,%vh,,%vi,%vv};@notify me}

&SUB_ERROR ATM=@pemit %0=switch(1,strmatch(%1,#-1),I don't see that here.,strmatch(%1,#-2),I don't know which one you mean!,strmatch(type(%1),ROOM),The Money System only work with Players and Things.,strmatch(type(%1),EXIT),The Money System only work with Players and Things.,strmatch(%0,%1),Some Money Systems commands do not allow you to perform them on yourself.,strmatch(type(%1),PLAYER),Some Money commands do not work on players.,Huh? %bType "$HELP" for help.)

@@ Attribute Mods
@@ ~~~~~~~~~~~~~~
@@ Most of the attributes on the object can be set NO_COMMAND, and save
@@ the server some headaches.

@dolist setdiff(lattr(ATM),lattr(ATM/CMD_*))=@set ATM/##=NO_COMMAND