skylib_fluffos_v3/
skylib_fluffos_v3/bin/
skylib_fluffos_v3/bin/db/
skylib_fluffos_v3/fluffos-2.9-ds2.04/
skylib_fluffos_v3/fluffos-2.9-ds2.04/ChangeLog.old/
skylib_fluffos_v3/fluffos-2.9-ds2.04/Win32/
skylib_fluffos_v3/fluffos-2.9-ds2.04/compat/
skylib_fluffos_v3/fluffos-2.9-ds2.04/compat/simuls/
skylib_fluffos_v3/fluffos-2.9-ds2.04/include/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/clone/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/command/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/data/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/etc/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/include/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/inherit/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/inherit/master/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/log/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/single/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/single/tests/compiler/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/single/tests/efuns/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/single/tests/operators/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/u/
skylib_fluffos_v3/fluffos-2.9-ds2.04/tmp/
skylib_fluffos_v3/fluffos-2.9-ds2.04/windows/
skylib_fluffos_v3/mudlib/
skylib_fluffos_v3/mudlib/cmds/
skylib_fluffos_v3/mudlib/cmds/admin/
skylib_fluffos_v3/mudlib/cmds/guild-race/
skylib_fluffos_v3/mudlib/cmds/living/broken/
skylib_fluffos_v3/mudlib/cmds/player/group_cmds/
skylib_fluffos_v3/mudlib/cmds/playtester/
skylib_fluffos_v3/mudlib/d/admin/
skylib_fluffos_v3/mudlib/d/admin/room/
skylib_fluffos_v3/mudlib/d/admin/room/we_care/
skylib_fluffos_v3/mudlib/d/admin/save/
skylib_fluffos_v3/mudlib/d/admin/text/
skylib_fluffos_v3/mudlib/d/learning/TinyTown/buildings/
skylib_fluffos_v3/mudlib/d/learning/TinyTown/map/
skylib_fluffos_v3/mudlib/d/learning/TinyTown/roads/
skylib_fluffos_v3/mudlib/d/learning/chars/
skylib_fluffos_v3/mudlib/d/learning/functions/
skylib_fluffos_v3/mudlib/d/learning/handlers/
skylib_fluffos_v3/mudlib/d/learning/help_topics/
skylib_fluffos_v3/mudlib/d/learning/help_topics/npcs/
skylib_fluffos_v3/mudlib/d/learning/help_topics/objects/
skylib_fluffos_v3/mudlib/d/learning/help_topics/rcs_demo/
skylib_fluffos_v3/mudlib/d/learning/help_topics/rcs_demo/RCS/
skylib_fluffos_v3/mudlib/d/learning/help_topics/rooms/
skylib_fluffos_v3/mudlib/d/learning/help_topics/rooms/crowd/
skylib_fluffos_v3/mudlib/d/learning/help_topics/rooms/situations/
skylib_fluffos_v3/mudlib/d/learning/save/
skylib_fluffos_v3/mudlib/d/learning/school/
skylib_fluffos_v3/mudlib/d/learning/school/add_sc/
skylib_fluffos_v3/mudlib/d/learning/school/characters/
skylib_fluffos_v3/mudlib/d/learning/school/general/
skylib_fluffos_v3/mudlib/d/learning/school/getting-started/
skylib_fluffos_v3/mudlib/d/learning/school/getting-started/basic_commands/
skylib_fluffos_v3/mudlib/d/learning/school/getting-started/edtutor/
skylib_fluffos_v3/mudlib/d/learning/school/getting-started/unix_tutor/
skylib_fluffos_v3/mudlib/d/learning/school/items/
skylib_fluffos_v3/mudlib/d/learning/school/npc_school/
skylib_fluffos_v3/mudlib/d/learning/school/room_school/
skylib_fluffos_v3/mudlib/d/learning/school/room_school/room_basic/
skylib_fluffos_v3/mudlib/d/learning/school/room_school/situations/
skylib_fluffos_v3/mudlib/d/learning/school/room_school/terrain_tutor/
skylib_fluffos_v3/mudlib/d/learning/text/
skylib_fluffos_v3/mudlib/d/liaison/
skylib_fluffos_v3/mudlib/d/mudlib/
skylib_fluffos_v3/mudlib/d/mudlib/changes/
skylib_fluffos_v3/mudlib/d/playtesters/
skylib_fluffos_v3/mudlib/d/playtesters/effects/
skylib_fluffos_v3/mudlib/d/playtesters/handlers/
skylib_fluffos_v3/mudlib/d/playtesters/items/
skylib_fluffos_v3/mudlib/d/sage/
skylib_fluffos_v3/mudlib/doc/
skylib_fluffos_v3/mudlib/doc/creator/
skylib_fluffos_v3/mudlib/doc/driver/
skylib_fluffos_v3/mudlib/doc/driver/efuns/arrays/
skylib_fluffos_v3/mudlib/doc/driver/efuns/buffers/
skylib_fluffos_v3/mudlib/doc/driver/efuns/calls/
skylib_fluffos_v3/mudlib/doc/driver/efuns/compile/
skylib_fluffos_v3/mudlib/doc/driver/efuns/filesystem/
skylib_fluffos_v3/mudlib/doc/driver/efuns/floats/
skylib_fluffos_v3/mudlib/doc/driver/efuns/functions/
skylib_fluffos_v3/mudlib/doc/driver/efuns/general/
skylib_fluffos_v3/mudlib/doc/driver/efuns/mappings/
skylib_fluffos_v3/mudlib/doc/driver/efuns/mixed/
skylib_fluffos_v3/mudlib/doc/driver/efuns/mudlib/
skylib_fluffos_v3/mudlib/doc/driver/efuns/numbers/
skylib_fluffos_v3/mudlib/doc/driver/efuns/parsing/
skylib_fluffos_v3/mudlib/doc/login/
skylib_fluffos_v3/mudlib/doc/lpc/basic_manual/
skylib_fluffos_v3/mudlib/doc/lpc/intermediate/
skylib_fluffos_v3/mudlib/doc/new/add_command/
skylib_fluffos_v3/mudlib/doc/new/events/
skylib_fluffos_v3/mudlib/doc/new/handlers/
skylib_fluffos_v3/mudlib/doc/new/living/race/
skylib_fluffos_v3/mudlib/doc/new/living/spells/
skylib_fluffos_v3/mudlib/doc/new/object/
skylib_fluffos_v3/mudlib/doc/new/player/
skylib_fluffos_v3/mudlib/doc/new/room/guild/
skylib_fluffos_v3/mudlib/doc/new/room/outside/
skylib_fluffos_v3/mudlib/doc/new/room/storeroom/
skylib_fluffos_v3/mudlib/doc/object/
skylib_fluffos_v3/mudlib/doc/playtesters/
skylib_fluffos_v3/mudlib/doc/policy/
skylib_fluffos_v3/mudlib/doc/weapons/
skylib_fluffos_v3/mudlib/global/
skylib_fluffos_v3/mudlib/global/creator/
skylib_fluffos_v3/mudlib/handlers/
skylib_fluffos_v3/mudlib/include/casino/
skylib_fluffos_v3/mudlib/include/cmds/
skylib_fluffos_v3/mudlib/include/effects/
skylib_fluffos_v3/mudlib/include/npc/
skylib_fluffos_v3/mudlib/include/room/
skylib_fluffos_v3/mudlib/include/shops/
skylib_fluffos_v3/mudlib/net/daemon/
skylib_fluffos_v3/mudlib/net/daemon/chars/
skylib_fluffos_v3/mudlib/net/inherit/
skylib_fluffos_v3/mudlib/net/obj/
skylib_fluffos_v3/mudlib/net/obj/BACKUPS/
skylib_fluffos_v3/mudlib/obj/amulets/
skylib_fluffos_v3/mudlib/obj/armours/plate/
skylib_fluffos_v3/mudlib/obj/b_day/
skylib_fluffos_v3/mudlib/obj/clothes/transport/horse/
skylib_fluffos_v3/mudlib/obj/faith/symbols/
skylib_fluffos_v3/mudlib/obj/fungi/
skylib_fluffos_v3/mudlib/obj/gatherables/
skylib_fluffos_v3/mudlib/obj/instruments/
skylib_fluffos_v3/mudlib/obj/media/
skylib_fluffos_v3/mudlib/obj/misc/player_shop/
skylib_fluffos_v3/mudlib/obj/monster/godmother/
skylib_fluffos_v3/mudlib/obj/monster/transport/
skylib_fluffos_v3/mudlib/obj/rings/
skylib_fluffos_v3/mudlib/obj/scabbards/
skylib_fluffos_v3/mudlib/obj/spells/
skylib_fluffos_v3/mudlib/obj/stationery/
skylib_fluffos_v3/mudlib/obj/stationery/envelopes/
skylib_fluffos_v3/mudlib/obj/toys/
skylib_fluffos_v3/mudlib/obj/vessels/
skylib_fluffos_v3/mudlib/obj/weapons/axes/
skylib_fluffos_v3/mudlib/obj/weapons/chains/
skylib_fluffos_v3/mudlib/obj/weapons/maces/BACKUPS/
skylib_fluffos_v3/mudlib/save/autodoc/
skylib_fluffos_v3/mudlib/save/book_handler/
skylib_fluffos_v3/mudlib/save/books/history/calarien/
skylib_fluffos_v3/mudlib/save/mail/
skylib_fluffos_v3/mudlib/save/new_soul/data/
skylib_fluffos_v3/mudlib/save/parcels/
skylib_fluffos_v3/mudlib/save/playerinfo/
skylib_fluffos_v3/mudlib/save/players/d/
skylib_fluffos_v3/mudlib/save/players/s/
skylib_fluffos_v3/mudlib/save/random_names/
skylib_fluffos_v3/mudlib/save/random_names/data/
skylib_fluffos_v3/mudlib/save/terrains/
skylib_fluffos_v3/mudlib/save/terrains/tutorial_desert/
skylib_fluffos_v3/mudlib/save/terrains/tutorial_grassy_field/
skylib_fluffos_v3/mudlib/save/terrains/tutorial_mountain/
skylib_fluffos_v3/mudlib/save/todo_lists/
skylib_fluffos_v3/mudlib/secure/
skylib_fluffos_v3/mudlib/secure/cmds/admin/
skylib_fluffos_v3/mudlib/secure/cmds/lord/
skylib_fluffos_v3/mudlib/secure/config/
skylib_fluffos_v3/mudlib/secure/handlers/autodoc/
skylib_fluffos_v3/mudlib/secure/handlers/intermud/
skylib_fluffos_v3/mudlib/secure/include/global/
skylib_fluffos_v3/mudlib/secure/save/
skylib_fluffos_v3/mudlib/secure/save/handlers/
skylib_fluffos_v3/mudlib/secure/std/
skylib_fluffos_v3/mudlib/secure/std/classes/
skylib_fluffos_v3/mudlib/secure/std/modules/
skylib_fluffos_v3/mudlib/std/creator/
skylib_fluffos_v3/mudlib/std/dom/
skylib_fluffos_v3/mudlib/std/effects/
skylib_fluffos_v3/mudlib/std/effects/external/
skylib_fluffos_v3/mudlib/std/effects/fighting/
skylib_fluffos_v3/mudlib/std/effects/magic/
skylib_fluffos_v3/mudlib/std/effects/magic/BACKUPS/
skylib_fluffos_v3/mudlib/std/effects/other/BACKUPS/
skylib_fluffos_v3/mudlib/std/effects/priest/
skylib_fluffos_v3/mudlib/std/effects/room/
skylib_fluffos_v3/mudlib/std/environ/
skylib_fluffos_v3/mudlib/std/guilds/
skylib_fluffos_v3/mudlib/std/guilds/old/
skylib_fluffos_v3/mudlib/std/languages/
skylib_fluffos_v3/mudlib/std/liquids/
skylib_fluffos_v3/mudlib/std/npc/
skylib_fluffos_v3/mudlib/std/npc/goals/
skylib_fluffos_v3/mudlib/std/npc/goals/basic/
skylib_fluffos_v3/mudlib/std/npc/goals/misc/
skylib_fluffos_v3/mudlib/std/npc/plans/
skylib_fluffos_v3/mudlib/std/npc/plans/basic/
skylib_fluffos_v3/mudlib/std/npc/types/
skylib_fluffos_v3/mudlib/std/npc/types/helper/
skylib_fluffos_v3/mudlib/std/npcs/
skylib_fluffos_v3/mudlib/std/outsides/
skylib_fluffos_v3/mudlib/std/races/shadows/
skylib_fluffos_v3/mudlib/std/room/basic/BACKUPS/
skylib_fluffos_v3/mudlib/std/room/basic/topography/
skylib_fluffos_v3/mudlib/std/room/controller/
skylib_fluffos_v3/mudlib/std/room/inherit/topography/
skylib_fluffos_v3/mudlib/std/room/topography/area/
skylib_fluffos_v3/mudlib/std/room/topography/iroom/
skylib_fluffos_v3/mudlib/std/room/topography/milestone/
skylib_fluffos_v3/mudlib/std/shadows/curses/
skylib_fluffos_v3/mudlib/std/shadows/disease/
skylib_fluffos_v3/mudlib/std/shadows/fighting/
skylib_fluffos_v3/mudlib/std/shadows/healing/
skylib_fluffos_v3/mudlib/std/shadows/magic/
skylib_fluffos_v3/mudlib/std/shadows/poison/
skylib_fluffos_v3/mudlib/std/shadows/room/
skylib_fluffos_v3/mudlib/std/shops/controllers/
skylib_fluffos_v3/mudlib/std/shops/objs/
skylib_fluffos_v3/mudlib/std/shops/player_shop/
skylib_fluffos_v3/mudlib/std/socket/
skylib_fluffos_v3/mudlib/std/soul/d/
skylib_fluffos_v3/mudlib/std/soul/e/
skylib_fluffos_v3/mudlib/std/soul/i/
skylib_fluffos_v3/mudlib/std/soul/j/
skylib_fluffos_v3/mudlib/std/soul/k/
skylib_fluffos_v3/mudlib/std/soul/l/
skylib_fluffos_v3/mudlib/std/soul/n/
skylib_fluffos_v3/mudlib/std/soul/o/
skylib_fluffos_v3/mudlib/std/soul/q/
skylib_fluffos_v3/mudlib/std/soul/r/
skylib_fluffos_v3/mudlib/std/soul/u/
skylib_fluffos_v3/mudlib/std/soul/v/
skylib_fluffos_v3/mudlib/std/soul/y/
skylib_fluffos_v3/mudlib/std/soul/z/
skylib_fluffos_v3/mudlib/std/stationery/
skylib_fluffos_v3/mudlib/w/
skylib_fluffos_v3/mudlib/w/default/
skylib_fluffos_v3/mudlib/w/default/armour/
skylib_fluffos_v3/mudlib/w/default/clothes/
skylib_fluffos_v3/mudlib/w/default/item/
skylib_fluffos_v3/mudlib/w/default/npc/
skylib_fluffos_v3/mudlib/w/default/room/
skylib_fluffos_v3/mudlib/w/default/weapon/
skylib_fluffos_v3/mudlib/www/
skylib_fluffos_v3/mudlib/www/java/
skylib_fluffos_v3/mudlib/www/secure/
skylib_fluffos_v3/mudlib/www/secure/lpc/advanced/
skylib_fluffos_v3/mudlib/www/secure/lpc/intermediate/
skylib_fluffos_v3/win32/
/**
 * This the base shop.  It buys and sells stuff form players.
 * <p>
 * A storeroom must be set for the shop!
 * <p>
 * Original made who knows when.
 * @author Pinkfish
 * @see set_storeroom()
 * @change bil
 * to make the list a lot nicer.
 * @change Pinkfish
 * to give shops types and make them send out
 * reps to sell/buy things from other shops.
 * @change Ceres
 * to add burglable storerooms.
 * @change 23-11-97, Gototh
 * to add buy, sell, list, browse, value,
 * cost_too_muchy and not_worthy functions.
*/

#include <money.h>
#include <move_failures.h>
#include <shop.h>

inherit "/std/shops/inherit/shopkeeper_base";
inherit ROOM_OBJ;

mixed our_storeroom, buy_mess, sell_mess, list_mess, value_mess,
too_costly_mess, not_worthy_mess, browse_mess, open_cond, *other_shops;

int amount_sold, amount_bought, strict_shop, no_steal,
sell_stolen, steal_difficulty, min_amount, max_inventory, sell_large;

mixed shop_type, buy_func, sell_func, value_func, too_costly_func,
cannot_sell_func, browse_func, list_func;

nosave string shoplift_handler;

string original_storeroom;

string shop_list(mixed arr, int detail);
string shop_parse(string str, mixed ob, object client, string money,
  string extra, string which);
string find_free_exit();
int do_buy(object *ob);
int do_list_these(object *obs);
void do_buy_things( object *obs, int cost, object pl );
void do_parse(mixed arr, mixed ob, object client, string money,
  string extra);
object create_mercenary(object rep);
void event_shoplift(object command_ob, object thief, object victim);
object query_store_room();

void create() {
    buy_mess = ({
      "You buy $ob$ for $money$.\n",
      "$client$ buys $ob$.\n"});
    sell_mess = ({
      "You sell $ob$ for $money$.\n",
      "$client$ sells $ob$.\n"});
    list_mess = "$extra$";
    value_mess = "$ob$ is valued at $money$.\n";
    too_costly_mess = "$ob$ is worth too much to be sold here.\n";
    not_worthy_mess = "$ob$ is not worth enough to be sold here.\n";
    browse_mess = "$ob$ costs $money$, it looks like:\n$extra$";
    open_cond = 1;
    other_shops = ({ });
    max_inventory = MAX_INVENTORY;
    min_amount = 50;
    add_help_file("shop");
    ::create();
} /* create() */

/** @ignore yes */
void reset() {
    if( !random(3) ) {
        // This is incremented by shoplifting.
        remove_property("inventory_loss");
    }

    call_out("send_out_reps", 2 );

} /* reset() */

/**
 * This method sets the function to call when buying something.  The
 * function will be called with two elements, the first being the
 * player doing the buying and the second being the array
 * of objects being bought.
 * <p>
 * If the function is a string then the function of that name
 * will be called on the shop, if it is a function pointer
 * then the function pointer will be evaluated.
 * @param func the function to call
 * @see set_sell_function()
 * @see set_value_function()
 * @see set_too_costly_function()
 * @see set_cannot_sell_function()
 * @see set_browse_function()
 * @see set_list_function()
 */
void set_buy_function( mixed func ) {
    if( stringp(func) || functionp(func) )
        buy_func = func;
} /* set_buy_function() */

/**
 * This method sets the function to call when selling something.  The
 * function will be called with two elements, the first being the
 * player doing the selling and the second being the array
 * of objects being sold.
 * <p>
 * If the function is a string then the function of that name
 * will be called on the shop, if it is a function pointer
 * then the function pointer will be evaluated.
 * @param func the function to call
 * @see set_buy_function()
 * @see set_value_function()
 * @see set_too_costly_function()
 * @see set_cannot_sell_function()
 * @see set_browse_function()
 * @see set_list_function()
 */
void set_sell_function( mixed func ) {
    if( stringp(func) || functionp(func) )
        sell_func = func;
} /* set_sell_function() */

/**
 * This method sets the function to call when an item(*s) are being
 * valued.  The
 * function will be called with three elements, the first being the
 * player doing the valueing and the second being the array
 * of objects being valued and the third being the string
 * value of the objects.
 * <p>
 * If the function is a string then the function of that name
 * will be called on the shop, if it is a function pointer
 * then the function pointer will be evaluated.
 * @param func the function to call
 * @see set_sell_function()
 * @see set_buy_function()
 * @see set_too_costly_function()
 * @see set_cannot_sell_function()
 * @see set_browse_function()
 * @see set_list_function()
 */
void set_value_function( mixed func ) {
    if( stringp(func) || functionp(func) )
        value_func = func;
} /* set_value_function() */

/**
 * This method sets the function to call when buying something and it
 * costs too much.  The
 * function will be called with two elements, the first being the
 * player doing the buying and the second being the array
 * of objects which cost too much.
 * <p>
 * If the function is a string then the function of that name
 * will be called on the shop, if it is a function pointer
 * then the function pointer will be evaluated.
 * @param func the function to call
 * @see set_sell_function()
 * @see set_value_function()
 * @see set_buy_function()
 * @see set_cannot_sell_function()
 * @see set_browse_function()
 * @see set_list_function()
 */
void set_too_costly_function( mixed func ) {
    if( stringp(func) || functionp(func) )
        too_costly_func = func;
} /* set_too_costly_function() */

/**
 * This method sets the function to call when selling something
 * which fails for some reason.  The
 * function will be called with two elements, the first being the
 * player doing the selling and the second being the array
 * of objects being sold.
 * <p>
 * If the function is a string then the function of that name
 * will be called on the shop, if it is a function pointer
 * then the function pointer will be evaluated.
 * @param func the function to call
 * @see set_sell_function()
 * @see set_value_function()
 * @see set_too_costly_function()
 * @see set_buy_function()
 * @see set_browse_function()
 * @see set_list_function()
 */
void set_cannot_sell_function( mixed func ) {
    if( stringp(func) || functionp(func) )
        cannot_sell_func = func;
} /* set_cannot_sell_function() */

/**
 * This method sets the function to call when browsing something.  The
 * function will be called with two elements, the first being the
 * player doing the browsing and the second being the array
 * of objects being browsed.
 * <p>
 * If the function is a string then the function of that name
 * will be called on the shop, if it is a function pointer
 * then the function pointer will be evaluated.
 * @param func the function to call
 * @see set_sell_function()
 * @see set_value_function()
 * @see set_too_costly_function()
 * @see set_cannot_sell_function()
 * @see set_browse_function()
 * @see set_list_function()
 */
void set_browse_function( mixed func ) {
    if( stringp(func) || functionp(func) )
        browse_func = func;
} /* set_browse_function() */

/**
 * This method sets the function to call when listing something.  The
 * function will be called with two elements, the first being the
 * player doing the listing and the second being the array
 * of objects being listed.
 * <p>
 * If the function is a string then the function of that name
 * will be called on the shop, if it is a function pointer
 * then the function pointer will be evaluated.
 * @param func the function to call
 * @see set_sell_function()
 * @see set_value_function()
 * @see set_too_costly_function()
 * @see set_cannot_sell_function()
 * @see set_browse_function()
 * @see set_list_function()
 */
void set_list_function( mixed func ) {
    if( stringp(func) || functionp(func) )
        list_func = func;
} /* set_list_function() */

/**
 * This sets the message which will be told to the players when
 * they sell something at the shop.  If the parameter is a string
 * then that message is sent to the player and nothing is sent to
 * the other people in the room.  If the message is a two element
 * array, the first element is sent to the player and the second
 * element is sent to the others in the room.  In both these
 * cases the pattersn $ob$ will be replaces with the objects
 * being sold, $client$ will be replaced with the client, $money$
 * will be replaced with the money information, $extra$ will be replaces
 * with any extra information.
 * <p>
 * If the parameter is a function pointer, then this is called with
 * the parameter func(obs, client, money, extra);
 * @param str the message to print
 * @see set_buy_message()
 * @see query_sell_message()
 * @see set_set_value_message()
 * @see set_too_costly_message()
 * @see set_no_worthy_message()
 * @see set_browse_message()
 * @see set_list_message()
 * @example
 * set_sell_message("You sell $ob$ for $money$.\n");
 * @example
 * set_sell_message( ({ "You sell $ob$ for $money$.\n",
 *                      "$client$ sells $ob$.\n"});
 */
void set_sell_message( mixed str ) {
    sell_mess = str;
} /* set_sell_message() */

/**
 * This sets the message which will be told to the players when
 * they buy something at the shop.  If the parameter is a string
 * then that message is sent to the player and nothing is sent to
 * the other people in the room.  If the message is a two element
 * array, the first element is sent to the player and the second
 * element is sent to the others in the room.  In both these
 * cases the pattersn $ob$ will be replaces with the objects
 * being sold, $client$ will be replaced with the client, $money$
 * will be replaced with the money information, $extra$ will be replaces
 * with any extra information.
 * <p>
 * If the parameter is a function pointer, then this is called with
 * the parameter func(obs, client, money, extra);
 * @param str the message to print
 * @see set_sell_message()
 * @see query_buy_message()
 * @see set_set_value_message()
 * @see set_too_costly_message()
 * @see set_no_worthy_message()
 * @see set_browse_message()
 * @see set_list_message()
 * @example
 * set_buy_message("You buy $ob$ for $money$.\n");
 * @example
 * set_buy_message( ({ "You buy $ob$ for $money$.\n",
 *                      "$client$ buys $ob$.\n"});
 */
void set_buy_message( mixed str ) {
    buy_mess = str;
} /* set_buy_message() */

/**
 * This sets the message which will be told to the players when
 * they value something at the shop.  If the parameter is a string
 * then that message is sent to the player and nothing is sent to
 * the other people in the room.  If the message is a two element
 * array, the first element is sent to the player and the second
 * element is sent to the others in the room.  In both these
 * cases the pattersn $ob$ will be replaces with the objects
 * being sold, $client$ will be replaced with the client, $money$
 * will be replaced with the money information, $extra$ will be replaces
 * with any extra information.
 * <p>
 * If the parameter is a function pointer, then this is called with
 * the parameter func(obs, client, money, extra);
 * @param str the message to print
 * @see set_buy_message()
 * @see query_value_message()
 * @see set_set_value_message()
 * @see set_too_costly_message()
 * @see set_no_worthy_message()
 * @see set_browse_message()
 * @see set_list_message()
 * @example
 * set_value_message("$ob$ is valued at $money$.\n");
 * @example
 * set_sell_message( ({ "$ob$ is valued at $money$.\n",
 *                      "$client$ values $ob$.\n"});
 */
void set_value_message( mixed str ) {
    value_mess = str;
} /* set_value_message() */

/**
 * This sets the message which will be told to the players when
 * they buy something at the shop and it costs too much.
 * If the parameter is a string
 * then that message is sent to the player and nothing is sent to
 * the other people in the room.  If the message is a two element
 * array, the first element is sent to the player and the second
 * element is sent to the others in the room.  In both these
 * cases the pattersn $ob$ will be replaces with the objects
 * being sold, $client$ will be replaced with the client, $money$
 * will be replaced with the money information, $extra$ will be replaces
 * with any extra information.
 * <p>
 * If the parameter is a function pointer, then this is called with
 * the parameter func(obs, client, money, extra);
 * @param str the message to print
 * @see set_buy_message()
 * @see query_too_costly_message()
 * @see set_set_value_message()
 * @see set_browse_message()
 * @see set_no_worthy_message()
 * @see set_sell_message()
 * @see set_list_message()
 * @example
 * set_browse_message("$ob$ is worth too much to be sold here.\n");
 * @example
 * set_browse_message( ({ "$ob$ is worth too much to be sold here.\n",
 *                      "$client$ tries to sell the terribly expensive $ob$.\n"});
 */
void set_too_costly_message( mixed str ) {
    too_costly_mess = str;
} /* set_too_costly_message() */

/**
 * This sets the message which will be told to the players when
 * they sell soemthign that is not worth enough at the shop.
 * If the parameter is a string
 * then that message is sent to the player and nothing is sent to
 * the other people in the room.  If the message is a two element
 * array, the first element is sent to the player and the second
 * element is sent to the others in the room.  In both these
 * cases the pattersn $ob$ will be replaces with the objects
 * being sold, $client$ will be replaced with the client, $money$
 * will be replaced with the money information, $extra$ will be replaces
 * with any extra information.
 * <p>
 * If the parameter is a function pointer, then this is called with
 * the parameter func(obs, client, money, extra);
 * @param str the message to print
 * @see set_buy_message()
 * @see query_not_worthy_message()
 * @see set_set_value_message()
 * @see set_too_costly_message()
 * @see set_browse_message()
 * @see set_sell_message()
 * @see set_list_message()
 * @example
 * set_not_worthy_message("$ob$ is not worth enough to be sold here.\n");
 * @example
 * set_not_worthy_message( ({ "$ob$ is not worth enough to be sold here.\n",
 *                      "$client$ tries to sell the rubbishy $ob$.\n"});
 */
void set_not_worthy_message( mixed str ) {
    not_worthy_mess = str;
} /* set_not_worthy_message() */

/**
 * This sets the message which will be told to the players when
 * they browse something at the shop.  If the parameter is a string
 * then that message is sent to the player and nothing is sent to
 * the other people in the room.  If the message is a two element
 * array, the first element is sent to the player and the second
 * element is sent to the others in the room.  In both these
 * cases the pattersn $ob$ will be replaces with the objects
 * being sold, $client$ will be replaced with the client, $money$
 * will be replaced with the money information, $extra$ will be replaces
 * with any extra information.  In the case oif a browse the
 * extra information is the long description of the object.
 * <p>
 * If the parameter is a function pointer, then this is called with
 * the parameter func(obs, client, money, extra);
 * @param str the message to print
 * @see set_buy_message()
 * @see query_browse_message()
 * @see set_set_value_message()
 * @see set_too_costly_message()
 * @see set_no_worthy_message()
 * @see set_sell_message()
 * @see set_list_message()
 * @example
 * set_browse_message("$ob$ costs $money$, it looks like:\n$extra$");
 * @example
 * set_browse_message( ({ "$ob$ costs $money$, it looks like:\n$extra$",
 *                      "$client$ browses $ob$.\n"});
 */
void set_browse_message( mixed str ) {
    browse_mess = str;
} /* set_browse_message() */

/**
 * This sets the message which will be told to the players when
 * they list something at the shop.  If the parameter is a string
 * then that message is sent to the player and nothing is sent to
 * the other people in the room.  If the message is a two element
 * array, the first element is sent to the player and the second
 * element is sent to the others in the room.  In both these
 * cases the pattersn $ob$ will be replaces with the objects
 * being sold, $client$ will be replaced with the client, $money$
 * will be replaced with the money information, $extra$ will be replaces
 * with any extra information.  In the case of a list,
 * the extra information *is* the list.
 * <p>
 * If the parameter is a function pointer, then this is called with
 * the parameter func(obs, client, money, extra);
 * @param str the message to print
 * @see set_buy_message()
 * @see query_buy_message()
 * @see set_set_value_message()
 * @see set_too_costly_message()
 * @see set_no_worthy_message()
 * @see set_browse_message()
 * @see set_list_message()
 * @example
 * set_list_message("$extra$.\n");
 * @example
 * set_list_message( ({ "You list $ob$ for $money$.\n",
 *                      "$client$ lists $ob$.\n"});
 */
void set_list_message( mixed str ) {
    list_mess = str;
} /* set_list_message() */

/**
 * This sets the conditons which the shop will be open during.  If this
 * is set to an integer the shop will always be in that state, so
 * if you set the open condition to 0, it would always be closed.
 * If it is set to a string, then that function will be called on
 * this object to test to see if it is open.  If it iset to
 * function pointer, the function pointer will be evaluated.  If it
 * is set to an array, the first element specifies the object and the
 * second specifies the function to call.
 * @see query_open_condition()
 * @see test_open()
 */
void set_open_condition( mixed str ) {
    open_cond = str;
} /* set_open_condition() */

/**
 * This method sets the no steal property.  If a shop is set
 * as no steal, then it cannot be shoplifted.
 * @param i the new value of the no_steal property
 * @see set_steal_difficulty()
 * @see query_no_steal()
 * @see set_sell_stolen()
 */
void set_no_steal( int i ) {
    no_steal = i;
} /* set_no_steal() */

/**
 * This method sets the difficulty at which to steal stuff from
 * this shop.
 * @see set_no_steal()
 * @see set_sell_stolen()
 * @see query_steal_difficulty()
 * @param i the new value for the steal difficulty
 */
void set_steal_difficulty( int i ) {
    steal_difficulty = i;
} /* set_steal_difficulty() */

/**
 * This method sets the shop as a place which will receive and
 * sell stolen goods.  It automaticly sets the no_steal property
 * to 1.
 * @see set_no_steal()
 * @see set_steal_difficulty()
 * @see query_sell_stolen()
 * @param i the new value for the stolen property
 */
void set_sell_stolen( int i ) {
    sell_stolen = i;
    no_steal = 1;
} /* set_sell_stolen() */

/**
 * This method sets the shop to sell very large objects. ie. If a player
 * cannot carry items they are placed in the room for the player rather
 * than not being sold to the player.
 */
void set_sell_large( int i ) { sell_large = i; }

/**
 * This method returns the current sell message of the shop.
 * @see set_sell_mess()
 * @return the current sell message of the shop
 */
mixed query_sell_mess() { return sell_mess; }

/**
 * This method returns the current list message of the shop.
 * @see set_list_mess()
 * @return the current list message of the shop
 */
mixed query_list_mess() { return list_mess; }

/**
 * This method returns the current value message of the shop.
 * @see set_value_mess()
 * @return the current value message of the shop
 */
mixed query_value_mess() { return value_mess; }

/**
 * This method returns the current too costly message of the shop.
 * @see set_too costly_mess()
 * @return the current too costly message of the shop
 */
mixed query_too_costly_mess() { return too_costly_mess; }

/**
 * This method returns the current not worthy message of the shop.
 * @see set_not worthy_mess()
 * @return the current not worthy message of the shop
 */
mixed query_not_worthy_mess() { return not_worthy_mess; }

/**
 * This method returns the current buy message of the shop.
 * @see set_buy_mess()
 * @return the current buy message of the shop
 */
mixed query_buy_mess() { return buy_mess; }

/**
 * This method returns the current browse message of the shop.
 * @see set_browse_mess()
 * @return the current browse message of the shop
 */
mixed query_browse_mess() { return browse_mess; }

/**
 * This method returns the current open conditon of the shop.
 * @see set_open_conditon_mess()
 * @return the current open condition message of the shop
 */
mixed query_open_condition() { return open_cond; }

/**
 * This method returns the current no steal property.
 * @see set_no_steal_mess()
 * @return the current no steal properyt of the shop
 */
int query_no_steal() { return no_steal; }

/**
 * This method returns the current steal difficulty of the shop.
 * @see set_steal_difficulty_mess()
 * @return the current steal difficulty message of the shop
 */
int query_steal_difficulty() { return steal_difficulty; }

/**
 * This method returns the shop lift response handler for the shop.
 * This allows the shop to respond in someway to someone shop
 * lifting stuff, like the heavys in Ankh-Morpork.
 * @returns the shop lift response handler
 * @see set_shoplift_response_handler()
 * @see set_no_steal()
 * @see set_sell_stolen()
 * @see set_steal_difficulty()
 */
string query_shoplift_response_handler() { return shoplift_handler; }

/**
 * This method tells us if the shop is really a shop or not.
 * @return 1 always
 */
int query_shop() { return 1; }

/**
 * This method returns the items which can potentially be shop lifted
 * with the passed in string.
 * @param str the name for the object to attempt to shop lift
 * @return the array of matching objects
 * @see query_steal_difficulty()
 * @see query_shoplift_response_handler()
 */
object *query_shop_lift_items( string str, object player ) {
    return match_objects_for_existence( str, ({ query_store_room() }) );
} /* query_shop_lift_items() */

/**
 * This method turns the objects into real objects (if that is
 * nessessary, it is not with a normal shop).
 * @param ob the object to turn into a normal object
 */
object shoplift_success( object ob ) {
    return ob;
} /* shoplift_success() */

/**
 * This method sets the shop lift response handler for the shop.
 * This allows the shop to respond in someway to shop lifting
 * stuff, like the heavies in Ankh-Morpork.  The function
 * 'handle_shoplift' will be called on the handler when the
 * shop lift is attempted.  It will be passed two arguments
 * the first is the thief, the second is the room being
 * shoplifted.  This can be set to  afucntion pointer
 * which will be evaluated and passed in two arguemtns
 * when  a shoplift occurs.
 * @see query_shoplift_response_handler()
 * @see set_no_steal()
 * @see set_sell_stolen()
 * @see set_steal_difficulty()
 * @param word the new shop lift response handler
 */
void set_shoplift_response_handler( string word ) {
    shoplift_handler = word;
} /* set_shoplift_response_handler() */

/**
 * This method sets the minimum value of items that can be sold here.
 * @see query_min_amount()
 */
void set_min_amount( int i ) { min_amount = i; }

/**
 * This method sets the maximum number of inventory items this shop will
 * take before it starts to refuse to purchase items from players and
 * deleting items from its inventory.
 * The default for this is defined as MAX_INVENTORY in shop.h
 */
void set_max_inventory( int i ) { max_inventory = i; }

/**
 * This method tests to see if the shop is actually open.
 * @return non-zero if the shop is open
 * @see set_open_condition()
 * @see query_open_condition()
 */
int test_open() {
    if( !check_shopkeeper_open() )
        return 0;
    if( stringp(open_cond) )
        return (int)call_other( TO, open_cond );
    if( intp(open_cond) )
        return open_cond;
    if( functionp(open_cond) )
        return evaluate(open_cond);
    return (int)call_other( open_cond[0], open_cond[1] );
} /* test_open() */

/** @ignore yes */
void init() {
    string room;

    ::init();
    if( stringp(our_storeroom) ) {
        room = our_storeroom;
    } else if( our_storeroom ) {
        room = file_name(our_storeroom);
    } else {
        tell_room( TO, "Oh dear, we don't seem to have a storeroom.\n");
    }
    add_command("sell", "<indirect:object:me>");
    add_command("buy", "<indirect:object:"+room+">");
    add_command("list", "[all]");
    add_command("list", "<indirect:object:"+room+">",
                     (: do_list_these($1) :));
    add_command("browse", "<indirect:object:"+room+">");
    add_command("value", "<indirect:object:me>");
} /* init() */

/**
 * This method returns the maximum value oif an object that can
 * be sold here.
 * @see query_min_amount()
 * @return the maximum amount
 * @see /handlers/money_handler.c
 */
int query_max_amount() { return MAX_AMOUNT; }

/**
 * This method returns the minimum value oif an object that can
 * be sold here.
 * @see query_max_amount()
 * @return the minimum amount
 * @see /handlers/money_handler.c
 */
int query_min_amount() { return min_amount; }

/**
 * This method returns the value of the object in this shop.
 * @return the value of the object here
 * @param thing the thing to value
 * @param sell are we selling it?
 * @see query_max_amount()
 * @see query_min_amount()
 */
varargs int query_value( object thing, int sell ) {
    return (int)thing->query_value_at(TO);
} /* query_value() */

/**
 * This returns the amount of money you can sell and object for,
 * which is less than the amount it will be sold for in the shop later.
 * @param n the value to scale
 * @return the sell value
 */
int scaled_value( int n ) {
    int profit;

    profit = 5 + to_int( sqrt( to_float( n / 5 ) ) );
    if( profit > MAX_PROFIT )
        profit = MAX_PROFIT;

    return n * ( 100 - profit ) / 100;

} /* scaled_value() */

/**
 * This method does the actual selling.
 * @param in_obs the objects to sell
 * @return 1 on success, 0 on failure
 */
int do_sell(object *in_obs) {
    int amt, total_amt;
    string place;
    object money, *obs, *selling, *cannot, *stolen, storeob, ob;
    mixed m_array;

    if( !test_open() )
        return 0;

    obs = filter( in_obs, (: !$1->query_keep() :) );
    if( !sizeof(obs) ) {
        TP->add_failed_mess( TO, "All of $I you previously decided to "
            "keep.\n", in_obs );
        return 0;
    }

    in_obs = TP->query_holding() + TP->query_armours();
    cannot = filter(obs, (: member_array( $1, $2 ) != -1 :), in_obs );
    if( sizeof(cannot) ) {
        obs -= cannot;
        tell_object( TP, "You decide not to sell "+
            query_multiple_short( cannot, "the")+", because you are wearing "
            "or holding $V$0=it,them$V$.\n");
    }

    // If there are more items in the storeroom than max_inventory allows
    // return a failure message.
    if( objectp(our_storeroom) ) {
        storeob = our_storeroom;
    } else {
        our_storeroom->rabbit_away();
        storeob = find_object(our_storeroom);
    }
    if( sizeof( INV(storeob) ) > max_inventory ) {
        TP->add_failed_mess( TO, "Sorry, the shop is full up and isn't "
            "buying items!\n", ({ }) );
        call_out("tidy_inventory", random(20) );
        return 0;
    }

    if( sizeof(obs) > MAX_OBS ) {
        write("The shopkeeper can't cope with all those objects.\n");
        obs = obs[0..MAX_OBS - 1];
    }
    selling = cannot = stolen = ({ });
    place = query_property("place");
    if( !place || place == "" )
        place = "default";

    foreach( ob in obs ) {
        if( !sell_stolen && ob->query_property("stolen") ) {
            stolen += ({ ob });
            continue;
        } else {
            ob->remove_property("stolen");
        }
        if( TO->query_value(ob) > 0 && !ob->do_not_sell() &&
            !TO->do_not_buy(ob) && ( !strict_shop ||
            shop_type == ob->query_property("shop type") ) &&
            ENV(ob) == TP ) {
            if( ob->move(our_storeroom) ) {
                if( ob->short() )
                    cannot += ({ ob });
                continue;
            }
            amt = (int)TO->query_value(ob);
            if( !ob->query_property("sale_value") )
                amt = scaled_value( amt );
            if( amt <= (int)TO->query_max_amount() &&
                amt >= (int)TO->query_min_amount() ) {
                if( shop_type != ob->query_property("shop type") )
                    amt = (amt * 90) / 100;
                total_amt += amt;
                selling += ({ ob });
                ob->being_sold();
            } else {
                if( ob->short() )
                    cannot += ({ ob });
                ob->move(TP);
            }
        } else if( ob->short() ) {
                cannot += ({ ob });
        }
    }
    if( !sizeof(selling) ) {
        if( sizeof(cannot) ) {
            if( stringp(cannot_sell_func) )
                call_other( TO, cannot_sell_func, TP, cannot );
            else if( functionp(cannot_sell_func) )
                evaluate( cannot_sell_func, TP, cannot );
            TP->add_failed_mess( TO, "You cannot sell $I.\n", cannot );
        } else if( sizeof(stolen) ) {
            TP->add_failed_mess( TO, "You cannot sell $I because "+
                ({"it's", "they're"})[query_group(stolen)]+" stolen!\n",
                stolen );
        } else {
            TP->add_failed_mess( TO, "You have nothing to sell.\n", ({ }) );
        }
        return 0;
    }
    if( TO->cannot_afford(total_amt) ) {
        selling->move(TP);
        TP->add_failed_mess( TO, "The shop cannot afford to buy $I from "
            "you.\n", selling );
        return 0;
    }
    amount_sold += total_amt;
    m_array = (mixed)MONEY_H->create_money_array( total_amt, place );
    money = clone_object(MONEY_OBJECT);
    money->set_money_array( m_array );
    if( sizeof(cannot) ) {
        if( stringp(cannot_sell_func) )
            call_other( TO, cannot_sell_func, TP, cannot );
        else if( functionp(cannot_sell_func) )
            evaluate( cannot_sell_func, TP, cannot );
        write("You cannot sell "+query_multiple_short(cannot)+".\n");
        cannot->move(TP);
    }

    if( stringp(sell_func) )
        call_other( TO, sell_func, TP, obs );
    else if( functionp(sell_func) )
        evaluate( sell_func, TP, obs );

    do_parse( sell_mess, selling, TP,
        (string)MONEY_H->money_string(m_array), "");

    if( (int)money->move(TP) != MOVE_OK ) {
        tell_object( TP, "You're too heavily burdened to accept all that "
            "money, so the shopkeeper puts it on the floor.\n");
        money->move(TO);
    }

    TO->made_transaction( -total_amt, selling );
    return 1;

} /* do_sell() */


/**
 * This method determines whether or not a given object is a creator object
 * and should be in this shop or not.
 * @param ob the object to check
 * @return 1 if the object is not allowed, 0 if it is.
 */
int creator_object( object ob ) {
    string path;

    if( file_name( ob )[0..2] == "/w/" )
        return 1;

    if( ( path = ob->query_property( "virtual name" ) ) &&
        path[0..2] == "/w/" )
        return 1;

    return 0;

} /* creator_object() */

/**
 * This method does the actual buying.
 * @param obs the objects to buy
 * @return 1 on success, 0 on failure
 */
int do_buy( object *obs ) {
    int i, amt, ob_amt, total_cost;
    string place;
    object money, *to_buy, *cannot, *too_much;
    object *creator_obs;

    if( !test_open() )
        return 0;

    if( sizeof(obs) > MAX_OBS ) {
        write("The shopkeeper can't cope with all those objects.\n");
        obs = obs[0..MAX_OBS-1];
    }

    creator_obs = filter( obs, (: creator_object( $1 ) :) );
    if( sizeof( creator_obs ) && file_name()[0..2] != "/w/" ) {
        tell_object( TP, "You cannot buy "+
            query_multiple_short( creator_obs, "the" )+" because they "
            "shouldn't be in the game!\n");
        obs -= creator_obs;
    }

    to_buy = too_much = cannot = ({ });
    place = query_property("place");
    if( !place || place == "" )
        place = "default";

    if( !money = present( MONEY_ALIAS, TP ) ) {
        if( stringp( too_costly_func ) )
            call_other( TO, too_costly_func, TP, obs );
        else if( functionp(too_costly_func) )
            evaluate( too_costly_func, TP, obs );
        TP->add_failed_mess( TO, "You have no money.\n", obs );
        return 0;
    }

    amt = (int)money->query_value_in(place);
    if( place != "default" )
        amt += (int)money->query_value_in("default");

    while( i < sizeof(obs) ) {
        ob_amt = (int)TO->query_value( obs[i], 1 );
        if( ob_amt > amt ) {
            if( obs[i]->short() )
                too_much += ({ obs[i] });
            obs = delete( obs, i, 1 );
            continue;
        }
        if( obs[i]->move(TP) ) {
            if( !sell_large ) {
                if( obs[i]->short() )
                    cannot += ({ obs[i] });
                i++;
                continue;
            } else {
                obs[i]->move(TO);
            }
        }
        amt -= ob_amt;
        total_cost += ob_amt;
        to_buy += ({ obs[i] });
        i++;
    }
    amount_bought += total_cost;
    if( sizeof(cannot) )
        TP->add_failed_mess( TO, "You cannot pick up $I.\n", cannot );
    if( sizeof(too_much) ) {
        if( stringp(too_costly_func) )
            call_other( TO, too_costly_func, TP, cannot );
        else if( functionp(too_costly_func) )
            evaluate( too_costly_func, TP, cannot );
        TP->add_failed_mess( TO, "$I $V$0=costs,cost$V$ too much.\n",
            too_much );
    }
    if( !sizeof(to_buy) )
        return 0;
    do_buy_things( to_buy, total_cost, TP );
    do_shopkeeper_buy();
    return 1;
} /* do_buy() */

/** @ignore yes */
void do_buy_things( object *obs, int cost, object pl ) {
    int i, j;
    string place;
    object money, change;
    mixed m_array, p_array;

    place = query_property("place");
    if( !place || place == "" )
        place = "default";


    if( !money = present( MONEY_ALIAS, pl ) ) {
        if( stringp(too_costly_func) )
            call_other( TO, too_costly_func, TP, obs );
        else if( functionp(too_costly_func) )
            evaluate( too_costly_func, TO, obs );
        TP->add_failed_mess( TO, "You don't have any money.\n", obs );
        return 0;
    }

    change = clone_object(MONEY_OBJ);
    m_array = (int)MONEY_H->create_money_array( cost, place );

    for( i = 0; i < sizeof(m_array); i += 2 ) {
        p_array = (mixed)MONEY_H->make_payment( m_array[i],
                  m_array[i + 1], money, place );
        if( !pointerp(p_array) )
            continue;
        for( j = 0; j < sizeof(p_array[0]); j += 2 )
            money->adjust_money( -p_array[0][j + 1], p_array[0][j] );
        change->adjust_money( p_array[1] );
    }

    do_parse( buy_mess, obs, pl, (string)MONEY_H->money_string(m_array), "");

    if( stringp(buy_func) )
        call_other( TO, buy_func, pl, obs );
    else if( functionp(buy_func) )
        evaluate( buy_func, pl, obs );

    if( (int)change->move(pl) != MOVE_OK ) {
        tell_object( pl, "You are too heavily burdened to accept your "
            "change, so the shopkeeper puts it on the floor.\n");
        change->move(TO);
    }

    TO->made_transaction( cost, obs );

} /* do_buy() */

/**
 * This method lists all the objects in stock.
 * @return 1 on succes, 0 on failure
 * @param do_list_these()
 */
int do_list() {
    object ob;

    if( !test_open() )
        return 0;

    if( objectp(our_storeroom) ) {
        ob = our_storeroom;
    } else {
        if( original_storeroom ) {
            our_storeroom = load_object( original_storeroom );
            ob = our_storeroom;
        } else {
            add_failed_mess("Please notify a creator: the storeroom for "
                "this shop cannot load or has gone missing.\n");
            return 0;
        }
    }

    if( stringp(list_func) )
        call_other( TO, list_func, TP );
    else if( functionp(list_func) )
        evaluate( list_func, TP );

    do_parse( list_mess, ({ TO }), TP, "", shop_list( INV(ob), 0 ) );
    do_shopkeeper_list();
    return 1;

} /* do_list() */

/**
 * This method lists only the specified objects.
 * @return 1 on success, 0 on failure
 * @see do_list()
 */
int do_list_these( object *obs ) {
    if( !test_open() )
        return 0;

    do_parse( list_mess, ({ TO }), TP, "", shop_list( obs, 1 ) );
    do_shopkeeper_list();
    return 1;

} /* do_list_these() */

/**
 * This method is called when the player is browseing stuff.
 * @param obs the objects to browse
 * @return 1 on success, 0 on failure
 */
int do_browse( object *obs ) {
    int value;
    string place;
    object ob;

    if( !test_open() )
        return 0;

    place = query_property("place");
    if( !place || place == "" )
        place = "default";

    if( stringp(browse_func) )
        call_other( TO, browse_func, TP, obs );
    else if( functionp(browse_func) )
        evaluate( browse_func, TP, obs );

    foreach( ob in obs ) {
        value = (int)TO->query_value( ob, 1 );
        do_parse( browse_mess, ({ ob }), TP,
            (string)MONEY_H->money_value_string(value, place),
            (string)ob->long() );
    }

    do_shopkeeper_browse();
    return 1;

} /* do_browse() */

/**
 * This method is called when the player is valueing stuff.
 * @param obs the objects to value
 * @return 1 on success, 0 on failure
 */
int do_value(object *obs) {
    int val;
    int total;
    string place;
    object ob;

    if( !test_open() )
        return 0;

    place = query_property("place");
    if( !place || place == "" )
        place = "default";

    foreach( ob in obs ) {
        if( ob->do_not_sell() || TO->do_not_buy(ob) ||
            ENV(ob) != TP || ( strict_shop &&
            ( shop_type != (string)ob->query_property("shop type") ) ) ) {
            val = 0;
        } else {
            val = (int)TO->query_value(ob);
            if( shop_type != (string)ob->query_property("shop type") )
                val = (val * 90) / 100;
        }
        val = scaled_value(val);
        total += val;
        if( val > (int)TO->query_max_amount() ) {
            do_parse( too_costly_mess, ({ ob }), TP, "",
                (string)ob->do_not_sell() );
        } else if( val < (int)TO->query_min_amount() ) {
            do_parse( not_worthy_mess, ({ ob }), TP, "",
                (string)ob->do_not_sell() );
        } else {
            do_parse( value_mess, ({ ob }), TP,
                (string)MONEY_H->money_value_string( val, place ),
                (string)ob->do_not_sell() );
            if( stringp(value_func) ) {
                call_other( TO, value_func, TP, obs,
                    MONEY_H->money_string( MONEY_H->create_money_array(
                    val, place ) ) );
            } else if( functionp(value_func) ) {
                evaluate( value_func, TP, obs,
                    MONEY_H->money_string( MONEY_H->create_money_array(
                    val, place ) ) );
            }
        }
    }

    if( sizeof(obs) > 1 )
        write("This gives you a total value of "+
              MONEY_H->money_value_string(total, place)+".\n");

    return 1;

} /* do_value() */

/**
 * This method creates the list for the shop.
 * @param arr the array of objects to list
 * @param detail display them in detail?
 * @return the string list
 */
string shop_list( mixed arr, int detail ) {
    int j, value;
    string s, mon, place, *shorts;
    object *list;
    mapping inv, costs;
    mixed ind, item;

    list = ( pointerp(arr) ? arr : INV(TO) );

    // Only keep track of things with shorts.
    inv = ([ ]);

    foreach( item in list ) {
        s = (string)item->short();
        if( !s || !TO->query_value( item, 1 ) )
            continue;
        if( !stringp(s) )
            s = "get a creator for this one!";
        if( inv[s] )
            inv[s] += ({ item });
        else
            inv[s] = ({ item });
    }

    // Okay, print it.
    s = "";
    shorts = keys(inv);
    if( !sizeof(shorts) ) {
        return ( detail ? "The shop is all out of what you wanted." :
                          "The shop is totally out of stock." )+"\n";
    }

    s = "You find on offer:\n";
    place = query_property("place");
    if( !place || place == "" )
        place = "default";

    foreach( item in shorts ) {
        ind = inv[item];
        switch( sizeof(ind) ) {
        case 1:
          s += "Our very last " + item;
        break;
        case 2..5 :
          s += CAP( query_num( sizeof(ind), 0 )+" "+
               (string)ind[0]->query_plural() );
        break;
        default:
          s += ( detail ? CAP( query_num( sizeof(ind), 0 ) ) :
                 "A large selection of" )+" "+(string)ind[0]->query_plural();
        }
        if( detail ) {
            costs = ([ ]);
            for( j = 0; j < sizeof(ind); j++ ) {
                value = (int)TO->query_value( ind[j], 1 );
                mon = (string)MONEY_H->money_value_string( value, place );
                if( !costs[mon] )
                    costs[mon] = ({""+(j + 1)});
                else
                    costs[mon] += ({""+(j + 1)});
            }
            if( sizeof(costs) == 1 ) {
                s += " for "+keys(costs)[0];
                if( sizeof(values(costs)[0]) > 1 ) {
                    s += " each.\n";
                } else {
                    s += ".\n";
                }
            } else {
                s += ":-\n";
                foreach( mon in keys(costs) )
                    s += "  [#"+implode(costs[mon], ",")+"] for "+mon+".\n";
            }
        } else {
            s += ".\n";
        }
    }

    return s;

} /* shop_list() */

/**
 * This method sets the current store room associated with
 * the shop.  This is important!  A shop needs a storeroom.
 * @param ob the storeroom to set
 * @example
 * set_store_room(PATH+"store_room");
 */
void set_store_room( mixed ob ) {
    if( stringp(ob) ) {
        original_storeroom = ob;
        our_storeroom = find_object(ob);
        if( !our_storeroom )
            our_storeroom = load_object(ob);
    } else
        our_storeroom = ob;
} /* set_store_room() */

/** @ignore yes */
void guards( object tp ) {
    object ob;

    if( ENV(tp) != TO && ENV(tp) != our_storeroom )
        return;

    while( !random(6) ) {
        ob = create_mercenary(0);
        ob->move( ENV(tp) );
        ob->attack_ob(tp);
    }
} /* guards() */

/**
 * This method returns the storeroom associated with the shop.
 * @see set_store_room()
 * @return the current store room
 */
object query_store_room() { return our_storeroom; }

/**
 * This method is the major message processing function for
 * the buye messages, sell messages etc.  It handles calling
 * the functions and setting the results back onto the
 * player.
 * @param arr the value of the message
 * @param ob the objects to process
 * @param money the money string
 * @param extra the extra string
 */
void do_parse( mixed arr, object *ob, object client, string money, string extra ) {
    if( stringp(arr) ) {
        TP->show_message("$P$List$P$"+
            TP->convert_message( replace( arr, ({
                "$ob$", query_multiple_short(ob),
                "$client$", TP->short(),
                "$money$", money,
                "$extra$", extra
                }) ) ) );
        TP->add_succeeded_mess( TO, "", ob );
    } else if( functionp(arr) ) {
        evaluate( arr, ob, client, money, extra );
    } else {
        TP->show_message("$P$List$P$"+
            TP->convert_message( replace( arr[0], ({
                "$ob$", query_multiple_short(ob),
                "$client$", TP->short(),
                "$money$", money,
                "$extra$", extra
                }) ) ) );
        TP->add_succeeded_mess( TO, ({ "", replace(arr[1], ({
            "$ob$", "$I", "$client$", "$N", "$money$", money,
            "$extra$", extra }) ) }), ob );
    }
} /* do_parse() */

/** @ignore yes */
string shop_parse( string str, mixed ob, object client, string money,
                   string extra, string which ) {
    if( sizeof(ob) )
        str = replace( str, "$ob$", query_multiple_short( ob, which ) );
    else
        str = replace( str, "$ob$", call_other( ob, which +"_short" ) );

    if( client )
        str = replace( str, "$client$", client->the_short() );

    return replace( str, ({ "$money$", money, "$extra$", extra }) );

} /* shop_parse() */

/**
 * This method adds a shop with shich we will exchange inventories
 * of certain types.  So the main shop can sell off its swords and
 * stuff to the sword shop.
 * @param shop the other shop
 */
void add_other_shop(mixed shop) {
    // Should give a nice string telling us the other room.
    other_shops += ({shop});
} /* add_other_shop() */

/**
 * This method returns the type of the shop.  This conttrols what sort
 * of merchandise the shop will buy and sell.
 * <p>The types of allowed shops are:
 * <ul>
 * <li>  jewelery
 * <li>  armoury
 * <li>  clothes
 * <li>  magic
 * <li>  none set (ie: 0, general type)
 * </ul>
 * @return the shop type
 * @see set_shop_type()
 * @see set_strict_shop()
 */
string query_shop_type() { return shop_type; }

/**
 * This method sets the type of the shop.  This controls what sort of
 * mechandise the shop will buy and sell.
 * <p>The types of allowed shops are:
 * <ul>
 * <li>  jewelery
 * <li>  armoury
 * <li>  clothes
 * <li>  magic
 * <li>  none set (ie: 0, general type)
 * </ul>
 * @see query_shop_type()
 * @see set_strict_shop()
 * @param ty the type of the shop
 */
void set_shop_type( string ty ) { shop_type = ty; }

/**
 * This method sets the strictness of the shop, if the shop is strict
 * it will not deal in items of other types at all.
 * @param i the new strict value
 * @see set_shop_type()
 * @see query_strict_shop()
 */
void set_strict_shop( int i ) { strict_shop = i; }

/**
 * This method returns the structness of the shop, if the shop is strict
 * it will not deal in items of other types at all.
 * @return the current strict value
 * @see set_shop_type()
 * @see set_strict_shop()
 */
int query_strict_shop() { return strict_shop; }

/**
 * This method creates the sales representative which is sent off
 * to exchange goods with other shops.
 * @return the sales representative
 * @see add_other_shop()
 * @see send_out_reps()
 */
object create_rep() {
    object ob;

    ob = clone_object(NPC_OBJ);
    ob->set_name("rep");
    ob->set_short("sales rep");
    ob->add_adjective("sales");
    ob->set_long("This is tall strong looking sales rep.  He "
        "stares at you with bright piercing eyes.\n");
    ob->add_alias("Sales rep alias");
    ob->basic_setup("human", 60, 1 );
    ob->adjust_bon_str(15);
    ob->get_item("dagger", 100 );
    ob->get_item("cloth robe", 100 );
    ob->init_equip();
    ob->add_property("rep type", shop_type );
    INV(ob)->add_property("mine", 1 );

    return ob;

} /* create_rep() */

// Send out the reps.

/**
 * This method checks to see if there are any other shops
 * associated with ours and sends out representatives to them
 * to exchange goods.
 * @see create_rep()
 * @see add_other_shop()
 */
void send_out_reps() {
    int i;
    object ob;

    for( i = 0; i < sizeof(other_shops); i++ ) {
        ob = (object)TO->create_rep();
        ob->add_property("goto destination", other_shops[i]);
        ob->add_property("goto property", "shop");
        ob->move( TO, "$N stride$s determinedly into the room.");
        ob->add_triggered_action("froggy", "goto_destination",
            file_name(TO), "rep_made_it");
    }
} /* send_out_reps() */

/**
 * This method is called onces the representative
 * reaches its destination.
 * @param bing we mkde it ok
 */
void rep_made_it( int bing ) {
    object *obs, rep;
    int i, cost;

    if( !bing ) {
        PO->init_command("'Oh no!  I am utterly lost!");
        PO->init_command("sigh");
        call_out("set_up_return", 5, PO );
        return ;
    }
    rep = present("Sales rep alias", (object)PO->query_current_room() );
    obs = (PO->query_current_room())->query_stock(shop_type) || ({ });

    if( !sizeof(obs) ) {
        TO->none_to_sell();
        call_out("set_up_return", 5, PO );
        return ;
    }
    for( i = 0; i < sizeof(obs); i++ ) {
        if( obs[i] ) {
            cost += (int)TO->query_value( obs[ i ], 1 ) * 2 / 3;
        }
    }
    call_out("do_rep_buy", 5, ({ PO, obs, cost }) );
    cost += (int)TO->query_value(obs[i], 1) * 2 / 3;
    call_out("do_rep_buy", 5, ({ PO, obs, cost }) );
    PO->adjust_value(cost);
} /* rep_made_it() */

/**
 * This method creates a mercenaries to wander along with the
 * sales rep to get the stuff to the destination.
 * @param rep the representative to protect
 * @return the new mercenary
 */
object create_mercenary( object rep ) {
    object ob;
    string nam;

    if( rep ) {
        nam = implode(rep->query_adjectives(), " ")+" "+rep->query_name();
    }
    ob = clone_object(NPC_OBJ);
    ob->set_name("mercenary");
    ob->add_alias("troll");
    ob->add_adjective("troll");
    ob->set_short("troll mercenary");
    ob->set_main_plural("troll mercenaries");
    ob->basic_setup("troll", 200 + random( 200 ), 1 );
    ob->set_long("This is a large, hulking troll.  He looks "
        "quite competent and capable of mashing you with or "
        "without a weapon.\n");
    if( rep ) {
        ob->move(ENV(rep));
        ob->do_command("follow "+nam);
        ob->do_command("protect "+nam);
        ob->add_property("merchant", rep );
    } else {
        ob->get_item("spiked club", 100 );
    }
    ob->add_skill_level("fighting", 300 + random(100) );
    ob->set_natural(1);
    ob->init_equip();
    ob->set_join_fights("The troll mercenary yells something "
        "incomprehensible.\n");
    ob->set_join_fight_type(0); // So they only beat up players.

    return ob;

} /* create_mercenry() */

/* The shop types are:
 *   jewelery
 *   armoury
 *   clothes
 *   magic
 *   none set (ie: 0, general type)
 */
/**
 * This method returns alkl the stock in the shop of various
 * types.  The types are:
 * <ul>
 * <li>  jewelery
 * <li>  armoury
 * <li>  clothes
 * <li>  magic
 * <li>  none set (ie: 0, general type)
 * </ul>
 * @param type the type of stock to return
 * @return the array of objects of the type
 */
object *query_stock(string type) {
    mapping blue;

    blue = (mapping)our_storeroom->query_shop_type_mapping();
    if( !blue[type] )
        return ({ });
    return blue[type];
} /* query_stock() */

/**
 * This method is called when the rep tries to buy stuff.
 * @param bing the stuff to buy
 */
void do_rep_buy( mixed bing ) {
    object rep, *obs;
    int cost;

    rep = bing[0];
    obs = bing[1];
    cost = bing[2];
    rep->adjust_money(cost, "brass");
    rep->query_current_room()->do_buy(obs, cost, rep);
    obs->move( present("Sales rep alias", rep->query_current_room() ) );
    call_out("set_up_return", 5, rep);

} /* do_rep_buy() */

/**
 * This method sets up the rep to return home.
 * @param rep the rep to return home
 */
void set_up_return(object rep) {
    rep->add_property("goto destination", file_name(TO) );
    rep->add_triggered_action("froggy", "goto_destination", TO, "rep_came_back");
} /* set_up_return() */

/**
 * This method is called when the rep gets back home.
 */
void rep_came_back() {
    object *obs, *obs2, rep, ob;

    obs = PO->find_inv_match("all", PO );
    obs2 = ({ });
    foreach( ob in obs ) {
        if( ob->query_property("mine") || ob->query_property("money") )
            continue;
        ob->move(our_storeroom);
        obs2 += ({ ob });
    }

    if( sizeof(obs2) ) {
        tell_room( TO, PO->short()+" puts "+
            query_multiple_short(obs2)+" into the stock.\n");
    }

    rep = present("Sales rep alias", (object)PO->query_current_room() );
    obs = INV(TO);
    obs2 = ({ });

    foreach( ob in obs ) {
        if( (object)ob->query_property("merchant") == rep )
            obs2 += ({ ob });
    }

    if( sizeof(obs2) )
        tell_room( TO, query_multiple_short( obs2 + ({ PO }), "the")+" go "
            "away.\n");
    else
        tell_room( TO, PO->short()+" goes away.\n");

    obs2->dest_me();
    PO->dest_me();

} /* rep_came_back() */

// Used to create guards to protect the shop.

/**
 * This method is yused to handle guards to protect the shop.
 * @param tp the object which is shop lifting
 * @see event_shoplift()
 * @see set_shoplift_handler()
 */
void summon_guards( object tp ) {
    object ob;
    int i;

    if( ENV(tp) != TO )
        return;


    if( !ob = ENV(tp)->create_mercenary(0) )
        return;

    ob->move( TO, "$N charge$s in to protect the shop!");
    ob->attack_ob(tp);
    for( i = 0; i < random(5); i++ ) {
        ob = create_mercenary(0);
        ob->move( TO, "$N charge$s in to protect the shop!");
        ob->attack_ob(tp);
    }
} /* summon_guards() */

/**
 * This method is called when a shop lift is done on the shop.
 * @param command_ob the command object
 * @param thief the theif doing the shop lifting
 * @param victim the victim of the shoplifiting, us I guess :)
 * @see event_shoplift()
 * @see set_shoplift_handler()
 */
void event_shoplift( object command_ob, object thief, object victim ) {
    if( stringp(shoplift_handler) ) {
        if( shoplift_handler != "none" )
            theft_handler->handle_shoplift( thief, victim );
    } else if( functionp(shoplift_handler) )
        evaluate( shoplift_handler, thief, victim );
    else
        THEFT_H->handle_shoplift( thief, victim );
} /* event_shoplift() */

/**
 * This method is used by the shop to tidy up its inventory.
 * It does this by desting objects at random until it has
 * reduced the inventory to 3/4 of its maximum.
 */
void tidy_inventory() {
    object storeob;
    object *inventory;
    int i, inv_to_leave;
    int count;

    inv_to_leave = max_inventory - ( max_inventory / 4 );

    if( objectp(our_storeroom) )
        storeob = our_storeroom;
    else {
        our_storeroom->rabbit_away();
        storeob = find_object(our_storeroom);
    }

    if( sizeof( inventory = INV(storeob) ) < inv_to_leave )
        return;

    while( sizeof(inventory) > inv_to_leave ) {
        i = random( sizeof(inventory) );
        if( inventory[i] )
            inventory[i]->dest_me();
        // safety code.
        if( count++ > 500 )
            break;
        inventory = INV(storeob);
    }

} /* tidy_inventory() */

/** @ignore yes */
void dest_me() {
    if( our_storeroom )
        our_storeroom->dest_me();
    ::dest_me();
} /* dest_me() */

/** @ignore yes */
int query_keep_room_loaded() { return 1; }

/** @ignore yes */
mixed stats() {
    return ::stats()+({
      ({"total sold", amount_sold }),
      ({"total bought", amount_bought }),
      ({"shop type", shop_type }),
      ({"shoplift handler", shoplift_handler }),
      ({"strict shop", strict_shop })
      });
} /* stats() */