money-howto

concepts
CONCEPT
        money - The money system 

OBJECTS
        /obj/money.c, /obj/money_demon.c, /lib/money.c

AUTHOR
        Sique, Dec 20 1996

DESCRIPTION
        This is meant as a collection of hints, how to deal with the money
        system.
        Though the money system does not lack a certain complexity, I tried
        to design the programming interface as simple as possible to hide
        all internal functionality before the programming wizard.

        I'll try to describe with some very common examples, how to handle
        the different aspects of payment, creating, moving and removing
        money.


        MOVING, REMOVING of money

        The simpliest thing first: moving and removing of money.
        The money object tries to look at good as possible like any other
        object in the mud for normal thing handling (getting, dropping,
        giving, looking at, putting into containers).
        So you should handle it like every other object.

        Example: A well you can throw money into.

        #include <money.h>
        #include <search.h>

        init()
        {
          ::init();
          .
          .
          .
          /* doing other initialisations */

          add_action("f_throw","throw");
          /* Bind a command "throw" to the function f_throw() */
        }

        f_throw(string arg)
        {
          int    vision
          string what;
          object ob;

          vision = this_player()->CantSee();

          if(vision > 0)
          {
            notify_fail("It's far to bright for you to throw anything.\n");
            return 0;
          } else if(vision < 0)
          {
            notify_fail("It's too dark for you to throw anything.\n");
            return 0;
          } /* Light and Vision handling */

          if(!arg)
          {
            notify_fail("What do you want to throw into the well?\n");
            return 0;
          } /* The player just entered "throw" without argument */

          if(1 != sscanf(arg,"%s into well",what))
          {
            notify_fail("Where do you want to throw something?\n\
       Try 'throw <amount of money> into well'!\n");
            return 0;
          } /* The argument the player gave to "throw" does not match the
             * expected form: "throw <something> into well" */

          if(!(ob = this_player()->Search(what,SM_OBJECT|SEARCH_INV))
          {
            notify_fail("You don't have that to throw it into the well.\n");
            return 0;
          } /* The player tries to throw something he isn't carrying into
             * the well. */

          if(!ob->id(MONEY_ID))
          {
            notify_fail("I wouldn't throw that into the well!\n");
            return 0;
          } /* MONEY_ID is the unique ID of money.c. So you can detect, if
             * it's really money the player wants to throw into the well */

          printf("You throw %s into the well.\n",ob->Short()));
          say(sprintf("%s throws some money into the well.\n",ob->CapName()));
          ob->remove();
          if(ob)
            destruct(ob);
          return 1;
        } /* We now know that it is a money object the player wants to throw
           * into the well, so we take it from the player and remove it. */

        Note, that in the above example the player may have thrown into the
        well only a part of the whole money he was carrying. The money object
        itself controles, which part of it is meant and splits itself into
        it and the remaining. So you should never have to take care about it.

        Note also, that the finding of the money object is done like it
        has to be done for every other object. Similar you would have to
        use it for giving it away, dropping it, taking it from containers
        and in all other handling circumstances.


        CREATING money

        There are two different situations where you can create money:
        1.) Adding it as an item to any environment.
        2.) Giving it to a player for a specified reason.

        1.) Money is added as an item to an environment like every other
            object, with one exception: The kind and number of coins it
            shall contain has to be set. Therefore you have to set the
            Property P_MONEY accordingly.
            This has to be a mapping, where the coin types are the indices
            and the numbers the values.

        Example:

        #include <properties.h>
        #include <money.h>
        #include <rooms.h>

        AddItem(MONEY, REFRESH_REMOVE, ([ P_MONEY : ([ "copper" : 22,
                                                       "gold"   : 7,
                                                       "pebble" : 15
                                                      ])
                                         ])
                );

        where MONEY is the filename of the money object, predefined in
        <money.h>.

        Often you just want to add money with a specified value, and you
        do not dare, how the value is spread onto the different types of
        money. Then you may delegate the creating of the money mapping
        to either MONEY_LIB or MONEY_DEMON. Using MONEY_LIB is only senseful
        if you inherit it anyway for other purposes, because the
        MONEY_LIB->CreateMoneyFromValue() just calls itself the MONEY_DEMON,
        so MONEY_DEMON->CreateMoney() does exactly the same and has one
        call_other() less than the call to MONEY_LIB.

        Example:

        #include <properties.h>
        #include <money.h>
        #include <rooms.h>

        AddItem(MONEY, REFRESH_REMOVE,
                       MONEY_DEMON->CreateMoney(20 + random(50),
                                                ({"copper", "silver"})
                                                )
                );

        Or:

        #include <properties.h>
        #include <money.h>
        #include <rooms.h>

        inherit MONEY_LIB;

        AddItem(MONEY, REFRESH_REMOVE,
                       CreateMoneyFromValue(20 + random(50),
                                            ({"copper","silver"})
                                            )
                );

        2.) Giving money to players is done by the MONEY_LIB->AdjustMoney()
            function. There are two ways to call it: Either you give amount
            and sort of money with the second and the third argument:

        AdjustMoney(this_player(), 100, "copper");

            or you give a money mapping as described above as second
            argument:

        AdjustMoney(this_player(), CreateMoneyFromValue(100, ({"copper"}) );

            There is still the way giving money to the player by cloning
            a money object, setting its P_MONEY and giving this to the
            player. From inside NPCs this may have advantages, because
            NPC->Give() creates the correct messages itself.

            In some circumstances it may be needed to parse a given string,
            if it is a senseful money description and to create an according
            money mapping. This may be needed if a player wants to take
            some money, which still does not exist as object, like it may
            happen at the banking tellers in the bank offices.
            For this purpose MONEY_LIB->CreateMoneyFromString() returns
            an according money mapping, if the string given has had any sense
            to it (Zero otherwise).

            Note, that dies way must not be gone if the asked money is part
            or a whole money object which already exist! In this case you
            should rather use the locate()/search()-functionality in the
            environment of this object, as described above (See search(C)).

        Example: A money exchanger, giving you a specified amount of money.

        #include <money.h>

        inherit MONEY_LIB;

        init()
        {
          ::init();
          .
          .
          .
          add_action("f_get", "get");
        }

        f_get(string arg)
        {
          int     value;
          mapping money;
          string  types;

          if(!arg)
          {
            notify_fail("How much money you want to get?\n");
            return 0;
          } /* No argument was given */

          if(!(money = CreateMoneyFromString(arg)))
          {
            notify_fail("Sorry, but you can only get money in several\n\
        different currencies from me.\n");
            return 0;
          } /* CreateMoneyFromString() wasn't able to parse the given
             * argument. */

          /* Now it's getting tricky. We have to look, if the player has enough
           * of the other coin types to pay for the created money mapping.
           * (Paying with the same coin types for the money is somewhat silly!)
           * So we have first to get its value: */

          value = MONEY_DEMON->QueryMoneyValue(money);

          /* Now we have to compare it with the value of the cointypes not
           * in this money mapping, but carried by the player */

          if(QuerySomeonesValue(this_player(),
                                types = 
                                /* A side effect: We store the result in a
                                 * variable, the same value is given as
                                 * argument to the function. */

                                m_indices(QueryMoney(this_player())) -
                                /* gives the list of all cointypes the player
                                 * is carrying */

                                m_indices(money)
                                /* gives the list of the cointypes in the
                                 * desired money mapping */

                                ) < value + fee ) // However fee is calculated
          {
            notify_fail("You don't have enough other money to pay for\n\
        the exchange.\n");
            return 0;
          } /* The money exchanger sighs deeply... */

          /* So, now we can do the paying. For this special case we have no
           * prepared function, so we have to simulate it using the money
           * demon directly. */

          change = MONEY_DEMON->PayValue(value + fee,
                                         QueryMoney(this_player()),
                                         types // We still know them from above
                                         );

          if(!change)
          {
            notify_fail("Excuse me, but something went totally wrong.\n");
            return 0;
          } /* This should never happen. It's more a security fallback. */

          SetMoney(this_player(), change[0] + money);
          /* We give him his remaining money AND the money he has exchanged.
           * This is the reason why we hadn't used a prepared function. */

          printf("You bought %s for %s%s.\n",
                 MONEY_DEMON->CreateMoneyString(money),
                 MONEY_DEMON->CreateMoneyString(change[1]),
                 change[2]? /* this part is only to be created, if we get any
                             * change */
                   sprintf(" and get %s of change",
                          MONEY_DEMON->CreateMoneyString(change[2]):
                   ""
                 );
          /* We create a message, that tells the player what has happened. */

          say(sprintf("%s exchanged some money.\n", this_player()->CapName()));
          /* We inform the environment */

           return 1; /* Success! */
        }

            Ok, this example is very sophisticated. Normally you do not have
            to create such complex code to deal with the money object.


        PAYMENT programming

        The main purpose of money is to make the player able to trade with
        goods and buy services.
        So the MONEY_LIB tries to make this part of programming with money
        as simple as possible. Mainly you need only one function, that does
        the whole work: MONEY_LIB->PayValueString().
        It is highly recommended to look at its properties first if you want
        to program any payment.
        In the most cases a piece of code like the one below will fit for your
        purposes:

          .
          .
          .
          res = PayValueString(this_player(),   // Or whoever has to pay!
                               value,           // Everything has its value
                               0,               // Because no specified money
                                                // types are given, it will
                                                // fall back to the standard
                                                // ones for your domain.
                               "for a service", // Gives the reason for the
                                                // payment
                               0 );             // take the standard size of
                                                // the user's terminal.
          /* The last three arguments can be omitted */
          if(!res)
            return 0;
            /* The correct notify_fail()-call is already done by the function
             * call */

          write(res); /* Inform the player */
          say(sprintf("%s paid for a service.\n",this_player()->CapName()));
          .           /* And her environment */
          .
          .

        In some special cases you may like to have your own notify_fail()-
        messages. In this case you may rather use PayValue2(). A piece of
        code like this should fit your desires:

        res = PayValue2(this_player(), value, ({"copper","silver"}) );
        if(intp(res)) // if res is an integer, it's an error message.
        {
          switch(res) {
          case MONEY_NEGATIVE:
            notify_fail("Strange.\n\
        Someone tried to force you a negative value!\n");
            break;
          case MONEY_NO_MONEY:
            notify_fail("Trying to get something for nothing, hey?\n");
            break;
          case MONEY_NOT_ENOUGH:
            notify_fail("Poor player, you should earn more money first!\n");
            break;
          case MONEY_CANT_PAY:
            notify_fail("Coming from far away, stranger?\n\
        I do not accept your money.\n");
            break;
          case MONEY_WRONG_RESULT:
            notify_fail("Don't know, what happened.\n\
        An non recoverable error occured.\n");
            break;
          default:
            notify_fail("Strange. I even don't know which error occured.\n");
          }
          return 0;
        }
        /* Now we for sure know, that we got an array of two elements. */
        if(!res[0]) {
          if(!res[1]) {
            notify_fail("Nothing happened.\n");
            return 0;
          } // There is neither money given away nor any change came back */
          printf("Lucky you! You got %s without paying anything!\n", res[1]));
          say(sprintf("Strange. %s get some change without having anything\n\
        paid.\n", this_player()->CapName()));
          return 1;
        }
        printf("You paid %s for a service%s.\n",
               res[0],
               res[1]?sprintf(" and got %s back",res[1]):""
               );
        say(sprintf("%s has to pay something.\n", this_player()->CapName()));
          .
          .
          .

        For a short and silent payment you may still use PayMoney().
        But then you have to do all the checks and generating messages
        yourself.


        DETECTING special coin types

        For implementing machines you can only start by inserting a specified
        amount of a given coin you have to detect if a player is really trying
        to use the right number of the right coins.

        Lets look at an example:

        #include <money.h>

        inherit MONEY_LIB;

        init()
        {
          ::init();
          .
          .
          .
          add_action("f_insert","insert");
        }

        f_insert(string arg)
�       {
          int    amount;
          string coins;

          if(!arg)
          {
            notify_fail("What do you want to insert?\n")
            return 0; /* The old game: No argument */
          }

          if(2 != sscanf(arg, "%d %s", amount, coins))
          {
            notify_fail("Insert <amount> <cointype>.\n");
            return 0; 
          } /* Send the correct format as error message. */

          if("copper" != MONEY_DEMON->PresentMoney(coins,
                                                   QueryMoney(this_player()))
          {
            notify_fail("Sorry, this machine takes only copper coins.\n");
            return 0;
          } /* PresentMoney() tries to find the cointype in the given
             * money mapping and returns its internal, unique name. */

          if(amount != 100)
          {
            notify_fail("The correct price is 100 copper coins.\n");
            return 0;
          }

          if(!PayValue(this_player(), 100 ({"copper"}) )
          {
            notify_fail("You do not own 100 copper coins.\n");
            return 0;
          } /* The payment was unsuccessful */

          /* put some special effects here */

          return 1;
        }

SEE ALSO
        money_concept(C), money(S)