skylib_mudos_v1/
skylib_mudos_v1/bin/
skylib_mudos_v1/bin/db/
skylib_mudos_v1/mudlib/banish/a/
skylib_mudos_v1/mudlib/banish/b/
skylib_mudos_v1/mudlib/banish/c/
skylib_mudos_v1/mudlib/banish/d/
skylib_mudos_v1/mudlib/banish/e/
skylib_mudos_v1/mudlib/banish/f/
skylib_mudos_v1/mudlib/banish/g/
skylib_mudos_v1/mudlib/banish/h/
skylib_mudos_v1/mudlib/banish/j/
skylib_mudos_v1/mudlib/banish/l/
skylib_mudos_v1/mudlib/banish/m/
skylib_mudos_v1/mudlib/banish/n/
skylib_mudos_v1/mudlib/banish/o/
skylib_mudos_v1/mudlib/banish/p/
skylib_mudos_v1/mudlib/banish/r/
skylib_mudos_v1/mudlib/banish/s/
skylib_mudos_v1/mudlib/banish/t/
skylib_mudos_v1/mudlib/banish/u/
skylib_mudos_v1/mudlib/banish/w/
skylib_mudos_v1/mudlib/cmds/
skylib_mudos_v1/mudlib/cmds/admin/
skylib_mudos_v1/mudlib/cmds/guild-race/
skylib_mudos_v1/mudlib/cmds/guild-race/crafts/
skylib_mudos_v1/mudlib/cmds/guild-race/magic/
skylib_mudos_v1/mudlib/cmds/guild-race/other/
skylib_mudos_v1/mudlib/cmds/living/broken/
skylib_mudos_v1/mudlib/cmds/player/group_cmds/
skylib_mudos_v1/mudlib/d/admin/
skylib_mudos_v1/mudlib/d/admin/room/
skylib_mudos_v1/mudlib/d/admin/room/we_care/
skylib_mudos_v1/mudlib/d/admin/save/
skylib_mudos_v1/mudlib/d/admin/text/
skylib_mudos_v1/mudlib/d/learning/TinyTown/buildings/
skylib_mudos_v1/mudlib/d/learning/TinyTown/map/
skylib_mudos_v1/mudlib/d/learning/TinyTown/roads/
skylib_mudos_v1/mudlib/d/learning/chars/
skylib_mudos_v1/mudlib/d/learning/functions/
skylib_mudos_v1/mudlib/d/learning/handlers/
skylib_mudos_v1/mudlib/d/learning/help_topics/
skylib_mudos_v1/mudlib/d/learning/help_topics/npcs/
skylib_mudos_v1/mudlib/d/learning/help_topics/objects/
skylib_mudos_v1/mudlib/d/learning/help_topics/rcs_demo/
skylib_mudos_v1/mudlib/d/learning/help_topics/rcs_demo/RCS/
skylib_mudos_v1/mudlib/d/learning/help_topics/rooms/
skylib_mudos_v1/mudlib/d/learning/help_topics/rooms/crowd/
skylib_mudos_v1/mudlib/d/learning/help_topics/rooms/situations/
skylib_mudos_v1/mudlib/d/learning/save/
skylib_mudos_v1/mudlib/d/learning/school/
skylib_mudos_v1/mudlib/d/learning/school/add_sc/
skylib_mudos_v1/mudlib/d/learning/school/characters/
skylib_mudos_v1/mudlib/d/learning/school/general/
skylib_mudos_v1/mudlib/d/learning/school/getting-started/
skylib_mudos_v1/mudlib/d/learning/school/getting-started/basic_commands/
skylib_mudos_v1/mudlib/d/learning/school/getting-started/edtutor/
skylib_mudos_v1/mudlib/d/learning/school/getting-started/unix_tutor/
skylib_mudos_v1/mudlib/d/learning/school/items/
skylib_mudos_v1/mudlib/d/learning/school/npc_school/
skylib_mudos_v1/mudlib/d/learning/school/room_school/
skylib_mudos_v1/mudlib/d/learning/school/room_school/room_basic/
skylib_mudos_v1/mudlib/d/learning/school/room_school/situations/
skylib_mudos_v1/mudlib/d/learning/school/room_school/terrain_tutor/
skylib_mudos_v1/mudlib/d/learning/text/
skylib_mudos_v1/mudlib/d/liaison/
skylib_mudos_v1/mudlib/d/mudlib/
skylib_mudos_v1/mudlib/d/mudlib/changes/
skylib_mudos_v1/mudlib/d/playtesters/
skylib_mudos_v1/mudlib/d/playtesters/effects/
skylib_mudos_v1/mudlib/d/playtesters/handlers/
skylib_mudos_v1/mudlib/d/playtesters/items/
skylib_mudos_v1/mudlib/d/sage/
skylib_mudos_v1/mudlib/doc/
skylib_mudos_v1/mudlib/doc/creator/
skylib_mudos_v1/mudlib/doc/driver/
skylib_mudos_v1/mudlib/doc/driver/efuns/arrays/
skylib_mudos_v1/mudlib/doc/driver/efuns/buffers/
skylib_mudos_v1/mudlib/doc/driver/efuns/compile/
skylib_mudos_v1/mudlib/doc/driver/efuns/filesystem/
skylib_mudos_v1/mudlib/doc/driver/efuns/floats/
skylib_mudos_v1/mudlib/doc/driver/efuns/functions/
skylib_mudos_v1/mudlib/doc/driver/efuns/general/
skylib_mudos_v1/mudlib/doc/driver/efuns/mappings/
skylib_mudos_v1/mudlib/doc/driver/efuns/mixed/
skylib_mudos_v1/mudlib/doc/driver/efuns/mudlib/
skylib_mudos_v1/mudlib/doc/driver/efuns/numbers/
skylib_mudos_v1/mudlib/doc/driver/efuns/parsing/
skylib_mudos_v1/mudlib/doc/known_command/
skylib_mudos_v1/mudlib/doc/login/
skylib_mudos_v1/mudlib/doc/lpc/basic_manual/
skylib_mudos_v1/mudlib/doc/lpc/intermediate/
skylib_mudos_v1/mudlib/doc/new/add_command/
skylib_mudos_v1/mudlib/doc/new/events/
skylib_mudos_v1/mudlib/doc/new/handlers/
skylib_mudos_v1/mudlib/doc/new/living/race/
skylib_mudos_v1/mudlib/doc/new/living/spells/
skylib_mudos_v1/mudlib/doc/new/object/
skylib_mudos_v1/mudlib/doc/new/player/
skylib_mudos_v1/mudlib/doc/new/room/guild/
skylib_mudos_v1/mudlib/doc/new/room/outside/
skylib_mudos_v1/mudlib/doc/new/room/storeroom/
skylib_mudos_v1/mudlib/doc/object/
skylib_mudos_v1/mudlib/doc/playtesters/
skylib_mudos_v1/mudlib/doc/policy/
skylib_mudos_v1/mudlib/doc/weapons/
skylib_mudos_v1/mudlib/global/
skylib_mudos_v1/mudlib/global/creator/
skylib_mudos_v1/mudlib/global/handlers/
skylib_mudos_v1/mudlib/global/virtual/setup_compiler/
skylib_mudos_v1/mudlib/include/cmds/
skylib_mudos_v1/mudlib/include/effects/
skylib_mudos_v1/mudlib/include/npc/
skylib_mudos_v1/mudlib/include/room/
skylib_mudos_v1/mudlib/include/shops/
skylib_mudos_v1/mudlib/net/daemon/
skylib_mudos_v1/mudlib/net/daemon/chars/
skylib_mudos_v1/mudlib/net/inherit/
skylib_mudos_v1/mudlib/net/obj/
skylib_mudos_v1/mudlib/obj/amulets/
skylib_mudos_v1/mudlib/obj/b_day/
skylib_mudos_v1/mudlib/obj/clothes/
skylib_mudos_v1/mudlib/obj/dwarmours/plate/
skylib_mudos_v1/mudlib/obj/dwclothes/transport/horse/
skylib_mudos_v1/mudlib/obj/dwscabbards/
skylib_mudos_v1/mudlib/obj/dwweapons/axes/
skylib_mudos_v1/mudlib/obj/dwweapons/chains/
skylib_mudos_v1/mudlib/obj/faith/symbols/
skylib_mudos_v1/mudlib/obj/fungi/
skylib_mudos_v1/mudlib/obj/gatherables/
skylib_mudos_v1/mudlib/obj/instruments/
skylib_mudos_v1/mudlib/obj/magic/
skylib_mudos_v1/mudlib/obj/media/
skylib_mudos_v1/mudlib/obj/misc/player_shop/
skylib_mudos_v1/mudlib/obj/monster/godmother/
skylib_mudos_v1/mudlib/obj/monster/transport/
skylib_mudos_v1/mudlib/obj/rings/
skylib_mudos_v1/mudlib/obj/spells/
skylib_mudos_v1/mudlib/obj/stationery/
skylib_mudos_v1/mudlib/obj/stationery/envelopes/
skylib_mudos_v1/mudlib/obj/stationery/papers/
skylib_mudos_v1/mudlib/obj/toys/
skylib_mudos_v1/mudlib/obj/vessels/
skylib_mudos_v1/mudlib/obj/weapons/swords/
skylib_mudos_v1/mudlib/save/autodoc/
skylib_mudos_v1/mudlib/save/leaflets/
skylib_mudos_v1/mudlib/save/mail/
skylib_mudos_v1/mudlib/save/new_soul/data/
skylib_mudos_v1/mudlib/save/parcels/
skylib_mudos_v1/mudlib/save/playerinfo/
skylib_mudos_v1/mudlib/save/players/d/
skylib_mudos_v1/mudlib/save/random_names/
skylib_mudos_v1/mudlib/save/random_names/data/
skylib_mudos_v1/mudlib/save/terrains/
skylib_mudos_v1/mudlib/save/terrains/tutorial_desert/
skylib_mudos_v1/mudlib/save/terrains/tutorial_grassy_field/
skylib_mudos_v1/mudlib/save/terrains/tutorial_mountain/
skylib_mudos_v1/mudlib/save/todo_lists/
skylib_mudos_v1/mudlib/secure/
skylib_mudos_v1/mudlib/secure/cmds/admin/
skylib_mudos_v1/mudlib/secure/cmds/lord/
skylib_mudos_v1/mudlib/secure/config/
skylib_mudos_v1/mudlib/secure/handlers/autodoc/
skylib_mudos_v1/mudlib/secure/handlers/intermud/
skylib_mudos_v1/mudlib/secure/include/global/
skylib_mudos_v1/mudlib/secure/save/
skylib_mudos_v1/mudlib/secure/save/handlers/
skylib_mudos_v1/mudlib/secure/std/classes/
skylib_mudos_v1/mudlib/secure/std/modules/
skylib_mudos_v1/mudlib/std/commands/
skylib_mudos_v1/mudlib/std/commands/shadows/
skylib_mudos_v1/mudlib/std/creator/
skylib_mudos_v1/mudlib/std/dom/
skylib_mudos_v1/mudlib/std/effects/
skylib_mudos_v1/mudlib/std/effects/external/
skylib_mudos_v1/mudlib/std/effects/fighting/
skylib_mudos_v1/mudlib/std/effects/priest/
skylib_mudos_v1/mudlib/std/effects/room/
skylib_mudos_v1/mudlib/std/environ/
skylib_mudos_v1/mudlib/std/guilds/
skylib_mudos_v1/mudlib/std/guilds/old/
skylib_mudos_v1/mudlib/std/languages/
skylib_mudos_v1/mudlib/std/languages/BACKUPS/
skylib_mudos_v1/mudlib/std/liquids/
skylib_mudos_v1/mudlib/std/npc/
skylib_mudos_v1/mudlib/std/npc/goals/
skylib_mudos_v1/mudlib/std/npc/goals/basic/
skylib_mudos_v1/mudlib/std/npc/goals/misc/
skylib_mudos_v1/mudlib/std/npc/plans/
skylib_mudos_v1/mudlib/std/npc/plans/basic/
skylib_mudos_v1/mudlib/std/npc/types/
skylib_mudos_v1/mudlib/std/npc/types/helper/
skylib_mudos_v1/mudlib/std/npcs/
skylib_mudos_v1/mudlib/std/outsides/
skylib_mudos_v1/mudlib/std/races/shadows/
skylib_mudos_v1/mudlib/std/room/basic/topography/
skylib_mudos_v1/mudlib/std/room/controller/
skylib_mudos_v1/mudlib/std/room/inherit/topography/
skylib_mudos_v1/mudlib/std/room/topography/area/
skylib_mudos_v1/mudlib/std/room/topography/iroom/
skylib_mudos_v1/mudlib/std/room/topography/milestone/
skylib_mudos_v1/mudlib/std/shadows/curses/
skylib_mudos_v1/mudlib/std/shadows/disease/
skylib_mudos_v1/mudlib/std/shadows/fighting/
skylib_mudos_v1/mudlib/std/shadows/healing/
skylib_mudos_v1/mudlib/std/shadows/magic/
skylib_mudos_v1/mudlib/std/shadows/poison/
skylib_mudos_v1/mudlib/std/shadows/rituals/
skylib_mudos_v1/mudlib/std/shadows/room/
skylib_mudos_v1/mudlib/std/shops/controllers/
skylib_mudos_v1/mudlib/std/shops/objs/
skylib_mudos_v1/mudlib/std/shops/player_shop/
skylib_mudos_v1/mudlib/std/socket/
skylib_mudos_v1/mudlib/std/soul/
skylib_mudos_v1/mudlib/std/soul/d/
skylib_mudos_v1/mudlib/std/soul/e/
skylib_mudos_v1/mudlib/std/soul/i/
skylib_mudos_v1/mudlib/std/soul/j/
skylib_mudos_v1/mudlib/std/soul/k/
skylib_mudos_v1/mudlib/std/soul/l/
skylib_mudos_v1/mudlib/std/soul/n/
skylib_mudos_v1/mudlib/std/soul/o/
skylib_mudos_v1/mudlib/std/soul/q/
skylib_mudos_v1/mudlib/std/soul/u/
skylib_mudos_v1/mudlib/std/soul/v/
skylib_mudos_v1/mudlib/std/soul/y/
skylib_mudos_v1/mudlib/std/soul/z/
skylib_mudos_v1/mudlib/std/stationery/
skylib_mudos_v1/mudlib/w/
skylib_mudos_v1/mudlib/w/default/
skylib_mudos_v1/mudlib/w/default/armour/
skylib_mudos_v1/mudlib/w/default/clothes/
skylib_mudos_v1/mudlib/w/default/item/
skylib_mudos_v1/mudlib/w/default/npc/
skylib_mudos_v1/mudlib/w/default/room/
skylib_mudos_v1/mudlib/w/default/weapon/
skylib_mudos_v1/mudlib/www/
skylib_mudos_v1/mudlib/www/download/
skylib_mudos_v1/mudlib/www/java/
skylib_mudos_v1/mudlib/www/secure/
skylib_mudos_v1/mudlib/www/secure/lpc/advanced/
skylib_mudos_v1/mudlib/www/secure/lpc/intermediate/
skylib_mudos_v1/v22.2b14-DSv10/
skylib_mudos_v1/v22.2b14-DSv10/ChangeLog.old/
skylib_mudos_v1/v22.2b14-DSv10/Win32/
skylib_mudos_v1/v22.2b14-DSv10/compat/
skylib_mudos_v1/v22.2b14-DSv10/compat/simuls/
skylib_mudos_v1/v22.2b14-DSv10/include/
skylib_mudos_v1/v22.2b14-DSv10/mudlib/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/clone/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/command/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/data/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/etc/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/include/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/inherit/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/inherit/master/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/log/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/single/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/single/tests/compiler/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/single/tests/efuns/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/single/tests/operators/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/u/
skylib_mudos_v1/v22.2b14-DSv10/tmp/
skylib_mudos_v1/v22.2b14-DSv10/windows/
/**
 * The standard inheritable object for player-run shop offices.
 * This is the main object for player-run shops.  The majority of
 * player-shop functionality is handled from within this object.
 * See the header file for a complete description of the shop's
 * workings.
 * @see /include/player_shop.h
 * @see /std/shops/player_shop/mgr_office.c
 * @see /std/shops/player_shop/counter.c
 * @see /std/shops/player_shop/storeroom.c
 * @see /std/shops/player_shop/shop_front.c
 * @see /std/shops/player_shop/shopkeeper.c
 * @author Ringo
 * @started 1st August 1999
 */
#include <player_shop.h>
#include <mail.h>
#include <board.h>
#include <money.h>
#include <move_failures.h>
#include <refresh.h>

inherit ROOM_OBJ;

int do_claim( string );
int do_clock( string );
int do_bank( mixed * );
int do_logs( mixed *, string );
int do_chart( mixed *, string );
int do_promote( string );
void demote( string, string );
void save_hist();
void save_times();
void add_policy( string );
void remove_policy( string );
int do_policy_vote( string, string, string );
private void adjust_profit( string emp, int amount );

private nosave string _proprietor = "???",
               _shop_name = "???",
               _very_short = "???",
               _place = "???",
               _channel = "???",
               _storeroom = "",
               _shop_front = "",
               _counter = "",
               _mgr_office = "",
               _store_dir = "",
               _counter_dir = "",
               _shop_dir = "",
               _shopkeeper = "",
               _log_file = "",
               _stock_policy = "",
               _save_dir = SAVE_DIR,
               _creator = CREATOR;

private nosave object _chart = 0,
                      _board = 0,
                      _notice = 0;

private nosave mapping _history = ([]),
                       _list = ([]),
                       _times = ([]),
                       _employees = ([]);

private nosave int _call_save = 0,
                   _call_hist = 0,
                   _call_times = 0,
                   _call_hire_list = 0,
                   _call_mail_hirees = 0,
                   _call_summon = 0,
                   _npc_time = 0,
                   _call_emps = 0;

private string *_retired = ({}),
               *_got_bonus = ({}),
               _last_month = "last month";

private mapping _applicants = ([]),
                _baddies = ([]),
                _declined = ([]),
                _accounts = (["profit":0,"bonus":0]),
                _new_policies = ([]),
                _policies = ([]);

private int _max_emp = MAX_EMP,
            _bonus_val = 0,
            _bonus = 0,
            _pay_val = 4,
            _num_cabinets = MIN_CABINETS,
            _net_takings = 0;

private mixed *_register;


/** @ignore yes */
void create() {
   string ob_file;
   object this_ob;

   this_ob = this_object();
   ob_file = file_name( this_ob );
#ifdef DEBUG
   debug_printf( "Filename: %s\n", ob_file );
#endif
   seteuid( (string)master()->creator_file( ob_file ) );
   do_setup++;
   ::create();
   do_setup--;
   if ( !do_setup ) {
      this_ob->setup();
      this_ob->reset();
   }
   add_property( "determinate", "" );
   _chart = clone_object( SHOP_CHART );
   _chart->set_office( ob_file );
   add_hidden_object( _chart );
   _notice = clone_object( SHOP_NOTICE );
   _notice->set_office( ob_file );
   add_hidden_object( _notice );
#ifndef DEBUG
   REFRESH->register_refresh( ob_file );
   REFRESH->register_delete( ob_file );
#endif
} /* create() */


/**
 * Set the creator of this shop.
 * This person will receive all applications, complaints, suggestions
 * etc in the absence of any managers.  Default is set by CREATOR in
 * <player_shop.h>
 * @param creator The person responsible for this shop.
 */
void set_creator( string creator ) { _creator = creator; }


/**
 * Query the maintainer of this shop's files.
 * @return The person responsible for this shop.
 */
void query_creator( string creator ) { return copy( _creator ); }


/**
 * Determine if this person is an employee of the shop.
 * @param player The player to query.
 * @return The employee's points, or FALSE if not an employee.
 */
int query_employee( string player )
{
   if ( _employees[player] )
   {
      return copy(_employees[player][EMP_POINTS]);
   }
   return FALSE;
}
/* query_employee() */


/**
 * Determine if this person is a supervisor of this shop.
 * @param player The player to query.
 * @return TRUE or FALSE
 */
int query_supervisor( string player )
{
   if ( _employees[player] )
   {
      return ( _employees[player][EMP_POINTS] & SUPERVISOR );
   }
   return FALSE;
}
/* query_supervisor() */


/**
 * Determine if this person is a manager of this shop.
 * @param player The player to query.
 * @return TRUE or FALSE
 */
int query_manager( string player )
{
   if ( _employees[player] )
   {
      return ( _employees[player][EMP_POINTS] & MANAGER );
   }
   return FALSE;
}
/* query_manager() */


/**
 * Determine if this person is a retired manager of this shop.
 * @param player The player to query.
 * @return TRUE or FALSE
 */
int query_retired( string player )
{
    return ( member_array( player, _retired ) == -1 )?FALSE:TRUE;
}
/* query_retired() */


/**
 * Determine if this person has applied for a job.
 * @param player The player to query.
 * @return APPLIED if applied, HIRED if voted in, AWAITING if awaiting
 * a vacancy.  Otherwise, will return FALSE.
 */
int query_applicant( string player )
{
    if ( !sizeof( _applicants ) )
    {
       return FALSE;
    }
    if ( _applicants[player] )
    {
       return copy(_applicants[player][APP_TYPE]);
    }
    return FALSE;
}
/* query_applicant() */


/**
 * Determine if this person is banned from the shop.
 * @param player The player to query.
 * @return The time of the ban if banned, FALSE if not banned.
 */
int query_baddie( string player )
{
   if ( !m_sizeof( _baddies ) )
   {
      return FALSE;
   }
   if ( _baddies[player] )
   {
      return copy(_baddies[player][BAD_TIME]);
   }
   return FALSE;
}
/* query_baddie() */


/** @ignore yes */
void init() {
   string word;
   object tp;

   ::init();
   tp = this_player();
   word = tp->query_name();

   /*
    * Only employees & creators should have access to shop commands.
    */
   if( !creatorp(tp) && !_employees[word] &&
       ( member_array( word, _retired ) == -1 ) ) {
       return;
   }

   add_command("claim", "{badge|handbook|bonus}", (: do_claim($4[0]) :) );
   add_command("resign", "");
   add_command("list", "");
   add_command("office", "");

   /*
    * If there is no board set up for this shop, all comms done by mail.
    */
   if( !_board )
       add_command( "memo", "" );

   /*
    * Retired managers have no need for following commands.
    */
   if( member_array( word, _retired ) != -1 )
       return;

   add_command("clock", "{in|out}", (: do_clock($4[0]) :) );
   add_command("bank", ({ "", "<number>" }), (: do_bank($4) :) );
   if( !tp->query_property( "no score" ) )
       add_command("promotion", "{on|off}", (: do_promote($4[0]) :) );

   /*
    * Only supervisors & managers have access to following commands.
    */
   if( !creatorp(tp) && !( _employees[word][EMP_POINTS] & SUPERVISOR ) )
       return;

   add_command("chart", ({ "add <string'item'>", "remove <string'item'>",
       "max <string'item'> <number>", "buy <string'item'> <number>",
       "sell <string'item'> <number>",
       "assign <string'item'> [to cabinet] <number>",
       "unassign <string'item'> [from cabinet] <number>" }),
       (: do_chart($4, $5) :) );
   add_command("check", "cabinets");

   if( !creatorp(tp) && !( _employees[word][EMP_POINTS] & MANAGER ) )
       add_command("logs", ({ "", "<number>", "[chart]" }),
           (: do_logs($4,$5) :) );
   else
       add_command("logs", ({ "", "<number>",
           "{personnel|accounts|chart}" }), (: do_logs($4,$5) :) );

} /* init() */


/**
 * @ignore yes
 * Saves the employee data.
 */
void do_save_emps() {
#ifdef DEBUG
   debug_printf( "Saving employee data to %s.\n",
     _save_dir + _very_short+ ".employees" );
#endif
   unguarded( (: write_file, _save_dir + _very_short+ ".employees",
     save_variable( _employees ), 1 :) );
}
/* do_save_emps() */

/**
 * Save the data file.
 * This method uses a call_out to help minimise the amount of disk activity
 * during normal operations of the shop.
 */
protected void save_emps() {
   if( _very_short == "???" )
       return;
   remove_call_out( _call_emps );
   _call_emps = call_out( "do_save_emps", SAVE_DELAY );
}
/* save_emps() */

/**
 * @ignore yes
 * Save this object immediately.
 */
void do_save() {
#ifdef DEBUG
   debug_printf( "Saving shop data to %s.\n",
     _save_dir + _very_short );
#endif
   unguarded( (: save_object, _save_dir + _very_short :) );
}
/* do_save() */

/**
 * Save the data file.
 * This method uses a call_out to help minimise the amount of disk activity
 * during normal operations of the shop.
 */
protected void save_me() {
   if( _very_short == "???" )
       return;
   remove_call_out( _call_save );
   _call_save = call_out( "do_save", SAVE_DELAY );
}
/* save_me() */


/**
 * @ignore yes
 * Saves the employment last-action times data.
 */
void save_list() {
   if ( _very_short == "???" )  {
      return;
   }
#ifdef DEBUG
   debug_printf( "Saving chart list data to %s.\n",
     _save_dir+ _very_short+ ".list" );
#endif
   unguarded( (: write_file, _save_dir + _very_short+ ".list",
     save_variable( _list ), 1 :) );
}
/* do_save_list() */


/**
 * @ignore yes
 * Make sure all data is saved before desting this object.
 */
void dest_me() {
   if( _very_short != "???" ) {
       remove_call_out( _call_save );
       remove_call_out( _call_hist );
       remove_call_out( _call_times );
       remove_call_out( _call_emps );
       save_hist();
       save_times();
       do_save_emps();
       do_save();
   }
   if( _chart )
       _chart->dest_me();
   if( _board )
       _board->dest_me();
   if( _notice )
       _notice->dest_me();

   ::dest_me();
} /* dest_me() */

/**
 * Query number of 'items' in stock.
 */
int query_stock( string items ) {
   return _storeroom->query_num_items( items, 0 );
} /* query_stock() */

/**
 * @ignore yes
 * Saves the employment last-action times data.
 */
void save_times() {
   if( _very_short == "???" )
       return;
#ifdef DEBUG
   debug_printf( "Saving times data to %s.\n",
     _save_dir + _very_short+ ".times" );
#endif
   unguarded( (: write_file, _save_dir + _very_short+ ".times",
     save_variable( _times ), 1 :) );
}
/* save_times() */

/**
 * @ignore yes
 * Sets the last action time of an employee.
 * This time is the last time an employee did something worth
 * recording and is used to determine if they are inactive.
 * @param employee The employee.
 */
private void set_emp_time( string employee )
{
   if ( !_employees[employee] )
   {
      return;
   }
   _employees[employee][EMP_TIME] = time();  // Set last-action time
   _employees[employee][EMP_INACTIVE] = 0;   // Reset inactivity flag
   if ( !sizeof( _times ) )
   {
      _times = ([ employee:0 ]);
   }
   else if ( !_times[employee] )
   {
      _times += ([ employee:0 ]);
   }
   _times[employee] = time();   // Update employee record
   remove_call_out( _call_times );
   _call_times = call_out( "save_times", PERS_DELAY );
   save_emps();
}
/* set_emp_time() */


/**
 * @ignore yes
 * This function adds an entry to the logs & pays employees.
 * Logs record a full day's (3 DW days) activity, apart from the
 * chart, accounts & personnel logs which use log_file.<br>
 * @param logtype The type of log entry - see <player_shop.h>
 * @param word The employee making the entry.
 * @param words The log entry text.
 */
void shop_log( int logtype, string word, string words ) {
   string date, file, month, colour, summary, item, sign;

   sscanf( mudtime(time()), "%*s %*s %*s %s", month );
   if ( file_size( _log_file +"general.log" ) > 0 ) {
      date = ctime( time() )[ 0 .. 9 ];

      /*
       * Are we on a new day yet?  If so, do the daily review.
       */
      if ( date != unguarded( (: read_file,
        _log_file +"general.log", 1, 1 :) )[ 0 .. 9 ] ) {
         _storeroom->force_load();
         call_out( "update_averages", 60 );
         file = _log_file +"general.log-"+ time();
         unguarded( (: rename, _log_file +"general.log", file :) );
         call_out( "review_employees", 5 );
         summary = sprintf( "%sFor the day ending %s:%s\n",
           "%^BOLD%^", mudtime(time()), "%^RESET%^" );
         foreach ( item in m_indices( _list ) ) {
            summary += sprintf( " %s - Bought %d, Sold %d\n", capitalize(item),
              _list[item][CHART_BOUGHT], _list[item][CHART_SOLD] );
           _list[item][CHART_AVESALE] = ( _list[item][CHART_AVESALE] +
             _list[item][CHART_SOLD] + random(2) ) / 2;
           _list[item][CHART_SOLD] = 0;
           _list[item][CHART_BOUGHT] = 0;
         }
         save_list();
         sign = "";
         if ( _net_takings < 0 )
         {
            _net_takings = -_net_takings;
            sign = "-";
         }
         summary += sprintf( "%sThe net takings of the shop were %s%s.%s\n\n",
           "%^BOLD%^", sign, MONEY_HAND->money_value_string( _net_takings,
           _place ), "%^RESET%^" );
         _net_takings = 0;
         save_me();
         unguarded( (: write_file, _log_file +"general.log",
           date +"\n" + summary :) );

         /*
          * Remove old logs.
          */
         foreach ( file in get_dir( _log_file +"general.log-*" ) )
         {
            sscanf( file, "%*s-%s", date );
            if ( time() - to_int( date ) > (60*60*24*LOG_DAYS) )
            {
               unguarded( (: rm, _log_file + "general.log-"+ date :) );
            }
         }
      }
   }

   /*
    * Are we in a new month yet?  If so do the monthly review.
    */
   if ( month != _last_month )
   {
      if ( member_array( month, ({ "Offle", "February", "March",
        "April", "May", "June", "Grune", "August", "Spune",
        "Sektober", "Ember", "December", "Ick" }) ) != -1 )
      {
         call_out( "monthly_review", 60, month);
      }
   }

   /*
    * Only pay employees if clocked in & this action is at least
    * 1 minute after the previous paid action.
    */
   if ( _employees[word] && !(_employees[word][EMP_POINTS] & NPC) )
   {
      if ( _employees[word][EMP_POINTS] & CLOCKED_IN &&
        _employees[word][EMP_TIME] < ( time() - 60 ) )
      {
         if ( _employees[word][EMP_POINTS] & MANAGER )
         {
            _employees[word][EMP_PAY] += ( _pay_val * 2 );
         }
         else if ( _employees[word][EMP_POINTS] & SUPERVISOR )
         {
            _employees[word][EMP_PAY] += to_int( _pay_val * 1.5 );
            _employees[word][EMP_POINTS] += 32;
         }
         else
         {
            _employees[word][EMP_PAY] += _pay_val;
            _employees[word][EMP_POINTS] += 32;
         }
         set_emp_time( word );
      }
   }

   /*
    * Write entry to the appropriate log.
    */
   switch ( logtype ) {
     case PURCHASE :
        colour = "%^GREEN%^";
        break;
     case SALE :
        colour = "%^RED%^";
        break;
     case GENERAL :
        colour = "%^CYAN%^";
        break;
     case PERSONNEL :
        log_file( _log_file+ "personnel.log", "%s: %s - %s\n",
          mudtime(time()), capitalize( word ), words );
        return;
        break;
     case ACCOUNTS :
        log_file( _log_file+ "accounts.log", "%s: %s - %s\n",
          mudtime(time()), capitalize( word ), words );
        return;
        break;
     case CHARTLOG :
        log_file( _log_file+ "chart.log", "%s: %s - %s\n",
          mudtime(time()), capitalize( word ), words );
        return;
        break;
     default :
        colour = "%^RESET%^";
        break;
   }
   unguarded( (: write_file, _log_file +"general.log", colour + mudtime(time())+
     ": "+ capitalize( word ) +"%^RESET%^ - "+ words +"\n" :) );
}
/* shop_log() */


/**
 * @ignore yes
 * Writes a message to the shop's board if it exists.  If not, will
 * send a mail to each employee.  This message is "written" by the shop's
 * proprietor.
 * @param subject Take a guess ;-)
 * @param post See subject...
 */
private void add_board_message( string subject, string post )
{
   string employee, *employees;

   if ( _board )
   {
#ifdef DEBUG
      debug_printf( "Posting message %s to board %s.\n", subject,
        _channel );
#endif
      BOARD_HAND->add_message( _channel, _proprietor,
        subject, post + "--\n"+ _proprietor+ " (proprietor)" );
   }
   else
   {
      employees = _retired;
      foreach( employee in m_indices( _employees ) )
      {
         if ( !( _employees[employee][EMP_POINTS] & NPC ) )
         {
            employees += ({ employee });
         }
      }
      if ( !sizeof( employees ) )
      {
#ifdef DEBUG
         debug_printf( "No employees to send mail to.\n" );
#endif
         return;
      }
      employees += ({ CREATOR });
#ifdef DEBUG
      debug_printf( "Sending mail %s to all employees.\n", subject );
#endif
      AUTO_MAILER->auto_mail( implode( employees, "," ), _proprietor,
           subject, "", post );
   }
}
/* add_board_message() */


/**
 * @ignore yes
 * Send a memo to each employee.  Only used if there is no board.
 */
int do_memo()
{
   this_player()->do_edit( 0, "end_memo" );
   add_succeeded_mess( "" );
   return 1;
}
/* do_memo() */


/** @ignore yes */
void end_memo( string text )
{
   string *employees;
   if ( !text )
   {
      tell_object( this_player(), "Aborted.\n" );
#ifdef DEBUG
      debug_printf( "No message text.  Aborting.\n" );
#endif
      return;
   }
   employees = _retired;
   if ( creatorp(TP) )
   {
      employees += ({ TP->query_name() });
   }
   foreach( string employee in m_indices( _employees ) )
   {
      if ( !( _employees[employee][EMP_POINTS] & NPC ) )
      {
         employees += ({ employee });
      }
   }
   tell_object( this_player(),
     "Do you want to keep a copy of the memo? " );
   input_to( "send_memo", 0, text, employees );
}
/* end_memo() */


/** @ignore yes */
void send_memo( string ans, string text, string *employees )
{
   ans = lower_case( ans );
   if ( strlen(ans) < 1 || ( ans[0] != 'y' && ans[0] != 'n' ) )
   {
      tell_object( TP,
        "Do you want to keep a copy of the memo? (Yes or No)? " );
      input_to( "send_memo", 0, text, employees );
      return;
   }
   if ( !sizeof( employees ) )
   {
#ifdef DEBUG
      debug_printf( "No employees to send mail to.\n" );
#endif
      tell_object( TP, "There is no-one to send a memo to!\n" );
      return;
   }
   if(ans[0] == 'n') employees -= ({ TP->query_name() });
#ifdef DEBUG
   debug_printf( "Sending employee memo to %s.\n",
     implode( employees, "," ) );
#endif
   tell_object( TP, "Sending your memo.\n" );
   AUTO_MAILER->auto_mail( implode( employees, "," ),
     TP->query_name(), _very_short+
     " employee memo", "", text, 0, 0 );
}
/* send_memo() */


/**
 * Return the employee data.
 * This includes only active employees - retired managers are not included.
 * The data is formatted as:<br>
 * ([ employee:({ points, time, bank, pay, inactive, nobonus, nopromote }) ])<br>
 * @return The employee mapping, formatted as above.
 */
mapping query_employees() { return copy(_employees + ([ ])); }


/**
 * @ignore yes
 * Displays the list of commands available to each employee.
 */
int do_office() {
   int employee;
   object tp;
   string tp_name, board_mess = "";

   if( !_board )
       board_mess = "   memo     - send a memo to the other employees\n";

   add_succeeded_mess( "" );
   tp = this_player();
   tp_name = tp->query_name();

   if( member_array( tp_name, _retired ) != -1 ) {
       tell_object( tp, "As a retired manager, you can use the following "
         "commands:\n"
         "   claim    - claim a badge or handbook\n"
         "   list     - list the other employees of the shop\n"
         "   logs     - review the shop's logs\n"
         "   resign   - terminate your association with Tarnach's\n"+
         board_mess+
         "You may also enter the managers' office for more commands.\n" );
       return 1;
   }

   employee = ( creatorp(tp) ? SUPERVISOR + MANAGER :
               _employees[tp_name][EMP_POINTS] );

   tell_object( tp, "As an employee, you can use the following commands:\n"
     "   clock    - start or finish your working day\n"+
     "   claim    - claim a badge, handbook, or this month's bonus\n"
     "   bank     - set to which bank account you are paid\n"
     "   list     - list the other employees of the shop\n"
     "   resign   - end your employment at the shop\n"+
     board_mess );

   if( employee & SUPERVISOR ) {
      tell_object( tp, "As a supervisor, you can also use:\n"
        "   chart  - change the information on the sales chart\n"
        "   check  - check cabinet assignments\n"
        "   logs   - review the shop's logs\n" );
   }

   if( employee & MANAGER ) {
      tell_object( tp, "As a manager you can enter the managers' "
        "office and\nuse the commands listed there.\n" );
   }

   return 1;

} /* do_office() */


/**
 * @ignore yes
 * Adds a new employee.
 * Also prevent the employee from receiving a bonus until they have
 * worked a full month by setting EMP_NOBONUS to 1.
 */
void add_employee( string player ) {
   if( !_employees[player] ) {
       _employees += ([ player: ({ 1, 0, 0, 0, 0, 1, 0 }) ]);
       set_emp_time( player );
       _got_bonus += ({ player });
       save_me();
   }
} /* add_employee() */

/**
 * @ignore yes
 * Remove this person from the retired managers array.
 * Used when a retired manager resigns.
 */
private void remove_retired( string retired ) {
   retired = lower_case( retired );
   if( member_array( retired, _retired ) == -1  )
       return;
   _retired -= ({ retired });
   save_me();
} /* remove_retired() */

/**
 * @ignore yes
 * Remove this person from the applicants mapping.
 * Used when an applicant is hired, declined, or cancels their
 * application.
 */
private void remove_applicant( string applicant ) {
   applicant = lower_case( applicant );
   if( query_applicant( applicant ) ) {
       map_delete( _applicants, applicant );
       save_me();
   }
} /* remove_applicant() */

/**
 * @ignore yes
 * Remove this person from the employees mapping.
 * Used when an employee is fired, resigns, or retires.
 */
private void remove_employee( string employee ) {
   employee = lower_case( employee );
   if( _employees[employee] ) {
       map_delete( _employees, employee );
       save_emps();
   }
   remove_applicant( employee );
   remove_retired( employee );

   /* Check to see if anyone should be hired */
   remove_call_out( _call_hire_list );
   _call_hire_list = call_out( "check_hire_list", 5 );
} /* remove_employee() */

/**
 * @ignore yes
 * Used to check access to the managers' office.
 */
int check_manager( string action ) {
   object tp = this_player();
   string tp_name = tp->query_name();

   if( creatorp(tp) || (_employees[tp_name][EMP_POINTS] & MANAGER) ||
       ( member_array( tp_name, _retired ) != -1 ) )
       return 1;

   return notify_fail( "You are not a manager here!\n" );

} /* check_manager() */

/**
 * Query the list of retired managers.
 * This method returns the list of retired managers, sorted
 * alphabetically.
 * @return The sorted array of retired managers.
 */
string *get_retired() {
   return copy( sort_array( _retired, 1 ) );
} /* get_retired() */

/**
 * Query the list of managers.
 * This method returns the list of managers, sorted alphabetically.
 * @return The sorted array of managers.
 */
string *get_managers() {
   return copy( sort_array( keys( filter( _employees,
          (: _employees[$1][EMP_POINTS] & MANAGER :) ) ), 1 ) );
} /* get_managers() */

/**
 * Query the list of supervisors.
 * This method returns the list of supervisors, sorted
 * alphabetically.
 * @return The sorted array of supervisors.
 */
string *get_supervisors() {
   string word, *supervisors;

   supervisors = keys( filter( _employees,
        (: _employees[$1][EMP_POINTS] & SUPERVISOR :) ) );

   /* Managers also have the supervisor bit set, so don't
    * include them in this list.
    */
   foreach( word in supervisors ) {
      if( _employees[word][EMP_POINTS] & MANAGER )
          supervisors -= ({ word });
   }

   return copy( sort_array( supervisors, 1 ) );

} /* get_supervisors() */

/**
 * Query the list of employees.
 * This method returns the list of employees, sorted alphabetically.
 * @return The sorted array of employees.
 */
string *get_employees() {
   string word, *employees;

   employees = keys( _employees );

   /* Don't include people with the supervisor bit set */
   foreach( word in employees ) {
      if( _employees[word][EMP_POINTS] & SUPERVISOR )
          employees -= ({ word });
   }

   return copy( sort_array( employees, 1 ) );
} /* get_employees() */

/**
 * @ignore yes
 * Used by the shop front to add this person to the applicants mapping.
 */
void add_applicant( string player ) {
   player = lower_case( player );
   if ( !query_applicant( player ) )
   {
      if ( !sizeof( _applicants ) )
      {
         _applicants = ([ player:({ APPLIED,0,({}),({}), ({}), }) ]);
      }
      else
      {
         _applicants += ([ player:({ APPLIED,0,({}),({}), ({}), }) ]);
         _applicants[player][APP_TIME] = time();
      }
      save_me();
   }
}
/* add_applicant() */


/**
 * Return the list of applicants.
 * This method returns the list of applicants as a mapping formatted
 * as follows:<br>
 * ([ "name": type, time, ({ for }), ({ against }), ({ abstain }), ])
 * @return The applicants mapping formatted as above.
 */
mapping get_applicants() { return copy(_applicants + ([ ])); }


/**
 * Determine if a player was declined for a job.
 * This method is used to determine if there is a declined
 * application registered for a player.
 * @param player The player to query.
 * @return FALSE, or the time at which the applicant was declined.
 */
int query_declined( string player )
{
   if ( !sizeof( _declined ) )
   {
      return FALSE;
   }
   if ( _declined[player] )
   {
      return copy(_declined[player]);
   }
   return FALSE;
}
/* query_declined() */


/**
 * @ignore yes
 * Add this person to the list of declined applicants.  Used by
 * the shop front to determine if this person can apply again
 * yet.
 */
private void add_declined( string applicant )
{
   applicant = lower_case( applicant );
   if ( !query_declined( applicant ) )
   {
      if ( !sizeof( _declined ) )
      {
         _declined = ([applicant:0]);
      }
      else
      {
         _declined += ([applicant:0]);
      }
      _declined[applicant] = time();
      save_me();
   }
}
/* add_declined() */


/**
 * @ignore yes
 * Remove this person from the list of declined applicants,
 * allowing them to re-apply.
 */
private void remove_declined( string declined )
{
   declined = lower_case( declined );
   if ( query_declined( declined ) )
   {
      map_delete( _declined, declined );
      save_me();
   }
}
/* remove_declined() */


/**
 * @ignore yes
 * Saves the employment history data.
 */
void save_hist()
{
   if ( _very_short == "???" )
   {
      return;
   }
#ifdef DEBUG
   debug_printf( "Saving history data to %s.\n",
     _save_dir + _very_short+ ".history" );
#endif
   unguarded( (: write_file, _save_dir + _very_short+ ".history",
     save_variable( _history ), 1 :) );
}
/* save_hist() */


/**
 * @ignore yes
 * Adds an entry to the employee's history.
 * This method is intended to log relevant personnel issues such as
 * applications, hirings, promotions etc.  The normal day-to-day
 * stuff is logged in the shop's logs themselves.
 * @param shop The shop very short name.
 * @param employee The employee.
 * @param note The note to log.
 */
void employee_log( string employee, string note )
{
   if ( !sizeof(_history) )
   {
      _history = ([ employee:({ ({}),({}), }) ]);
   }
   else if ( !_history[employee] )
   {
      _history += ([ employee:({ ({}),({}), }) ]);
   }
   _history[employee][0] += ({ time() });
   _history[employee][1] += ({ note });
   remove_call_out( _call_hist );
   _call_hist = call_out( "save_hist", PERS_DELAY );
   if ( !sizeof( _times ) )
   {
      _times = ([ employee:0 ]);
   }
   else if ( !_times[employee] )
   {
      _times += ([ employee:0 ]);
   }
   _times[employee] = time();   // Update employee record
   remove_call_out( _call_times );
   _call_times = call_out( "save_times", PERS_DELAY );
}
/* employee_log() */


/**
 * @ignore yes
 * Used when applicant has sufficient supporting votes to be
 * accepted.
 */
private void hire( string word )
{
   int gender;
   string obj, poss, subj;

   remove_applicant( word );

   /* Do not hire if not a user, already an employee, or banned */
   if ( !"/secure/login"->test_user( word ) || _employees[word] ||
     query_baddie( word ) )
   {
        return;
   }

   add_employee( word );
   employee_log( word, "Hired" );
   shop_log( PERSONNEL, _proprietor, "hired "+ capitalize( word ) );
   AUTO_MAILER->auto_mail( word, _proprietor, _shop_name, "",
     "Congratulations!  You've been hired to work at "+ _shop_name+
     ".  You'll find that you can now move through the counter "
     "to the back areas of the shop.  The first things you should "
     "do are \"claim\" a new badge and staff handbook.\n" );
   gender = "/secure/login"->test_gender(word);
   obj = ({ "it", "him", "her" })[gender];
   poss = ({ "its", "his", "her" })[gender];
   subj = ({ "it", "he", "she" })[gender];
   add_board_message( "New employee", capitalize( word )+
     " has today been employed to work for the shop.  Please make " +
     obj + " feel welcome, and assist " + obj + " while " + subj + " gets "
     "started in " + poss + " new position.\n" );

   /* Update the other accepted applicants */
   remove_call_out( _call_mail_hirees );
   _call_mail_hirees = call_out( "mail_hirees", 5 );
}
/* hire() */


/**
 * @ignore yes
 * Check to see if we should hire any more accepted applicants.
 */
void check_hire_list()
{
   int count, x;
   string word;
   string *hirees;
   hirees = ({});

   /* Applicant has not confirmed employment after being accepted
    * so remove them.
    */
   foreach ( word in m_indices( filter( _applicants,
     (: _applicants[$1][APP_TYPE] == HIRED :) ) ) )
   {
      if ( time() - _applicants[word][APP_TIME] > HIRE_TIMEOUT )
      {
         AUTO_MAILER->auto_mail( word, _proprietor,
           _shop_name, "", "Since you have not returned to confirm "
           "your employment with us, we have assumed that you are no "
           "longer interested, and removed your name from our files.  "
           "If you wish to re-apply at any time in the future, please "
           "return to the shop to do so.\n" );
         employee_log( word, "Lapsed their application" );
         remove_applicant( word );
      }
   }

   foreach ( word in m_indices( filter( _applicants,
     (: _applicants[$1][APP_TYPE] == APPLIED :) ) ) )
   {
      if ( time() - _applicants[word][APP_TIME] > HIRE_TIMEOUT )
      {
         if ( sizeof( _applicants[word][APP_FOR] ) <
           sizeof( _applicants[word][APP_AGAINST] ) )
         {
            /* Applicant has more 'no' votes than 'yes' votes so
             * decline them.
             */
            remove_applicant( word );
            AUTO_MAILER->auto_mail( word, _proprietor, _shop_name, "",
              "Thank you for your recent application for employment "
              "with us.  Unfortunately, I have to inform you that you "
              "have been unsuccessful at this time.  Please feel free "
              "to re-apply again in the future, when your application "
              "will be re-considered.\n" );
            employee_log( word, "Application was declined" );
            add_declined( word );
         }
         else
         {
            /* Applicant has at least as many 'yes' votes as 'no' votes
             * so accept them.
             */
            AUTO_MAILER->auto_mail( word, _proprietor, _shop_name, "",
              "Congratulations!  You've been accepted to work at "+
              _shop_name+ ".  Please return to the shop within the next "
              "seven days to \"confirm\" that you wish to accept "
              "the position.\n" );
            _applicants[word][APP_TYPE] = HIRED;
            _applicants[word][APP_TIME] = time();
            employee_log( word, "Application was accepted" );
         }
      }
   }

   /* Number of places available */
   count = _max_emp - sizeof( _employees );

   if ( count < 0 )
   {
      return;
   }
   TCRE("shiannar", count+"");
   hirees = sort_array( keys( filter( _applicants,
     (: _applicants[$1][APP_TYPE] == AWAITING :) ) ),
     (: _applicants[$1][APP_TIME] - _applicants[$2][APP_TIME] :) );

   /* See if we can hire all people awaiting a vacancy */
   if ( count > sizeof( hirees ) )
   {
      count = sizeof( hirees );
   }

   /* Hire as many people as we can */
   for ( x = 0; x < count; x++ )
   {
      hire( hirees[x] );
   }
   save_me();
}
/* check_hire_list() */

/**
 * @ignore yes
 * Send a mail to each applicant awaiting a vacancy to let them know
 * their position in the queue.
 */
void mail_hirees() {
   int x;
   string *hirees;

   /* Make sure we are mailing the correct person with the correct position
    * by sorting the list by time.
    */
   hirees = sort_array( keys( filter( _applicants,
     (: _applicants[$1][APP_TYPE] == AWAITING :) ) ),
     (: _applicants[$1][APP_TIME] - _applicants[$2][APP_TIME] :) );

   for( x = 0; x < sizeof( hirees ); x++ ) {
      AUTO_MAILER->auto_mail( hirees[x], _proprietor, _shop_name, "",
        "I am writing to inform you that you have now moved to position "+
        query_num( x + 1 )+ " in the employment waiting list.  If you "
        "are not near the top of the list, please be patient.\nThank you.\n");
   }

} /* mail_hirees() */

/**
 * @ignore yes
 * Employee bits are set upon promotion, or when clocking in.
 */
private void set_employee( string word, int bit ) {
   if( !_employees[word] )
       return;

   if( bit < EMPLOYEE || bit > CLOCKED_IN ) {
#ifdef DEBUG
       debug_printf("Trying to set an employee bit < %d || > %d\n",
                    EMPLOYEE, CLOCKED_IN );
#endif
       return;
   }

   _employees[word][EMP_POINTS] |= bit;
   save_emps();

} /* set_employee() */

/**
 * @ignore yes
 * Employee bits are reset on clocking out.
 */
void reset_employee( string word, int bit ) {
   if( !_employees[word] )
       return;

   if( bit < EMPLOYEE || bit > CLOCKED_IN ) {
#ifdef DEBUG
       debug_printf("Trying to reset an employee bit < %d || > %d\n",
                    EMPLOYEE, CLOCKED_IN );
#endif
      return;
   }

   _employees[word][EMP_POINTS] -= _employees[word][EMP_POINTS] & bit;
   save_emps();
} /* reset_employee() */


/**
 * Query the list of items sold by the shop.
 * @return The list of items, formatted nicely in a multiple_short string.
 */
string query_list_string() {
   if( !sizeof( _list ) )
       return "absolutely nothing at the moment";

   return query_multiple_short( keys( _list ) );
} /* query_list_string() */

/**
 * Query the list of items sold by the shop.
 * @return The list of items in array form.
 */
string *query_list_array() { return copy(m_indices( _list ) + ({ })); }

/**
 * Query the list of items sold by the shop.
 * @return The list of items in mapping form.
 */
mapping query_list_mapping() { return copy( _list ) + ([]); }

/**
 * @ignore yes
 * Used by the shop front when an applicant confirms their employment
 * after being accepted.
 */
private void confirm_employment( string applicant )
{
   applicant = lower_case( applicant );
   _applicants[applicant][APP_TYPE] = AWAITING;
   _applicants[applicant][APP_TIME] = time();
   tell_object( find_player( applicant ), "You have now been added to "
     "our waiting list.  You will be notified of your position in the "
     "list as it changes.  You may \"cancel\" your application at "
     "any time.  You are currently at position " +
     sizeof( m_indices( filter( _applicants,
     (: _applicants[$1][APP_TYPE] == AWAITING :) ) ) )+
     " in the waiting list.\n" );
   save_me();
}
/* confirm_employment() */


/**
 * @ignore yes
 * Used when employees are fired by managers, or automatically.
 */
private void fire_them( string word, string them, string reason )
{
   if ( !_employees[them] )
   {
      return;
   }
   BANK->adjust_account( them, BANKS[_employees[them][EMP_BANK]][1],
     _employees[them][EMP_PAY] );
   shop_log( ACCOUNTS, _proprietor, "paid "+
     MONEY_HAND->money_value_string( _employees[them][EMP_PAY], _place )+
     " to "+ capitalize( them ) );
   shop_log( PERSONNEL, word, "fired "+ capitalize( them ) +
     " for "+ reason );
   AUTO_MAILER->auto_mail( them, word, _shop_name, "",
      "Unfortunately, I have to inform you that you have today "
      "been fired for " + reason + ".  You have been paid the sum of "+
      MONEY_HAND->money_value_string( _employees[them][EMP_PAY], _place )+
      " for the work you have carried out to this date.\nIf you feel you "
      "have been unfairly dismissed, please refer to a manager.\n" );
   employee_log( them, "Fired by "+ capitalize( word )+
     " for "+ reason );
   remove_employee( them );
}
/* fire_them() */


/**
 * @ignore yes
 * Remove this person from the list of people banned from the shop.
 */
private void remove_baddie( string word )
{
   if ( !query_baddie( word ) )
   {
      return;
   }
   map_delete( _baddies, word );
   save_me();
}
/* remove_baddie() */


/**
 * @ignore yes
 * Called once a day to conduct the shop's daily review.
 * This method is the daily maintenance function for the shop.  It checks that
 * employees are still valid players, and not creators.  It conducts automatic
 * promotions, and handles demotions for inactive employees.  It also updates
 * the lists of declined applicants and banned people and removes that status
 * if applicable.  Finally, it calls the check_hire_list function to see if
 * we can hire any new employees.
 */
void review_employees()
{
   int prom, prom_number, managers, time;
   string word, *promos, promopost, *emps;

   emps = _retired;
   emps += m_indices( _employees );
   foreach ( word in emps )
   {
      if ( !"/secure/login"->test_user( word ) )
      {
         /* Make sure we are not firing the npc shopkeeper */
         if ( _employees[word][EMP_POINTS] & NPC )
         {
            continue;
         }
         fire_them( _proprietor, word, "not existing" );
      }
      if ( creatorp(word) )
      {
         fire_them( _proprietor, word, "being a creator" );
      }
   }

   promos = ({ });
   prom = FALSE;
   time = time();

   /* Check for inactive managers */
   foreach ( word in get_managers() )
   {
      if ( ( time - _employees[ word ][EMP_TIME] ) >
        (60*60*24*MGR_DEMOTE) )
      {
         demote( _proprietor, word );
      }
      else if ( time - _employees[word][EMP_TIME] > (60*60*24*MGR_WARN) &&
        !_employees[word][EMP_INACTIVE] )
      {
         AUTO_MAILER->auto_mail( word, _proprietor, "Poor attendance",
           "", "It has come to my attention that you have now been "
           "inactive for over " + MGR_WARN+ " days.  As you are a manager, "
           "you are required to meet certain levels of attendance.  "
           "You are now in serious danger of being demoted without "
           "further warning.\n---\n" + _proprietor+ " (proprietor)\n" );
         _employees[word][EMP_INACTIVE] = TRUE;
         employee_log( word, "Warned about inactivity" );
         shop_log( PERSONNEL, _proprietor, "warned "+
               capitalize( word ) + " about inactivity");
      }
   }

   /* Check supervisors for inactivity or promotion.  Sorted by points so
    * people promoted in order.
    */
   foreach( word in sort_array( get_supervisors(),
     (: _employees[$1][EMP_POINTS] - _employees[$2][EMP_POINTS] :) ) )
   {
      if ( time - _employees[word][EMP_TIME] > (60*60*24*SPR_DEMOTE) )
      {
         demote( _proprietor, word );
      }
      else if ( time - _employees[word][EMP_TIME] > (60*60*24*SPR_WARN) &&
        !_employees[word][EMP_INACTIVE] )
      {
         AUTO_MAILER->auto_mail( word, _proprietor, "Poor attendance",
           "", "It has come to my attention that you have now been "
           "inactive for over "+ SPR_WARN+ " days.  As you are a supervisor, "
           "you are required to meet certain levels of attendance.  "
           "You are now in serious danger of being demoted without "
           "further warning.\n---\n" + _proprietor+ " (proprietor)\n" );
         _employees[word][EMP_INACTIVE] = TRUE;
         employee_log( word, "Warned about inactivity" );
         shop_log( PERSONNEL, _proprietor, "warned "+
               capitalize( word ) + " about inactivity");
      }
      else
      {
         /* See if any managerial vacancies.  If so, and this person has
          * sufficient points & is not being ignored for promotion,
          * promote them.
          */
         prom_number = ( _max_emp * PERCENT_M ) / 100;
         if ( ( _employees[word][EMP_POINTS] > 32 * MANAGER_POINTS ) &&
           sizeof( get_managers() ) < prom_number &&
           !_employees[word][EMP_NOPROMOTE] )
         {
            set_employee( word, MANAGER );
            shop_log( PERSONNEL, _proprietor, "promoted "+
              capitalize( word )+ " to manager" );
            employee_log( word, "Promoted to manager" );
            AUTO_MAILER->auto_mail( word, _proprietor, "Promotion!",
              "", "Congratulations!  You've been promoted to manager "
              "of "+ _shop_name+ ".  You'll find that you can now enter "
              "the managers' office.  Please remember to use the \"memo\" "
              "facility from there to discuss any major admin points with "
              "other managers.  This includes hiring, firing, and so on.\n" );
            promos += ({ word });
            prom = TRUE;
         }
      }
   }

   /* Check employees for inactivity or promotion.  Sorted by points so
    * people promoted in order.
    */
   foreach( word in sort_array( get_employees(),
     (: _employees[$1][EMP_POINTS] - _employees[$2][EMP_POINTS] :) ) )
   {
      if ( _employees[word][EMP_POINTS] & NPC )
      {
         continue;
      }
      if ( time - _employees[word][EMP_TIME] > (60*60*24*EMP_FIRE) )
      {
         fire_them( _proprietor, word, "serious inactivity" );
         continue;
      }
      if ( time - _employees[word][EMP_TIME] > (60*60*24*EMP_WARN) )
      {
         if ( !_employees[word][EMP_INACTIVE] )
         {
            AUTO_MAILER->auto_mail( word, _proprietor, "Inactivity",
              "", "It has come to my attention that you have now been "
              "inactive for over "+ EMP_WARN+ " days.  Unless this "
              "situation is resolved, the management may have no option "
              "but to terminate your employment.\n---\n"+ _proprietor+
              " (proprietor)\n" );
            _employees[word][EMP_INACTIVE] = TRUE;
            shop_log( PERSONNEL, _proprietor, "warned "+
              capitalize( word )+ " about inactivity" );
            employee_log( word, "Warned about inactivity" );
         }
      }
      else
      {
         /* See if any supervisory vacancies.  If so, and this person has
          * sufficient points & is not being ignored for promotion,
          * promote them.
          */
         prom_number = ( _max_emp * PERCENT_S ) / 100;
         if ( ( _employees[word][EMP_POINTS] > 32 * SUPER_POINTS ) &&
           sizeof( get_supervisors() ) < prom_number &&
           !_employees[word][EMP_NOPROMOTE] )
         {
            if( _employees[word][EMP_POINTS] & CLOCKED_IN )
            {
               _employees[word][EMP_POINTS] = (SUPER_POINTS * 32) +
                 EMPLOYEE+ SUPERVISOR + CLOCKED_IN;
            }
            else
            {
               _employees[word][EMP_POINTS] = (SUPER_POINTS * 32) +
                 EMPLOYEE+ SUPERVISOR;
            }
            shop_log( PERSONNEL, _proprietor, "promoted "+
              capitalize( word )+ " to supervisor" );
            employee_log( word, "Promoted to supervisor" );
            AUTO_MAILER->auto_mail( word, _proprietor, "Promotion!", "",
              "Congratulations!  You've been promoted to supervisor "
              "of "+ _shop_name+ ".  You will now be able to use your "
              "newly acquired supervisor commands.\n" );
            promos += ({ word });
            prom = TRUE;
         }
      }
   }

   /* Post about promotions */
   if ( prom )
   {
      promopost = "The following employees have been promoted:\n\n";
      foreach ( word in promos )
      {
         promopost += sprintf( "     %s has been promoted to %s\n",
           capitalize( word ), (_employees[word][EMP_POINTS] & MANAGER)?
           "manager":"supervisor");
      }
      promopost += "\nCongratulations!\n";
      add_board_message( "Promotions", promopost );
   }

   /* Check the list of banned people */
   foreach ( word in m_indices( _baddies ) )
   {
      if ( time - _baddies[word][BAD_TIME] > (60*60*24*BAN_LENGTH) )
      {
         remove_baddie( word );
      }
   }

   /* Check the list of declined applicants */
   foreach ( word in m_indices( _declined ) )
   {
      if ( time - _declined[word] > (60*60*24*DECLINE_LENGTH) )
      {
         remove_declined( word );
      }
   }

   /* See if anyone can be hired */
   remove_call_out( _call_hire_list );
   _call_hire_list = call_out( "check_hire_list", 5 );
   save_emps();

   /* Update policies */
   managers = sizeof( get_managers() ) + sizeof( get_retired() );
   foreach ( word in m_indices( _new_policies ) )
   {
      if ( time - _new_policies[word][0] > (60*60*24*14) )
      {
         if ( sizeof( _new_policies[word][3] ) > managers / 2 )
         {
            add_policy( word );
         }
         else
         {
            remove_policy( word );
         }
      }
   }

   /* Update the player history data */
   foreach ( word in m_indices( _history ) )
   {
      if ( !"/secure/login"->test_user( word ) ||
        creatorp( word ) ||
        !_times[word] || _times[word] < (time - HIST_TIMEOUT) )
      {
         map_delete( _times, word );
         map_delete( _history, word );
      }
   }
   save_hist();
   save_times();
}
/* review_employees() */


/**
 * Calculate total employee wage packet for this month.
 * @return The current value of the months' wages.
 */
int calc_pay()
{
   string word, *emps;
   int amount = 0;

   emps = m_indices( _employees );
   foreach ( word in emps )
   {
      if ( _employees[word][EMP_POINTS] & NPC )
      {
         continue;
      }
      amount += _employees[word][EMP_PAY];
   }
   return amount;
}
/* calc_pay() */


/**
 * Calculate the months' projected bonuses.
 * These values are based upon the current staff levels, as well as
 * the amount of money in the bonus account.  The result is written
 * directly to this_player().
 */
void calc_bonus()
{
   int bonus_val, bonus_divisor = 0;
   string word;

   foreach ( word in m_indices( _employees ) )
   {
      if ( _employees[word][EMP_POINTS] & NPC ||
        _employees[word][EMP_NOBONUS] )
      {
         continue;
      }
      if ( _employees[word][EMP_POINTS] & MANAGER )
      {
         bonus_divisor += 4;
      }
      else if ( _employees[word][EMP_POINTS] & SUPERVISOR )
      {
         bonus_divisor += 3;
      }
      else
      {
         bonus_divisor += 2;
      }
   }
   if ( !bonus_divisor )
   {
      bonus_val = _accounts["bonus"];
   }
   else
   {
      bonus_val = ( _accounts["bonus"] * 2 ) / bonus_divisor;
   }
   word = "Based on the bonus fund of "+
     MONEY_HAND->money_value_string( _accounts["bonus"], _place )+
     ", the following bonuses are anticipated:\n"
     "\n     Managers    - "+
     MONEY_HAND->money_value_string( bonus_val * 2, _place )+
     "\n     Supervisors - "+
     MONEY_HAND->money_value_string( to_int( bonus_val * 1.5 ), _place )+
     "\n     Employees   - "+
     MONEY_HAND->money_value_string( bonus_val, _place )+ "\n";
   tell_object( this_player(), word );
}
/* calc_bonus() */


/**
 * @ignore yes
 * Called once a month to conduct the shop's monthly review.
 * This method is the monthly maintenance function for the shop.  This review
 * involves paying employees direct into their nominated bank account, and
 * awarding bonuses based on the current value of the bonus fund.  If there is
 * not enough money in the profit account to pay the correct amount, the employees
 * are paid on a pro-rata basis.
 */
void monthly_review(string month) {
   int amount, bonus_divisor, cabinet_cost, pay;
   float pay_multiplier;
   string *emps, post, word;

   // MONTH THINGIE IS BUGGERED HERE.

   amount = bonus_divisor = 0;
   pay_multiplier = 1.0;
   emps = keys( _employees );
   amount = calc_pay();

   cabinet_cost = ( _num_cabinets - MIN_CABINETS ) * CABINET_COST;
   adjust_profit( _proprietor, -cabinet_cost );
   shop_log( ACCOUNTS, _proprietor, "paid "+
       MONEY_H->money_value_string( cabinet_cost, _place )+" for the rent "
       "of " + ( _num_cabinets - MIN_CABINETS ) + " cabinets" );
   if( amount > _accounts["profit"] ) {
       pay_multiplier = _accounts["profit"] / amount;
   }
   foreach ( word in emps ) {
      if( _employees[word][EMP_POINTS] & NPC ) {
          continue;
      }
      if( !_employees[word][EMP_PAY] ) {
          continue;
      }
      pay = to_int(_employees[word][EMP_PAY] * pay_multiplier);
      BANK->adjust_account( word, BANKS[_employees[word][EMP_BANK]][1],
        pay );
      shop_log( ACCOUNTS, _proprietor, "paid "+ MONEY_HAND->
        money_value_string( pay, _place )+ " to "+ capitalize( word ) );
      AUTO_MAILER->auto_mail( word, _proprietor,
        "Pay advice for "+ _last_month, "", "For your work during "+
        _last_month+ ", you have been paid a total of "+
        MONEY_HAND->money_value_string( pay, _place )+
        ".  Keep up the good work.\n--\n"+ _proprietor+ " (proprietor)\n" );
      _employees[word][EMP_PAY] = 0;
      _accounts["profit"] -= pay;
   }
   _bonus += _accounts["bonus"];
   _accounts["bonus"] = 0;
   foreach ( word in m_indices( _employees ) )
   {
      if ( _employees[word][EMP_POINTS] & NPC ||
        _employees[word][EMP_NOBONUS] )
      {
         continue;
      }
      if ( _employees[word][EMP_POINTS] & MANAGER )
      {
         bonus_divisor += 4;
      }
      else if ( _employees[word][EMP_POINTS] & SUPERVISOR )
      {
         bonus_divisor += 3;
      }
      else
      {
         bonus_divisor += 2;
      }
   }
   if ( !bonus_divisor )
   {
      _bonus_val = _bonus;
   }
   else
   {
      _bonus_val = ( _bonus * 2 ) / bonus_divisor;
   }
   _got_bonus = ({ });
   foreach ( word in m_indices( _employees ) )
   {
      if ( _employees[word][EMP_POINTS] & NPC )
      {
         continue;
      }
      if ( _employees[word][EMP_NOBONUS] )
      {
         _employees[word][EMP_NOBONUS]--;
         _got_bonus += ({ word });
      }
   }
   post = "Based on the bonus fund of "+
     MONEY_HAND->money_value_string( _bonus, _place )+
     " for "+ month+ ", the following bonuses have been awarded:\n"
     "\n     Managers    - "+
     MONEY_HAND->money_value_string( _bonus_val * 2, _place )+
     "\n     Supervisors - "+
     MONEY_HAND->money_value_string( to_int( _bonus_val * 1.5 ), _place )+
     "\n     Employees   - "+
     MONEY_HAND->money_value_string( _bonus_val, _place )+
     "\n\nKeep up the good work.\n";
   add_board_message( "Bonuses for "+ month, post );
   shop_log( ACCOUNTS, _proprietor, "paid out "+
     MONEY_HAND->money_value_string( _bonus, _place )+
     " in bonuses for "+ month );
   _last_month = month;
   save_me();
   save_emps();
}
/* monthly_review() */


/**
 * Query the people banned from the shop.
 * This method returns a mapping of the people banned from this shop, along
 * with the time that they were banned, the person banning them, and the
 * reason they were banned.  The format for this mapping is:<br>
 * ([ person:({ reason, banner, time }) ])
 * @return The mapping, formatted above.
 */
mapping get_baddies() { return copy(_baddies + ([ ])); }


/**
 * @ignore yes
 * Ban this person from the shop.
 * This person will be banned for a specific period, and will not be allowed
 * back in the shop within this period.  People can be banned by managers, or
 * automatically for attacking employees.
 */
private void add_baddie( string word, string reason, string banner )
{
   object ob;

   word = lower_case( word );
   if ( query_baddie( word ) )
   {
      /*
       * Added this bit after discovering someone had been banned 75
       * times for killing Gretta @ Tarnach's- all at the same time.
       */
      if ( _baddies[word][BAD_TIME] == time() ||
         reason == _baddies[word][BAD_REASON] )
      {
         return;
      }
      remove_baddie( word );
   }
   if ( !sizeof( _baddies ) )
   {
      _baddies = ([ word:({ "", "", 0, }) ]);
   }
   else
   {
      _baddies += ([ word:({ "", "", 0, }) ]);
   }
   _baddies[word][BAD_REASON] = reason;
   _baddies[word][BAD_BANNER] = banner;
   _baddies[word][BAD_TIME] = time();
   save_me();
   shop_log( GENERAL, capitalize(banner), "banned "+ capitalize( word )+
     " for "+ reason );
   employee_log( word, sprintf( "Banned by %s for %s.", capitalize(banner),
     reason ) );
   fire_them( _proprietor, word, reason );
   remove_applicant( word );
   if ( !( ob = find_player( word ) ) )
   {
      return;
   }

   /* If this person is in the shop front, move them outside. */
   if ( environment( ob ) == find_object( _shop_front ) )
   {
      tell_room( _shop_front, ob->query_short()+ " drifts out of the door, "
        "seemingly against "+ ob->query_possessive()+ " will.\n", ({ ob }) );
      tell_object( ob , "You feel yourself pushed out of the shop "
        "by a mysterious force.\n" );
      tell_room( _shop_front->query_outside(), ob->query_short()+
        " drifts through the door of "+ _shop_name +
        ", seemingly against "+ ob->query_possessive()+ " will.\n", ({ ob }) );
      ob->move( _shop_front->query_outside() );
   }
}
/* add_baddie() */


/**
 * Query the number of employees currently clocked in.
 * This function will also clock out any employees that are no longer on DW.
 * @return The number of employees clocked in.
 */
int num_employees_in()
{
   int any = 0;
   string word;
   object ob;

   foreach ( word in m_indices( _employees ) )
   {
      if ( _employees[word][EMP_POINTS] & CLOCKED_IN )
      {
         if ( _employees[word][EMP_POINTS] & NPC )
         {
            continue;
         }
         if ( !( ob = find_player( word ) ) ||
           !interactive( ob ) )
         {
            reset_employee( word, CLOCKED_IN );
            shop_log( GENERAL, word, "was clocked out" );
         }
         else
         {
            any++;
         }
      }
   }
   return any;
}
/* num_employees_in() */


/**
 * @ignore yes
 * Called by the npc shopkeeper to limit loading patterns.
 */
void set_npc_time() { _npc_time = time(); }


/**
 * @ignore yes
 * Loads the npc shopkeeper and calls him/her/it to work.  Will only reload
 * the npc once every NPC_DELAY, unless clock_out is non-zero in which case the
 * npc will be loaded regardless - this parameter indicates that the npc is
 * being recalled due to an employee clocking out.
 */
void summon_shopkeeper( int clock_out )
{
   /* Already loaded */
   if ( find_object( _shopkeeper ) )
   {
      return;
   }

   /* Don't appear if any employees in */
   if ( num_employees_in() )
   {
      return;
   }

   /* Start shift */
   if ( ( time() - _npc_time > NPC_RELOAD ) || clock_out )
   {
      load_object(_shopkeeper)->start_shift();
   }
}
/* summon_shopkeeper() */


/**
 * @ignore yes
 * A nicely formatted employee list for the employee badge.
 */
string badge_list() {
   string word, temp_result, *results, tp_name;
   object ob, tp;

   results = ({});
   tp = this_player();
   tp_name = tp->query_name();

   foreach( word in sort_array( _retired, 1 ) ) {
      if ( ( ob = find_player( word ) ) && !ob->query_invis() ) {
         results += ({ "%^CYAN%^"+ ob->query_cap_name() +"%^RESET%^ (R)" });
      }
   }

   foreach ( word in get_managers() ) {
      temp_result = "";
      if ( ( ob = find_player( word ) ) && !ob->query_invis() ) {
         if( ( creatorp(tp) || ( _employees[tp_name][EMP_POINTS] & SUPERVISOR ) ) &&
             ( _employees[word][EMP_POINTS] & CLOCKED_IN ) ) {
             temp_result = "%^YELLOW%^";
         }
         results += ({ temp_result+ ob->query_cap_name()+ "%^RESET%^ (m)" });
      }
   }

   foreach( word in get_supervisors() ) {
      temp_result = "";
      if( ( ob = find_player( word ) ) && !ob->query_invis() ) {
         if( ( creatorp(tp) || ( _employees[tp_name][EMP_POINTS] & SUPERVISOR ) ) &&
             ( _employees[word][EMP_POINTS] & CLOCKED_IN ) ) {
             temp_result = "%^YELLOW%^";
         }
         results += ({ temp_result+ ob->query_cap_name()+ "%^RESET%^ (s)" });
      }
   }

   foreach( word in get_employees() ) {
      temp_result = "";
      if( ( ob = find_player( word ) ) && !ob->query_invis() ) {
         if( ( creatorp(tp) || ( _employees[tp_name][EMP_POINTS] & SUPERVISOR ) ) &&
             ( _employees[word][EMP_POINTS] & CLOCKED_IN ) ) {
             temp_result = "%^YELLOW%^";
         }
         results += ({ temp_result+ ob->query_cap_name()+ "%^RESET%^" });
      }
   }

   if( !sizeof( results ) ) {
      /* Will only occur if no employees logged on, and a creator is using
       * badge.
       */
      return "Just you, I'm afraid.\n";
   }

   return query_multiple_short( results ) + "\n";

} /* badge_list() */

/**
 * @ignore yes
 * The list of employees clocked in used by the extra_look in the shop front.
 */
string employees_clocked_in() {
   string word;
   object *words, ob;
   words = ({ });

   if( !num_employees_in() )
       return "No employees";

   foreach( word in keys( _employees ) ) {
      if( ( ob = find_player( word ) ) && !ob->query_invis() ) {
         if( _employees[word][EMP_POINTS] & CLOCKED_IN )
             words += ({ ob->query_cap_name() });
      }
   }

   return query_multiple_short( words );

} /* employees_clocked_in() */

/**
 * Checks to see if the specified employee is clocked in.
 * @param ob the object to check
 */
int employee_clocked_in(object ob) {
  if(!query_employee(ob->query_name())) return 0;
  return (_employees[ob->query_name()][EMP_POINTS] & CLOCKED_IN);
} /* employee_clocked_in() */

/**
 * @ignore yes
 * Used by the managers' office whenever a manager casts a vote
 * on an applicant.
 */
private void add_vote( string applicant, int vote ) {
   int managers;
   string voter;

   voter = this_player()->query_name();
   managers = sizeof( get_managers() );
   if ( vote == VABSTAIN )
   {
      _applicants[applicant][APP_ABSTAIN] += ({ voter });
   }
   else if ( vote == VFOR )
   {
      _applicants[applicant][APP_FOR] += ({ voter });
   }
   else
   {
       _applicants[applicant][APP_AGAINST] += ({ voter });
   }
   shop_log( GENERAL, voter, "voted on "+ capitalize( applicant ) );
   save_me();
   managers -= sizeof( _applicants[applicant][APP_ABSTAIN] );

   if ( sizeof( _applicants[applicant][APP_FOR] ) >= managers / 2 )
   {
      /* This applicant has the vote of at least 50%
       * of managers so hire them.
       */
      AUTO_MAILER->auto_mail( applicant, _proprietor, _shop_name, "",
        "Congratulations!  You've been accepted to work at "+
        _shop_name+ ".  Please return to the shop within the next "
        "seven days to \"confirm\" that you wish to accept "
        "the position.\n" );
      _applicants[applicant][APP_TYPE] = HIRED;
      _applicants[applicant][APP_TIME] = time();
      employee_log( applicant, "Application was accepted" );
   }
   else if ( sizeof( _applicants[applicant][APP_AGAINST] ) >
     managers / 2 )
   {
      /* This applicant has been voted against by over 50% of the
       * managers so decline them.
       */
      remove_applicant( applicant );
      AUTO_MAILER->auto_mail( applicant, _proprietor, _shop_name, "",
        "Thank you for your recent application for employment "
        "with us.  Unfortunately, I have to inform you that you "
        "have been unsuccessful at this time.  Please feel free "
        "to re-apply again in the future, when your application "
        "will be re-considered.\n" );
      employee_log( applicant, "Application was declined" );
      add_declined( applicant );
   }
}
/* add_vote() */


/**
 * @ignore yes
 * Used by the managers' office to determine whether a manager has
 * already voted on this applicant.
 */
int check_vote( string applicant, string voter )
{
   if ( member_array( voter, _applicants[applicant][APP_FOR] ) != -1 ||
     member_array( voter, _applicants[applicant][APP_AGAINST] ) != -1 ||
     member_array( voter, _applicants[applicant][APP_ABSTAIN] ) != -1 )
   {
      return TRUE;
   }
   return FALSE;
}
/* check_vote() */


/**
 * @ignore yes
 * Updates the chart with the average stock of an item.
 */
void update_averages()
{
   string words;

   foreach ( words in m_indices( _list ) )
   {
      _list[words][CHART_AVE] = ( _list[words][CHART_AVE] +
        query_stock( words ) + random(2) ) / 2;
   }
   save_list();
}
/* update_averages() */


/**
 * @ignore yes
 * Query if the shop sells this item.
 * @param item The item to query.
 * @return TRUE or FALSE
 */
int query_on_list( string item ) { return (!_list[item])?FALSE:TRUE; }


/**
 * @ignore yes
 * Add an item for the shop to deal in.
 */
private void put_on_list( string word )
{
   if ( !_list[word] )
   {
      _list[word] = ({ 5, 0, 20, 25, ({}), 0, 0, 0 });
      save_list();
   }
}
/* put_on_list() */


/**
 * @ignore yes
 * Stop the shop dealing in this item.
 */
private void take_off_list( string word )
{
   if ( !_list[word] )
   {
      return;
   }
   map_delete( _list, word );
   save_list();
}
/* take_off_list() */


/**
 * @ignore yes
 * Query the maximum number of this item allowed in stock.
 * @param item The item to query.
 * @return The maximum stock.
 */
int query_max( string item )
{
   return ( !_list[item] ) ? 0 : copy(_list[item][CHART_MAX]);
}
/* query_max() */


/**
 * @ignore yes
 * Set the maximum number of this item allowed in stock.
 */
private void set_max( string word, int number )
{
   if ( !_list[word] )
   {
      return;
   }
   _list[word][CHART_MAX] = number;
   save_list();
}
/* set_max() */


/**
 * @ignore yes
 * Query the average number of this item in stock.
 * @param item The item to query.
 * @return The average stock.
 */
int query_ave( string item )
{
   return ( !_list[item] ) ? 0 : copy(_list[item][CHART_AVE]);
}
/* query_ave() */


/**
 * @ignore yes
 * Query the buying price of this item.
 * This is the actual value, in the smallest unit of local currency
 * (eg. pence, farthings etc), rather than the absolute value.
 * @param item The item to query.
 * @return The buy price.
 */
int query_buy( string item )
{
   return ( !_list[item] ) ? 0 : copy(_list[item][CHART_BUY]);
}
/* query_buy() */


/**
 * @ignore yes
 * Set the buy price of this item.
 */
private void set_buy( string word, int number )
{
   if ( !_list[ word ] )
   {
      return;
   }
   _list[word][CHART_BUY] = number;
   save_list();
}
/* set_buy() */


/**
 * @ignore yes
 * Query the sell price of this item.
 * This is the actual value, in the smallest unit of local currency
 * (eg. pence, farthings etc), rather than the absolute value.
 * @param item The item to query.
 * @return The sell price.
 */
int query_sell( string item )
{
   return ( !_list[item] ) ? 0 : copy(_list[item][CHART_SELL]);
}
/* query_sell() */


/**
 * @ignore yes
 * Set the sell price of this item.
 */
private void set_sell( string word, int number )
{
   if ( !_list[word] )
   {
      return;
   }
   _list[word][CHART_SELL] = number;
   save_list();
}
/* set_sell() */


/**
 * @ignore yes
 * Query whether this cabinet is currently assigned to any items.
 * @param cabinet The cabinet number ( between 1 and number of cabinets )
 * @return TRUE or FALSE
 */
int query_cabinet_used( int cabinet )
{
   string word;

   foreach( word in m_indices(_list) )
   {
      if ( member_array( cabinet, _list[word][CHART_CAB] ) != -1 )
      {
         return TRUE;
      }
   }
   return FALSE;
}
/* query_cabinet_used() */


/**
 * @ignore yes
 * Query which cabinets are assigned to this item.
 * @param item The item to query.
 * @return An array of cabinet numbers assigned to the item.
 */
int *query_cabinet( string item )
{
   return ( !_list[item] ) ? ({}) : copy(_list[item][CHART_CAB]);
}
/* query_cabinet() */


/**
 * @ignore yes
 * Assign an item to use a cabinet.
 */
private void add_chart_cabinet( string word, int number )
{
   if ( !_list[word] )
   {
      return;
   }
   _list[word][CHART_CAB] += ({ number });
   _list[word][CHART_CAB] = sort_array( _list[word][CHART_CAB], 1 );
   save_list();
}
/* add_chart_cabinet() */


/**
 * @ignore yes
 * Stop an item using a cabinet.
 */
private void remove_chart_cabinet( string word, int number )
{
   if ( !_list[word] )
   {
      return;
   }
   _list[word][CHART_CAB] -= ({ number });
   save_list();
}
/* remove_chart_cabinet() */


/**
 * @ignore yes
 * Used by the employee list to show managers & creators the last
 * time an employee was active.  Employees will be highlighted yellow
 * if they are currently on an inactivity warning, and red if they are
 * within 7 days of being fired/demoted
 */
private string query_worked( string emp )
{
   string blurb;

   /* Clocked in */
   if ( _employees[emp][EMP_POINTS] & CLOCKED_IN )
   {
      return " is currently clocked in";
   }

   /* NPC */
   if ( _employees[emp][EMP_POINTS] & NPC )
   {
      return " has gone home for tea";
   }

   /* On leave */
   if ( _employees[emp][EMP_TIME] > time() )
   {
      return " - %^CYAN%^on leave until "+
        ctime( _employees[emp][EMP_TIME]  ) + "%^RESET%^";
   }

   blurb = " - last action ";

   if ( _employees[emp][EMP_POINTS] & MANAGER )
   {
      if ( ( time() - _employees[emp][EMP_TIME] ) > ((60*60*24*MGR_DEMOTE)-7) )
      {
         blurb += "%^RED%^";
      }
      else if ( ( time() - _employees[emp][EMP_TIME] ) > (60*60*24*MGR_WARN) )
      {
         blurb += "%^RED%^";
      }
   }
   else if ( _employees[emp][EMP_POINTS] & SUPERVISOR )
   {
      if ( ( time() - _employees[emp][EMP_TIME] ) > ((60*60*24*SPR_DEMOTE)-7) )
      {
         blurb += "%^RED%^";
      }
      else if ( ( time() - _employees[emp][EMP_TIME] ) > (60*60*24*SPR_WARN) )
      {
         blurb += "%^YELLOW%^";
      }
   }
   else if ( ( time() - _employees[emp][EMP_TIME] ) > ((60*60*24*EMP_FIRE)-7) )
   {
      blurb += "%^RED%^";
   }
   else if ( ( time() - _employees[emp][EMP_TIME] ) > (60*60*24*EMP_WARN) )
   {
      blurb += "%^YELLOW%^";
   }
   blurb += ctime( _employees[emp][EMP_TIME] ) + "%^RESET%^";
   return( blurb );
}
/* query_worked() */


/**
 * @ignore yes
 * This function is used by employees to clock in and out of work.
 * Clocking out may also summon the npc shopkeeper.
 */
int do_clock( string clock )
{
   string word;
   object tp;

   tp=this_player();

   if ( creatorp(tp) ) {
      tell_object( tp, "Creators cannot clock "+clock+"!\n" );
      return 1;
   }

   word = tp->query_name();
   switch( clock )
   {
     case "in" :
        if ( _employees[word][EMP_POINTS] & CLOCKED_IN )
        {
           tell_object( tp, "You are already clocked in!\n" );
           return 1;
        }

        /* Stop the shopkeeper appearing if on their way */
        remove_call_out( _call_summon );

        /* Is this person a cre alt? If so, prevent promotion. */
        if ( tp->query_property( "no score" ) )
        {
           _employees[word][EMP_NOPROMOTE] = TRUE;
        }
        set_employee( word, CLOCKED_IN );
        break;
     case "out" :
        if ( !_employees[word][EMP_POINTS] & CLOCKED_IN )
        {
           tell_object( tp, "You are already clocked out!\n" );
           return 1;
        }

        /* Call npc shopkeeper to work */
        remove_call_out( _call_summon );
        _call_summon = call_out( "summon_shopkeeper", 60, 1 );
        reset_employee( word, CLOCKED_IN );
        break;
   }
   add_succeeded_mess( "$N $V "+ clock+ ".\n" );
   shop_log( GENERAL, word, "clocked "+ clock );
   return 1;
}
/* do_clock() */


/**
 * @ignore yes
 * Displays an employee's percentage towards promotion, if applicable.
 * Displays all employees' scores to creators.
 * Displays last-action (leave) times to managers.
 */
string list_stuff( string word )
{
   string results = "", tp;

   /* Nothing to display for retired managers */
   if ( member_array( word, _retired ) != -1 )
   {
      return "\n";
   }

   tp = this_player()->query_name();

   /* Npc shopkeeper */
   if ( _employees[word][EMP_POINTS] & NPC )
   {
      return query_worked( word ) + "\n";
   }

   /* Creator viewing - display all scores */
   if ( creatorp(this_player()) )
   {
      results = " ["+ ( _employees[word][EMP_POINTS] / 32 ) +"]";
   }
   else if ( ( word == tp ) && !(_employees[word][EMP_POINTS] & MANAGER) )
   {
      if ( _employees[word][EMP_POINTS] & SUPERVISOR )
      {
         results = " ["+ ( ( 100 * ( _employees[word][EMP_POINTS] /
           32 - SUPER_POINTS )) /
           ( MANAGER_POINTS - SUPER_POINTS )) +"%]";
      }
      else
      {
         results = " ["+ ( ( 100 * ( _employees[word][EMP_POINTS] / 32 ) ) /
           SUPER_POINTS ) +"%]";
      }
   }

   /* Last-action times displayed to managers & creators */
   if ( creatorp(this_player()) || (_employees[tp][EMP_POINTS] & MANAGER) )
   {
      results += query_worked( word );
   }
   return results + "\n";
}
/* list_stuff() */


/**
 * @ignore yes
 * Employee wishes to terminate their employment with the shop.
 */
int do_resign()
{
   string word;

   word = this_player()->query_name();
   add_succeeded_mess( "$N $V.\n" );
   remove_employee( word );
   shop_log( PERSONNEL, word, "resigned" );
   employee_log( word, "Resigned" );
   return 1;
}
/* do_resign() */


/**
 * @ignore yes
 * The list of employees in the shop.
 * This list is displayed sorted primarily by status (Retired, manager,
 * supervisor, riff-raff) and then alphabetically within those groups.
 * It also highlights those employees currently logged on (as long as they
 * are visible).
 */
int do_list() {
   string word, results;
   int emps_in;
   object ob;

   results = "   Employees of "+ _shop_name + "\n"
     "     As at " + ctime( time() ) + "\n\n";

   /* Retired managers */
   foreach ( word in sort_array( _retired, 1 ) )
   {
      if ( (ob = find_player( word )) && !ob->query_invis() )
      {
         results += "%^GREEN%^";
      }
      results += "     "+ capitalize( word )+
      "%^RESET%^ (retired manager)\n";
   }

   /* Managers */
   foreach ( word in get_managers() )
   {
      if ( (ob = find_player( word )) && !ob->query_invis() )
      {
         emps_in ++;
         results += "%^GREEN%^";
      }
      results += "     "+ capitalize( word )+ "%^RESET%^ (manager)"+
        list_stuff( word );
   }

   /* Supervisors */
   foreach ( word in get_supervisors() )
   {
      if ( (ob = find_player( word )) && !ob->query_invis() )
      {
         emps_in ++;
         results += "%^GREEN%^";
      }
      results += "     "+ capitalize( word )+ "%^RESET%^ (supervisor)"+
        list_stuff( word );
   }

   /* Riff-raff */
   foreach ( word in get_employees() )
   {
      if ( (ob = find_player( word )) && !ob->query_invis() )
      {
         emps_in ++;
         results += "%^GREEN%^";
      }
      results += "     "+ capitalize( word )+ "%^RESET%^"+
        list_stuff( word );
   }

   results += "\n";

   /* If viewed by manager or creator, display number of employees logged
    * on and number clocked in.
    */
   if( creatorp(TP) || _employees[TP->query_name()][EMP_POINTS] & MANAGER ) {
      if ( emps_in == 1 ) {
         results += "   There is one employee ";
      } else {
         results += "   There are "+ query_num(emps_in,0)+ " employees ";
      }
      results += "out of "+ query_num( sizeof( _employees ) )+
        " on Discworld at the moment, with "+ query_num( num_employees_in(),
          0 ) + " currently clocked in.\n\n";
   }

   tell_object( this_player(), "$P$Employees$P$"+ results );
   add_succeeded_mess( "" );
   return 1;
}
/* do_list() */

/**
 * @ignore yes
 * Claim a badge, handbook, or bonus.
 */
int do_claim( string item ) {
   object thing, tp;
   int give_bonus;
   string tp_name;

   tp = this_player();
   switch( item ) {
     case "badge" :
        thing = clone_object( BADGE );
        thing->set_props( file_name( this_object() ),
          tp->query_name() );
        break;
     case "handbook" :
        thing = clone_object( STAFF_BOOK );
        thing->set_read_mess("\n   "+ _shop_name+ "\n\n"
          "   Staff Handbook\n\n   This handbook belongs to: "+
          tp->query_short()+ "\n\n", "common", 100 );
        thing->set_office( file_name( this_object() ) );
        break;
     case "bonus" :
        tp_name = tp->query_name();
        /* Already claimed, new employee, or had bonus suspended. */
        if ( member_array( tp_name, _got_bonus ) != -1 )
        {
           tell_object( tp, "You are not entitled to a "
             "bonus this month!\n" );
           return 1;
        }

        /* Value depends on status */
        if ( _employees[tp_name][EMP_POINTS] & MANAGER )
        {
           give_bonus = _bonus_val * 2;
        }
        if ( _employees[tp_name][EMP_POINTS] & SUPERVISOR )
        {
           give_bonus = to_int( _bonus_val * 1.5 );
        }
        else
        {
           give_bonus = _bonus_val;
        }
        /* Nothing to give */
        if ( !give_bonus )
        {
           tell_object( tp,  "There is no money in the bonus "
             "fund this month.\n" );
           return 1;
        }

        /* Prevent them from claiming again this month */
        _got_bonus += ({ tp_name });
        _bonus -= give_bonus;

        /* Update value of unclaimed bonus */
        if ( _bonus < 0 )
        {
           _bonus = 0;
        }

        thing = MONEY_HAND->make_new_amount( give_bonus, _place );
        shop_log( GENERAL, tp_name, "claimed "+
          MONEY_HAND->money_value_string( give_bonus, _place ) );
        if ( thing->move( tp ) != MOVE_OK )
        {
           thing->move( environment( tp ) );
           tell_object( tp, "You drop "+ thing->query_short()+
             " as you're carrying too much.\n" );
        }
        save_me();
        add_succeeded_mess( "$N $V "+ MONEY_HAND->
          money_value_string( give_bonus, _place )+ ".\n" );
        return 1;
        break;
   }
   if ( thing->move( tp ) != MOVE_OK )
   {
      thing->move( this_object() );
      tell_object( tp, "You drop your "+ item+
        " as you're carrying too much.\n" );
   }
   add_succeeded_mess( "$N $V a new "+ item+ ".\n" );
   return 1;
}
/* do_claim() */


/**
 * @ignore yes
 * This employee has requested to be passed over for promotion.
 */
int do_promote( string on )
{
   object tp;

   add_succeeded_mess( "" );
   tp = this_player();
   switch ( on )
   {
     case "on" :
        _employees[tp->query_name()][EMP_NOPROMOTE] = TRUE;
        tell_object( tp, "You have now requested to be "
          "passed over for promotion.\n" );
        break;
     case "off" :
        if ( tp->query_property( "no score" ) ) {
           tell_object( tp, "Creator alts cannot be promoted.\n" );
           return 1;
        }
        _employees[tp->query_name()][EMP_NOPROMOTE] = FALSE;
        tell_object( tp, "You have now requested to be "
          "considered for promotion.\n" );
        break;
   }
   save_emps();
   return 1;
}
/* do_promote() */


/**
 * @ignore yes
 * Display or update the employee's bank details.
 */
int do_bank( mixed *args )
{
   string word, message;
   int x;
   object tp;

   tp = this_player();
   if ( creatorp(tp) ) {
      tell_object( tp, "Why do you need to be paid?\n" );
      return 1;
   }
   word = tp->query_name();
   add_succeeded_mess( "" );

   if ( !sizeof( args ) )
   {
      message = "You are currently being paid into your account at "+
        BANKS[_employees[word][EMP_BANK]][0]+ ".  To change which bank "
        "you would like your pay to be deposited at, use the command "
        "\"bank <number>\" where <number> is one of the following:\n\n";
      for ( x = 0; x < sizeof(BANKS); x++ )
      {
         message += sprintf( "     %d: %s\n", x+1, BANKS[x][0] );
      }
      tell_object( tp, message + "\n" );
      return 1;
   }
   if ( args[0] < 1 || args[0] > sizeof(BANKS) )
   {
      tell_object( tp,  "There are "+ sizeof(BANKS)+
        " banks to choose from.\n" );
      return 1;
   }
   _employees[word][EMP_BANK] = args[0] - 1;
   save_emps();
   tell_object( tp, "You change your bank details.\n" );
   return 1;
}
/* do_bank() */


/**
 * @ignore yes
 * Maintain the list of stock dealt in by this shop.
 */
int do_chart( mixed *args, string pattern )
{
   int number;
   string word, item;

   word = this_player()->query_name();
   item = args[0];
   if ( sizeof(args) > 1 )
   {
      number = args[1];
   }

   if ( pattern == "add <string'item'>" )
   {
      if ( query_on_list( item ) )
      {
         tell_object( this_player(), item +
           " are already on the sales list.\n" );
         return 1;
      }
   }
   else if ( !query_on_list( item ) )
   {
      tell_object( this_player(), item +" are not on the sales list.\n" );
      return 1;
   }

   switch ( pattern )
   {
     case "add <string'item'>" :
        /* Start dealing in this item */
        add_succeeded_mess( "$N put$s "+ item +" on the sales list.\n" );
        put_on_list( item );
        shop_log( CHARTLOG, word, "put "+ item +" on the sales list" );
        break;
     case "remove <string'item'>" :
        /* Stop dealing in this item */
        add_succeeded_mess( "$N take$s "+item +" off the sales list.\n" );
        take_off_list( item );
        shop_log( CHARTLOG, word, "took "+item +" off the sales list" );
        break;
     case "max <string'item'> <number>" :
        /* Set maximum stock */
        add_succeeded_mess( "$N set$s the maximum stock of "+ item +" to "+
          number +".\n" );
        set_max( item, number );
        shop_log( CHARTLOG, word, "set max of "+ item +" to "+ number );
        break;
     case "buy <string'item'> <number>" :
        /* Set buy price */
        add_succeeded_mess( "$N set$s the buying price of "+ item +" to "+
          number +".\n" );
        set_buy( item, number );
        shop_log( CHARTLOG, word, "set buy of "+ item +" to "+ number );
        break;
     case "sell <string'item'> <number>" :
        /* Set sell price */
        add_succeeded_mess( "$N set$s the selling price of "+ item +" to "+
          number +".\n" );
        set_sell( item, number );
        shop_log( CHARTLOG, word, "set sell of "+ item +" to "+ number );
        break;
     case "assign <string'item'> [to cabinet] <number>" :
        /* Assign item to cabinets */
        if ( member_array( number, _list[item][CHART_CAB] ) != -1 )
        {
           tell_object( this_player(), item+
             " are already assigned to cabinet "+ number+ ".\n" );
           return 1;
        }
        if ( number < 1 || number > _num_cabinets )
        {
           tell_object( this_player(), "That cabinet does not exist.\n" );
           return 1;
        }
        add_chart_cabinet( item, number );
        add_succeeded_mess( "$N assign$s "+ item +" to use cabinet "+
          number +".\n" );
        shop_log( CHARTLOG, word, "assigned "+ item +
          " to cabinet "+ number );
        break;
     case "unassign <string'item'> [from cabinet] <number>" :
        /* Stop item using cabinets */
        if ( member_array( number, _list[item][CHART_CAB] ) == -1 )
        {
           tell_object( this_player(), item+
             " are not assigned to cabinet "+ number+ ".\n" );
           return 1;
        }
        remove_chart_cabinet( item, number );
        add_succeeded_mess( "$N stop$s "+ item +" using cabinet "+
          number +".\n" );
        shop_log( CHARTLOG, word, "unassigned "+ item +
          " from cabinet "+ number );
        break;
   }
   return 1;
}
/* do_chart() */


/**
 * @ignore yes
 * Check cabinet assignments
 */
int do_check()
{
   string result, item, word,*items;
   int x;

   result = "Current cabinet assignments are:\n";
   for( x = 0; x < _num_cabinets; x++ )
   {
      result += sprintf( "Cabinet %2d: ", x+1 );
      items = ({});
      foreach( item in m_indices(_list) )
      {
         if ( member_array( x+1, _list[item][CHART_CAB] ) != -1 )
         {
            word = sprintf( "%s (%d)", item, _list[item][CHART_MAX] );
            items += ({ word });
         }
      }
      if ( sizeof( items ) )
      {
         result += query_multiple_short( items );
      }
      result += "\n";
   }
   result += "\n";
   tell_object( this_player(), "$P$Cabinet assignments$P$"+ result );
   return 1;
}
/* do_check() */


/**
 * @ignore yes
 * Display the shop's logs.
 */
int do_logs( mixed *args, string pattern )
{
   int i;
   string start, end, file, *files, words;

   add_succeeded_mess("");
   switch ( pattern )
   {
     case "" :
        /* Display available logs */
        files = get_dir( _log_file +"general.log*" );
        words = "Available logs:\n\n";
        for ( i = 0; i < sizeof( files ); i++ )
        {
           if ( !i )
           {
              words += " 1: current log\n";
              continue;
           }
           sscanf( unguarded( (: read_file, _log_file + files[i], 2, 1 :) ),
             "%*s, %s: %*s", start );
           sscanf( unguarded( (: read_file, _log_file + files[i],
             file_length( _log_file + files[i] ), 1 :) ),
             "%*s, %s: %*s", end );
           if ( start == end )
           {
              words += sprintf( "%2d: %s\n", i + 1, start );
              continue;
           }
           words += sprintf( "%2d: %s to %s\n", i + 1, start, end );
        }
        words += "\nUse \"logs <number>\" to read one of them, or logs "
          "{personnel|accounts|chart} to view those logs.\n";
        tell_object( this_player(),"$P$Logs$P$"+ words );
        break;
     case "<number>" :
        /* Display specific log */
        files = get_dir( _log_file +"general.log*" );
        if ( ( args[0] < 1 ) || ( args[0] > sizeof( files ) ) )
        {
           tell_object( this_player(), "There are "+ sizeof( files ) +
             " logs.  Use \"logs\".\n" );
           return 1;
        }
        sscanf( unguarded( (: read_file, _log_file +
          files[args[0] - 1] :) ), "%*s\n%s", words );
        tell_object( this_player(),"$P$Log "+ args[0]+ "$P$"+ words );
        break;
     case "[chart]" :
        /* Display chart log */
        file = _log_file + "chart.log";
        if ( file_size( file ) > 0 )
        {
           sscanf( unguarded( (: read_file, file :) ), "%s", words );
           tell_object( this_player(),"$P$"+ capitalize(args[0])+
             " log$P$"+ words );
        }
        else
        {
           tell_object( this_player(), "The chart log is empty.\n" );
           return 1;
        }
        break;
     case "{personnel|accounts|chart}" :
        /* Display special log */
        file = _log_file + args[0] + ".log";
        if ( file_size( file ) > 0 )
        {
           sscanf( unguarded( (: read_file, file :) ), "%s", words );
           tell_object( this_player(),"$P$"+ capitalize(args[0])+
             " log$P$"+ words );
        }
        else
        {
           tell_object( this_player(), "The "+ args[0]+ " log is empty.\n" );
           return 1;
        }
        break;
   }
   return 1;
}
/* do_logs() */


/**
 * @ignore yes
 * Query the direction to another part of the shop.
 * This function is used by the npc shopkeeper to navigate
 * around the shop.
 */
string directions_to( string place )
{
   if ( place == _counter )
   {
      return copy(_counter_dir);
   }
   if ( place == _storeroom )
   {
      return copy(_store_dir);
   }
   if ( place == _shop_front )
   {
      return copy(_shop_dir);
   }
   return "here";
}
/* directions_to() */


/**
 * Set the location of the shop.
 * This is used by the money handling functions to determine which
 * currency to use and therefore should be one of the locations returned
 * by the query_all_places() function in /handlers/money_handler
 * @example set_place( "Ankh-Morpork" );
 * @param place The location of this shop.
 */
protected void set_place( string place ) { _place = place; }


/**
 * Set the npc shopkeeper object.
 * @example set_shopkeeper( CHARS + "gretta_shuffle" );
 * @param path The full path to the shopkeeper.
 */
protected void set_shopkeeper( string path ) {
   _shopkeeper = path;
}
/* set_shopkeeper() */


/**
 * Set the path to the customer area.
 * @example set_shop_front( PATH + "consumables" );
 * @param path The full path and filename to the customer area of the shop.
 */
protected void set_shop_front( string path ) { _shop_front = path; }


/**
 * Set the path to the counter.
 * @example set_counter( PATH + "counter" );
 * @param path The full path and filename to the shop's counter.
 */
protected void set_counter( string path ) { _counter = path; }


/**
 * Set the path to save the log files to.
 * @example set_log_files( PATH + "logs/" );
 * @param path The full path to the log files.
 */
protected void set_log_files( string path ) { _log_file = path; }

/**
 * Set the path to the storeroom.
 * @example set_storeroom( PATH + "tarnach's_storeroom" );
 * @param path The full path and filename to the storeroom of the shop.
 */
protected void set_storeroom( string path ) { _storeroom = path; }


/**
 * Query the name of the proprietor.
 */
string query_proprietor() { return copy(_proprietor); }

/**
 * Query the channel used by the shop.
 */
string query_channel() { return copy(_channel); }

/**
 * Query the value of the profit account.
 */
int query_profit() { return copy(_accounts["profit"]); }

/**
 * Query the value of the bonus account.
 */
int query_bonus() { return copy(_accounts["bonus"]); }

/**
 * Query the maximum number of employees.
 */
int query_maxemp() { return copy(_max_emp); }

/**
 * Query the base pay rate.
 */
int query_pay() { return copy(_pay_val); }

/**
 * Query the location of this shop.
 */
string query_place() { return copy(_place); }

/**
 * Query the path to the shop front.
 */
string query_shop_front() { return copy(_shop_front); }

/**
 * Query the path to the counter.
 */
string query_counter() { return copy(_counter); }

/**
 * Query the path to the managers' office.
 */
string query_mgr_office() { return copy(_mgr_office); }

/**
 * Query the path to the storeroom.
 */
string query_storeroom() { return copy(_storeroom); }

/**
 * Query the full name of the shop.
 */
string query_shop_name() { return copy(_shop_name); }

/**
 * Set the exit to the managers' office.
 * @example add_manager_exit( "east", PATH + "tarnach's_man_office" );
 * @param dir The direction of the exit.
 * @param path The full path and filename to the managers' office of the shop.
 */
protected void add_manager_exit( string dir, string path )
{
   add_exit( dir, path, "door" );
   modify_exit( dir, ({ "function", ({ this_object(),
     "check_manager" }), "door short", "office door", "door long",
     "There is a sign on the door which reads: \""+ _proprietor+
     "- Private\".\n" }) );
   _mgr_office = path;
}
/* set_mgr_office() */


/**
 * Set the full name of the shop.
 * This is used throughout the shop, and passed to the shop front as the
 * short description for the shop.
 * @example set_shop_name( "Tarnach Fendertwin's Quality Consumables" );
 * @param name The name of the shop.
 */
protected void set_shop_name( string name ) { _shop_name = name; }


/**
 * Set the very short name of the shop.
 * This is used in many places including save-file names, setting up
 * player-titles, and mail headers.  It should be no more than around
 * 4 or 5 chars in length.
 * @example set_very_short( "TFQC" );
 * @param name The short name.
 */
protected void set_very_short( string name )
{
   _very_short = name;
   if ( file_size( _save_dir + name + ".o" ) > 0 )
   {
      unguarded( (: restore_object, _save_dir + name :) );
   }
   if ( file_size( _save_dir + name+ ".employees" ) > 0 )
   {
      _employees = restore_variable( unguarded( (: read_file,
        _save_dir+ name+ ".employees" :) ) );
   }
   if ( file_size( _save_dir + name+ ".history" ) > 0 )
   {
      _history = restore_variable( unguarded( (: read_file,
        _save_dir+ name+ ".history" :) ) );
   }
   if ( file_size( _save_dir + name+ ".times" ) > 0 )
   {
      _times = restore_variable( unguarded( (: read_file,
        _save_dir+ name+ ".times" :) ) );
   }
   TCRE("shiannar", "PSHOP:"+file_name(TO)+": Restoring files.");
}
/* set_very_short() */


/**
 * Returns the very short name of the shop.
 */
string shop_very_short() { return copy(_very_short); }


/**
 * Set the name of the proprietor.
 * @example set_proprietor( "Tarnach Fendertwin" );
 * @param name The name of the proprietor.
 */
protected void set_proprietor( string name ) { _proprietor = name; }


/**
 * Set the channel used by the shop.
 * This sets the channel used by the employees' badges, and also the
 * name of the board.  If a board has been set-up for this shop, this
 * function will also add the board into the room.
 * @example set_channel( "Tarnach's", 1 );
 * @param name The name of the channel.
 * @param board Non-zero if a board exists for this shop.
 */
protected void set_channel( string name, int board )
{
   _channel = lower_case( name );
   if ( board )
   {
      _board = clone_object( "/obj/misc/board" );
      _board->set_datafile( name );
      _board->move( this_object() );
   }
}
/* set_channel() */


/**
 * @ignore yes
 * Adjust the value of the profit account.
 */
private void adjust_profit( string emp, int amount )
{
   string sign = "";

   _accounts["profit"] += amount;
   if ( _accounts["profit"] < 0 )
   {
      _accounts["profit"] = 0;
   }
   if ( amount < 0 )
   {
      sign = "-";
      amount = -amount;
   }
   shop_log( ACCOUNTS, emp, sprintf( "adjusted the profit account by %s%s",
     sign, MONEY_HAND->money_value_string( amount, _place ) ) );
   save_me();
}
/* adjust_profit() */


/**
 * @ignore yes
 * Adjust the value of the bonus account.
 */
private void adjust_bonus( string emp, int amount )
{
   string sign = "";

   _accounts["bonus"] += amount;
   if ( _accounts["bonus"] < 0 )
   {
      _accounts["bonus"] = 0;
   }
   if ( amount < 0 )
   {
      sign = "-";
      amount = -amount;
   }
   shop_log( ACCOUNTS, emp, sprintf( "adjusted the bonus account by %s%s",
     sign, MONEY_HAND->money_value_string( amount, _place ) ) );
   save_me();
}
/* adjust_profit() */


/**
 * Set the directions to other parts of the shop.
 * This function is used by the npc shopkeeper to navigate
 * around the shop, using the exits at the given directions.
 * These directions should be the standard "north", "southeast" etc.
 * @example set_directions( "southeast", "southeast", "southeast" );
 * @param store The direction to the storeroom.
 * @param counter The direction to the counter.
 * @param shop The direction to the shop front.
 */
protected void set_directions( string store, string counter, string shop  )
{
   _store_dir = store;
   _counter_dir = counter;
   _shop_dir = shop;
}
/* set_directions() */


/**
 * @ignore yes
 * Demote a manager or supervisor.
 */
private void demote( string demoter, string demotee )
{
   int points;

   points = _employees[demotee][EMP_POINTS] & CLOCKED_IN;
   if( _employees[demotee][EMP_POINTS] & MANAGER )
   {
      points += (SUPER_POINTS * 32) + EMPLOYEE + SUPERVISOR;
   }
   else
   {
      points += EMPLOYEE;
   }
   _employees[demotee][EMP_POINTS] = points;
   save_emps();
   AUTO_MAILER->auto_mail( demotee, _proprietor, "Demotion", "",
     "This is to advise you that you have today been demoted.\n"
     "This demotion will now stay on your employment record.\n" );
   employee_log( demotee, "Demoted by "+ demoter );
   shop_log( PERSONNEL, demoter, "demoted "+ capitalize( demotee ) );
}
/* demote() */


/**
 * @ignore yes
 * View an employee's history.
 * This method displays a formatted display of the employee's history
 * with a particular shop, and is viewable by managers of that shop.
 * @param employee The employee.
 */
void view_record( string employee )
{
   string text;
   int i;

   if ( !sizeof(_history) || !_history[employee] )
   {
      tell_object( this_player(), "There is no history for that person.\n" );
      return;
   }
   text = "Employment history of "+ capitalize( employee )+ ":\n\n";
   for( i = 0; i < sizeof( _history[employee][0] ); i++ )
   {
      text += ctime(_history[employee][0][i])+
        ": "+ _history[employee][1][i]+ "\n";
   }
   tell_object( this_player(), "$P$"+ capitalize(employee)+
     "'s history$P$"+ text );
}
/* view_record() */


/**
 * @ignore yes
 * Saving the contents of the shop's register.
 */
void save_register( mixed *money )
{
   _register = money;
   save_me();
}
/* save_register() */


/**
 * @ignore yes
 * Loading the contents of the shop's register.
 */
mixed *restore_register()
{
   return copy(({_register}));
}
/* restore_register() */


/**
 * Query the number of storeroom cabinets.
 * @return The number of cabinets in the storeroom.
 */
int query_num_cabinets() { return copy(_num_cabinets); }


/**
 * @ignore yes
 * Managers' office.
 * Retire from management.
 */
int do_retire()
{
   string manager;

   manager = this_player()->query_name();
   if ( !(_employees[manager][EMP_POINTS] & MANAGER) )
   {
      return 0;
   }
   remove_employee( manager );
   _retired += ({ manager });
   shop_log( PERSONNEL, manager, "retired from management" );
   employee_log( manager, "Retired from management" );
   save_me();
   add_succeeded_mess( "$N retire$s.\n" );
   return 1;
}
/* do_retire() */


/**
 * @ignore yes
 * Managers' office.
 * Transfer money between accounts.
 */
int do_transfer( mixed *args )
{
   int value, best;
   object money;
   string tp;

   if ( args[2] == args[3] || !args[0] )
   {
      tell_object( this_player(), "The point being?\n" );
      return 1;
   }
   tp = this_player()->query_name();
   money = clone_object( MONEY_OBJECT );
   money->set_money_array( MONEY_HAND->query_values_in( _place ) );
   if ( ( best = money->find_best_fit( args[1] ) ) == -1 )
   {
      tell_object( this_player(), "That currency is not used by "
        "the shop.\n" );
      return 1;
   }
   value = args[0] * ( money->query_money_array() )[ best + 1 ];
   money->dest_me();
   switch( args[2] ) {
   case "register" :
      if ( _counter->query_register() < value )
      {
         tell_object( this_player(), "There isn't that much available.\n" );
         return 1;
      }
      _counter->adjust_register( tp, value );
      if ( args[3] == "profit" )
      {
         adjust_profit( tp, value );
      }
      else
      {
         adjust_bonus( tp, value );
      }
      break;
   case "bonus" :
      if ( _accounts["bonus"] < value )
      {
         tell_object( this_player(), "There isn't that much available.\n" );
         return 1;
      }
      adjust_bonus( tp, -value );
      if ( args[3] == "profit" )
      {
         adjust_profit( tp, value );
      }
      else
      {
         money = MONEY_HAND->make_new_amount( value, _place );
         _counter->add_money_ob( money );
         shop_log( ACCOUNTS, tp, "adjusted the register by "+
           MONEY_HAND->money_value_string( value, _place ) );
      }
      break;
   case "profit" :
      if ( _accounts["profit"] < value )
      {
         tell_object( this_player(), "There isn't that much available.\n" );
         return 1;
      }
      adjust_profit( tp, -value );
      if ( args[3] == "bonus" )
      {
         adjust_bonus( tp, value );
      }
      else
      {
         money = MONEY_HAND->make_new_amount( value, _place );
         _counter->add_money_ob( money );
         shop_log( ACCOUNTS, tp, "adjusted the register by "+
           MONEY_HAND->money_value_string( value, _place ) );
      }
      break;
   }
   tell_object( this_player(), "Ok.\n" );
   return 1;
}
/* do_transfer() */


/**
 * @ignore yes
 * Make sure cabinet's dested before removing file.
 */
void rm_cab( string cab_name )
{
#ifdef DEBUG
   debug_printf( "Removing cabinet file %s.\n",
     _save_dir+ _very_short+ "_" + cab_name+ ".o" );
#endif
   unguarded( (: rm, _save_dir + _very_short+ "_"+ cab_name+ ".o" :) );
}
/* rm_cab() */


/**
 * @ignore yes
 * Managers' office.
 * Renting store cabinets.
 */
int do_rent()
{
   if ( !( _num_cabinets < MAX_CABINETS ) )
   {
      tell_object( this_player(), "The shop already contains the "
        "maximum number of cabinets.\n" );
      return 1;
   }
   if ( CABINET_COST > _accounts["profit"] )
   {
      tell_object( this_player(), "Cabinets currently cost "+
        MONEY_HAND->money_value_string( CABINET_COST, _place ) +
        ".  There is not enough money in the profit account.\n" );
      return 1;
   }
   tell_object( this_player(), "Are you sure you wish to rent a cabinet for "+
     MONEY_HAND->money_value_string( CABINET_COST, _place ) + " per month? " );
   input_to( "confirm_cabinet", 0, 1 );
   return 1;
}
/* do_rent() */


/**
 * @ignore yes
 * Managers' office.
 * Removing storeroom cabinets.
 */
int do_remove()
{
   int stock;

   if ( !( (stock = _num_cabinets ) > MIN_CABINETS ) )
   {
      tell_object( this_player(), "The shop already contains the "
        "minimum number of cabinets.\n" );
      return 1;
   }
   if ( sizeof( _storeroom->query_stock(stock) ) )
   {
      tell_object( this_player(), "Cabinet "+ stock+
        " still contains stock.\n" );
      return 1;
   }
   if ( query_cabinet_used(stock) )
   {
      tell_object( this_player(), "Cabinet "+ stock+
        " is still assigned to hold stock.\n" );
      return 1;
   }
   tell_object( this_player(), "Are you sure you wish to remove a cabinet? " );
   input_to( "confirm_cabinet", 0, 0 );
   return 1;
}
/* do_remove() */


/** @ignore yes */
void confirm_cabinet( string confirm, int rent )
{
   string cab_name;

   confirm = lower_case( confirm );
   if ( strlen( confirm ) < 1 ||
     ( confirm[0] != 'y' && confirm[0] != 'n' ) )
   {
       tell_object( this_player(), sprintf("Please enter 'yes' or 'no'.\n"
         "Are you sure you want to %s a cabinet? ", (rent)?"rent":"remove" ) );
       input_to( "confirm_cabinet", 0, rent );
       return ;
   }
   if ( confirm[0] == 'n' )
   {
      tell_object( this_player(), "Ok.\n" );
      return;
   }
   if ( rent )
   {
      if ( _storeroom->add_cabinet() )
      {
         _num_cabinets++;
         adjust_profit( this_player()->query_name(), -CABINET_COST );
         shop_log( ACCOUNTS, this_player()->query_name(),
           "rented cabinet for "+ MONEY_HAND->money_value_string( CABINET_COST,
           _place ) );
         save_me();
      }
   }
   else
   {
      if ( ( cab_name = _storeroom->remove_cabinet() ) != "" )
      {
         _num_cabinets--;
         shop_log( ACCOUNTS, this_player()->query_name(),
           "removed cabinet" );
         if ( file_size( _save_dir + _very_short+ "_"+ cab_name + ".o" ) > 0 )
         {
            call_out( "rm_cab", 5, cab_name );
         }
         save_me();
      }
   }
   tell_object( this_player(), "Cabinet "+ (rent)?"rent":"remove" + "ed.\n" );
}
/* confirm_cabinet() */


/**
 * @ignore yes
 * Managers' office.
 * Set maximum employees & base pay rate.
 */
int do_set( mixed *args, string pattern )
{
   int value, best;
   object money;

   switch( pattern )
   {
      case "maximum <number> [employees]" :
         if ( args[0] < MIN_EMP || args[0] > MAX_EMP )
         {
            tell_object( this_player(), "Must be between "+ MIN_EMP+
              " and "+ MAX_EMP+ ".\n" );
            return 1;
         }
         shop_log( PERSONNEL, this_player()->query_name(),
           "set maximum employees to "+ args[0] );
         _max_emp = args[0];
         break;
      case "pay <number'amount'> <string'type'>" :
         money = clone_object( MONEY_OBJECT );
         money->set_money_array( MONEY_HAND->query_values_in( _place ) );
         if ( ( best = money->find_best_fit( args[1] ) ) == -1 )
         {
            tell_object( this_player(),
              "That currency is not used by the shop.\n" );
            return 1;
         }
         value = args[0] * ( money->query_money_array() )[best + 1];
         money->dest_me();
         if ( value < 1 )
         {
            tell_object( this_player(),
              "You must pay your employees something.\n" );
            return 1;
         }
         shop_log( PERSONNEL, this_player()->query_name(), "set pay to "+
           MONEY_HAND->money_value_string( value, _place ) );
         _pay_val = value;
         add_board_message( "Pay", sprintf( "The base pay rate has today "
           "been set to:\n\n   Managers: %s\n   Supervisors: %s\n   "
           "Employees: %s\n", MONEY_HAND->money_value_string(value * 2, _place),
           MONEY_HAND->money_value_string(to_int(value * 1.5), _place),
           MONEY_HAND->money_value_string(value, _place) ) );
         break;
   }
   tell_object( this_player(), "Ok.\n" );
   save_me();
   return 1;
}
/* do_set() */


/**
 * @ignore yes
 * Managers' office.
 * Commend employees.  Adds 5% of their promotion target.
 */
int do_commend( string emp )
{
   string commender;

   if ( !_employees[emp] )
   {
      tell_object( this_player(), capitalize( emp )+
        " is not an active employee!\n" );
      return 1;
   }
   if ( _employees[emp][EMP_POINTS] & MANAGER )
   {
      tell_object( this_player(), "You can't commend a manager.\n" );
      return 1;
   }
   if ( _employees[emp][EMP_POINTS] & NPC )
   {
      tell_object( this_player(), "Don't be silly!  "
        "You can't commend $C$"+ emp +".\n" );
      return 1;
   }
   commender = this_player()->query_cap_name();
   AUTO_MAILER->auto_mail( emp, lower_case(commender), "Commendation",
     "", "This is to advise you that you have today received a "
     "commendation for outstanding service.\nThis will now stay on "
     "your employment record.\n" );
   employee_log( emp, "Received a commendation from "+ commender );
   shop_log( PERSONNEL, commender, "commended "+ capitalize( emp ) );
   _employees[emp][EMP_POINTS] += (_employees[emp][EMP_POINTS] & SUPERVISOR)?
     to_int(MANAGER_POINTS * 0.05 * 32) : to_int(SUPER_POINTS * 0.05 * 32);
   save_emps();
   tell_object( this_player(), "You commend "+ capitalize( emp )+ ".\n" );
   return 1;
}
/* do_commend() */


/**
 * @ignore yes
 * Managers' office.
 * Warn employees.  Removes 5% of their promotion target.
 */
int do_warn( mixed *args )
{
   string warner;
   object tp;
   int points;

   tp = this_player();
   args[0] = lower_case( args[0] );
   if ( !_employees[args[0]] )
   {
      tell_object( tp, capitalize( args[0] )+
        " is not an active employee!\n" );
      return 1;
   }
   if ( _employees[args[0]][EMP_POINTS] & MANAGER )
   {
      if ( !creatorp(tp) )
      {
         tell_object( tp, "You don't have the authority to "
           "warn $C$"+ args[0] +".\n" );
         return 1;
      }
   }
   if ( _employees[args[0]][EMP_POINTS] & NPC )
   {
      tell_object( tp, "Don't be silly!  You can't warn $C$"+
        args[0] +".\n" );
      return 1;
   }
   warner = tp->query_cap_name();
   AUTO_MAILER->auto_mail( args[0], _proprietor, "Official warning", "",
     "This is to advise you that you have today received a formal "
     "warning for " + args[1] + ".\nThis warning will now stay on "
     "your employment record.\n" );
   employee_log( args[0], "Received a warning from "+ warner+
     " for "+ args[1] );
   shop_log( PERSONNEL, warner, "warned "+
     capitalize( args[0] ) + " for "+ args[1] );
   points = _employees[args[0]][EMP_POINTS] & CLOCKED_IN;
   if ( _employees[args[0]][EMP_POINTS] & SUPERVISOR )
   {
      _employees[args[0]][EMP_POINTS] -= to_int(MANAGER_POINTS * 0.05 * 32);
   }
   else
   {
      _employees[args[0]][EMP_POINTS] -= to_int(SUPER_POINTS * 0.05 * 32) +
        EMPLOYEE;
      if ( _employees[args[0]][EMP_POINTS] < 1 )
      {
         _employees[args[0]][EMP_POINTS] = EMPLOYEE + points;
      }
   }
   save_emps();
   tell_object( tp, "You warn "+ capitalize( args[0] )+ " for "+
     args[1]+ ".\n" );
   return 1;
}
/* do_warn() */


/**
 * @ignore yes
 * Managers' office.
 * Demote supervisors or managers.
 */
int do_demote( string emp )
{
   object tp;

   tp = this_player();
   emp = lower_case( emp );
   if ( !_employees[emp] )
   {
      tell_object( tp, capitalize( emp )+
        " is not an active employee!\n" );
      return 1;
   }
   if ( _employees[emp][EMP_POINTS] & MANAGER )
   {
      if ( !creatorp(tp) )
      {
         tell_object( tp, "You don't have the authority to "
           "demote $C$"+ emp +".\n" );
         return 1;
      }
   }
   if ( !( _employees[emp][EMP_POINTS] & SUPERVISOR ) )
   {
      tell_object( tp, "Don't be silly!  You can't demote $C$"+
        emp +".\n" );
      return 1;
   }
   demote( tp->query_cap_name(), emp );
   tell_object( tp, "You demote "+ capitalize( emp )+ ".\n" );
   return 1;
}
/* do_demote() */


/**
 * @ignore yes
 * Managers' office.
 * Suspend employee's bonus for x months.
 */
int do_suspend( mixed *args )
{
   string suspender;
   object tp;

   args[0] = lower_case( args[0] );
   tp = this_player();
   if ( !_employees[args[0]] )
   {
      tell_object( tp, capitalize( args[0] )+
        " is not an active employee!\n" );
      return 1;
   }
   if ( (_employees[args[0]][EMP_POINTS] & MANAGER) &&
        ( !creatorp(tp) ) )
   {
      tell_object( tp, "You don't have the authority to "
        "suspend $C$"+ args[0] +"'s bonus.\n" );
      return 1;
   }
   if ( _employees[args[0]][EMP_POINTS] & NPC )
   {
      tell_object( tp, "Don't be silly!  "
        "You can't suspend $C$"+ args[0] +"'s bonus.\n" );
      return 1;
   }
   suspender = tp->query_cap_name();
   _employees[args[0]][EMP_NOBONUS] = args[1];
   save_emps();
   AUTO_MAILER->auto_mail( args[0], _proprietor, "Suspended bonus", "",
     sprintf( "This is to advise you that you have had your bonus "
       "entitlement suspended for %d month%s.\nThis suspension will "
       "now stay on your employment record.\n", args[1],
       (args[1] == 1)?"":"s" ) );
   employee_log( args[0], sprintf( "Bonus suspended for %d month%s by %s",
     args[1], (args[1] == 1)?"":"s", suspender ) );
   shop_log( PERSONNEL, suspender,
     sprintf( "suspended %s's bonus for %d month%s", args[0],
     args[1], (args[1] == 1)?"":"s" ) );
   tell_object( tp, "You suspend "+ capitalize( args[0] )+
     "'s bonus for "+ args[1]+ " months.\n" );
   return 1;
}
/* do_suspend() */


/**
 * @ignore yes
 * Managers' office.
 * Place an employee on leave.
 */
int do_leave( mixed *args )
{
   object tp;

   tp = this_player();
   args[0] = lower_case( args[0] );
   if ( !_employees[args[0]] )
   {
      tell_object( tp, capitalize( args[0] )+
        " is not an employee!\n" );
      return 1;
   }
   if ( args[1] > MAX_LEAVE )
   {
      tell_object( tp, "You cannot place an employee on leave "
        "for more than "+ MAX_LEAVE+ " days at a time.\n" );
      return 1;
   }
   _employees[args[0]][EMP_TIME] = time() + ( args[1] * 86400 );
   save_emps();
   add_succeeded_mess( capitalize( args[0] )+
     " is on leave until "+ ctime( time() + ( args[1] * 86400 ) )+
     ".\n" );
   shop_log( PERSONNEL, tp->query_name(), "placed "+
     capitalize( args[0] )+ " on leave for " + args[1]+ " days" );
   tell_object( tp, "You place "+ capitalize( args[0] )+
     " on leave for " + args[1]+ " days" );
   return 1;
}
/* do_leave() */


/**
 * @ignore yes
 * Managers' office.
 * Ban a person from the shop.
 */
int do_ban( mixed *args )
{
   if ( !"/secure/login"->test_user( lower_case( args[0] ) ) )
   {
      tell_object( this_player(), args[0]+ " is not a player.\n" );
      return 1;
   }
   add_baddie( args[0], args[1], this_player()->query_name() );
   tell_object( this_player(), "You ban "+ capitalize( args[0] )+
     " for "+ args[1]+ ".\n" );
   return 1;
}
/* do_ban() */


/**
 * @ignore yes
 * Managers' office.
 * Remove the ban on a person.
 */
int do_unban( string person )
{
   if ( !query_baddie( person ) )
   {
      tell_object( this_player(), person+ " is not currently banned.\n" );
      return 1;
   }
   remove_baddie( person );
   shop_log( GENERAL, this_player()->query_name(),
     "removed the ban on "+ capitalize( person ) );
   tell_object( this_player(), "You remove the ban on "+
     capitalize( person )+ ".\n" );
   return 1;
}
/* do_unban() */


/**
 * @ignore yes
 * Managers' office.
 * Fire an employee.
 */
int do_fire( mixed args ) {
   args[0] = lower_case( args[0] );

   if( !_employees[args[0]] )  {
       tell_object( TP, capitalize( args[0] )+" doesn't work at the shop!\n");
       return 1;
   }

   if( ( member_array( args[0], _retired ) != -1 ||
       query_manager( args[0] ) ) && !creatorp(TP) ) {
       tell_object( TP, "You don't have the authority to fire $C$"+
           args[0] +".\n");
       return 1;
   }

   if( _employees[args[0]][EMP_POINTS] & NPC ) {
       tell_object( TP, "Don't be silly!  You can't fire $C$"+
           args[0] +".\n");
       return 1;
   }

   fire_them( this_player()->query_name(), args[0], args[1] );
   tell_object( this_player(), "You fire "+ capitalize( args[0] )+
     " for "+ args[1]+ ".\n" );
   return 1;
}
/* do_fire() */


/**
 * @ignore yes
 * Managers' office.
 * Vote on an applicant.
 */
int do_vote( mixed *args, string pattern )
{
   string tp;
   int query_app;

   tp = this_player()->query_name();

   if ( pattern == "<word'applicant'> {y|n|a}" )
   {
      args[0] = lower_case( args[0] );
      if ( !( query_app = query_applicant(args[0]) ) )
      {
         tell_object( this_player(), capitalize( args[0] )+
           " hasn't applied!\n" );
         return 1;
      }
      if ( query_app != APPLIED )
      {
         tell_object( this_player(), capitalize( args[0] )+
           " has already been hired!\n" );
         return 1;
      }
      if ( check_vote( args[0], tp ) )
      {
         tell_object( this_player(),
           "You have already voted for this person!\n" );
         return 1;
      }
      if ( args[1] == "y" )
      {
         add_vote( args[0], VFOR );
         tell_object( this_player(), "You vote for " +
           capitalize( args[0] ) + " to work for the shop.\n" );
      }
      else if ( args[1] == "a" )
      {
         add_vote( args[0], VABSTAIN );
         tell_object( this_player(), "You abstain on " +
           capitalize( args[0] ) + ".\n" );
      }
      else
      {
         add_vote( args[0], VAGAINST );
         tell_object( this_player(), "You vote against " +
           capitalize( args[0] ) + " working for the shop.\n" );
      }
   }
   else
   {
      do_policy_vote( tp, args[0], args[1] );
   }
   return 1;
}
/* do_vote() */


/**
 * Someone has entered the room.
 * This function will automatically fire an employee if they have
 * teleported to this room.  To avoid this happening, or to modify,
 * mask this function.
 */
void event_enter( object ob, string message, object from )
{
   string room;

   if ( !from || creatorp(ob) || from == find_object( "/room/void") ||
     !ob->query_player() )
   {
      return;
   }
   room = file_name( from );
   if ( room == _counter || room == _shop_front || room == _storeroom ||
     room == _mgr_office || from == this_object() )
   {
      return;
   }
   else
   {
#ifdef DEBUG
      debug_printf( "%s arrived from %O.\n", ob->query_name(), from );
#endif
      fire_them( _proprietor, ob->query_name(), "for teleporting into the shop" );
   }
}
/* event_enter() */


/**
 * Someone has died.
 * This function will automatically fire an employee if they have
 * killed someone whilst on duty.  It will also make a note of anyone
 * who has killed an on-duty employee (including the npc shopkeeper).  To
 * avoid this happening, or to modify, mask this function.
 */
void event_death( object killed, object *others, object killer,
                  string rmess, string kmess ) {
   object baddie;

   /* Only look for players & the shopkeeper
    * We don't want to fire/ban people for killing a cabbage...
    */
   if( !killer || ( !interactive(killed) &&
       !( query_employee( killed->query_name() ) & NPC ) ) ) {
       return;
   }

   if( ( query_employee( killed->query_name() ) & NPC )  ||
       ( query_employee( killed->query_name() ) ) ) {
        add_baddie( killer->query_name(), "the vicious assault on "+
            killed->query_name(), _proprietor );
        foreach( baddie in others ) {
          if( interactive(baddie) ) {
              add_baddie( baddie->query_name(), "the vicious assault on "+
                  killed->query_name(), _proprietor );
          }
        }
        return;
   }

   if( query_employee( killer->query_name() ) & CLOCKED_IN ) {
       add_baddie( killer->query_name(), "the vicious assault on "+
           killed->query_name(), _proprietor );
   }

   if( sizeof( others ) ) {
       foreach( baddie in others ) {
         if( interactive(baddie) )
             return;
         if( query_employee( baddie->query_name() )& CLOCKED_IN ) {
             add_baddie( baddie->query_name(), "the vicious assault "
                 "on "+ killed->query_name(), _proprietor );
         }
      }
   }
}
/* event_death() */

/**
 * @ignore yes
 * Shop front.
 * Cancel application.
 */
int do_cancel() {
   object tp;

   tp = this_player();
   remove_applicant( tp->query_name() );
   employee_log( tp->query_name(),
     "Cancelled application" );
   tell_object( tp, "You cancel your application.\n" );
   return 1;
}
/* do_cancel() */


/**
 * @ignore yes
 * Shop front.
 * Confirm employment.
 */
int do_confirm()
{
   if(!query_applicant(TP->query_name()))
     return notify_fail("You are not currently an applicant here.\n");
   confirm_employment( TP->query_name() );
   employee_log( TP->query_name(),
     "Confirmed employment" );
   tell_object( TP, "You confirm your employment.\n" );
   return 1;
}
/* do_confirm() */


/**
 * @ignore yes
 * Keep track of net change in register.
 */
void adjust_takings( int amt )
{
   _net_takings += amt;
   save_me();
}
/* adjust_takings() */


/**
 * @ignore yes
 * Keep track of items bought.
 */
void adjust_bought( string item, int amt )
{
   _list[item][CHART_BOUGHT] += amt;
   save_list();
}
/* adjust_bought() */


/**
 * @ignore yes
 * Keep track of items sold.
 */
void adjust_sold( string item, int amt )
{
   _list[item][CHART_SOLD] += amt;
   save_list();
}
/* adjust_sold() */


/**
 * @ignore yes
 * Add standard stuff
 */
void set_long( string long_desc )
{
   long_desc += "Employees may \"clock\" in and out of work here "
     "and examine the items chart.  There is also a policy notice "
     "located next to the chart.\n";
   ::set_long( long_desc );
}
/* set_long() */


/**
 * @ignore yes
 * Update policies in force.
 */
private void add_policy( string policy )
{
   if ( !_policies[policy] )
   {
      _policies += ([ policy:"" ]);
      _policies[policy] = _new_policies[policy][2];
      AUTO_MAILER->auto_mail( _new_policies[policy][1], _proprietor,
        "Policy proposition - "+ policy, "",
        "Your proposition has been accepted by majority vote and is "
        "now shop policy.\n" );
      shop_log( GENERAL, "Shop", "accepted the "+ policy + " policy.\n" );
   }
   else
   {
      map_delete( _policies, policy );
      AUTO_MAILER->auto_mail( _new_policies[policy][1], _proprietor,
        "Policy proposition - "+ policy, "",
        "Your proposition to remove the above policy has been accepted "
        "by majority vote.\n" );
      shop_log( GENERAL, "Shop", "removed the "+ policy + " policy.\n" );
   }
   map_delete( _new_policies, policy );
   save_me();
}
/* add_policy() */


/**
 * @ignore yes
 * Policy update has been unsuccessful.
 */
private void remove_policy( string policy )
{
   AUTO_MAILER->auto_mail( _new_policies[policy][1], "Tarnach Fendertwin",
     "Policy proposition - "+ policy, "",
     "Your proposition has been rejected by majority vote.\n" );
   shop_log( GENERAL, "Shop", "rejected the "+ policy + " policy.\n" );
   map_delete( _new_policies, policy );
   save_me();
}
/* remove_policy() */


/**
 * @ignore yes
 * Add a vote
 */
private int do_policy_vote( string mgr, string policy, string decision )
{
   int managers;

   if ( !m_sizeof(_new_policies) || !_new_policies[policy] )
   {
      tell_object( this_player(), "There is no such policy pending!\n" );
      return 1;
   }
   if ( member_array( mgr, _new_policies[policy][3] ) != -1 ||
     member_array( mgr, _new_policies[policy][4] ) != -1 )
   {
      tell_object( this_player(), "You have already voted on this policy!\n" );
      return 1;
   }
   if ( decision == "y" )
   {
      _new_policies[policy][3] += ({ mgr });
      tell_object( this_player(), "You vote for the " + policy+ " policy.\n" );
   }
   else
   {
      _new_policies[policy][4] += ({ mgr });
      tell_object( this_player(), "You vote against the " + policy+ " policy.\n" );
   }
   shop_log( GENERAL, mgr, "voted on the "+ policy + " policy.\n" );
   save_me();
   managers = sizeof( get_managers() ) + sizeof( get_retired() );
   if ( sizeof( _new_policies[policy][3] ) > managers / 2 )
   {
      add_policy( policy );
   }
   else if ( sizeof( _new_policies[policy][4] ) >= managers / 2 )
   {
      remove_policy( policy );
   }
   return 1;
}
/* do_policy_vote() */


/**
 * Determine if a shop policy exists.
 * @param policy The name of the policy to query.
 * @return 2 if it is already policy, 1 if it is being voted upon, else 0
 */
int query_policy( string policy )
{
   if ( m_sizeof( _policies ) && _policies[policy] ) return 2;
   if ( m_sizeof( _new_policies ) && _new_policies[policy] ) return 1;
   return 0;
}
/* query_policy() */


/**
 * @ignore yes
 * Add a new policy suggestion.
 */
void add_policy_suggest( string name, string text, string mgr )
{
   if ( !sizeof( _new_policies ) )
   {
      _new_policies = ([ name:({0,"","",({}),({}) }) ]);
   }
   else
   {
      _new_policies += ([ name:({0,"","",({}),({}) }) ]);
   }
   if ( sizeof( _policies ) && _policies[name] )
   {
      text = _policies[name][2];
   }
   _new_policies[name][0] = time();
   _new_policies[name][1] = mgr;
   _new_policies[name][2] = text;
   do_policy_vote( mgr, name, "y" );
   save_me();
}
/* add_policy_suggest() */


/**
 * Query the policy suggestions.
 * @return A mapping of the suggested shop policies.
 */
mapping get_new_policies() { return copy( _new_policies ); }


/**
 * Query the policies.
 * @return A mapping of the shop policies.
 */
mapping get_policies() { return copy( _policies ); }


/**
 * Set the stock's main policy.
 * @param desc The main stock description, eg. "magical spell components"
 */
void set_stock_policy( string desc ) { _stock_policy = desc; }


/**
 * @ignore yes
 * Query the shop's main policy - used for the notice.
 */
string get_stock_policy() { return copy( _stock_policy ); }


/**
 * @ignore yes
 * Called when someone is refreshed or deleted
 * Will delete the employee in case of deletion or full refresh.  In case
 * of partial refresh, will reset employee to new employee status.
 */
void refresh_function( mixed employee, int flag )
{
   string emp_name;

   switch ( flag )
   {
      case PLAYER_DELETED :
        emp_name = employee;
        break;
      case TOTAL_REFRESH :
      case PARTIAL_REFRESH :
        emp_name = employee->query_name();
        break;
   }

   if ( query_applicant( emp_name ) )
   {
      remove_employee( emp_name );
      switch ( flag )
      {
         case PLAYER_DELETED :
           shop_log( PERSONNEL, capitalize(emp_name), "deleted character" );
           employee_log( emp_name, "deleted character" );
           break;
         case TOTAL_REFRESH :
         case PARTIAL_REFRESH :
           tell_object( employee, "Your application at "+ _shop_name+
             " has been removed.\n" );
           shop_log( PERSONNEL, capitalize(emp_name), "refreshed character" );
           employee_log( emp_name, "refreshed character" );
           break;
      }
   }

   if ( member_array( emp_name, _retired ) != -1 )
   {
      remove_employee( emp_name );
      switch ( flag )
      {
         case PLAYER_DELETED :
           shop_log( PERSONNEL, capitalize(emp_name), "deleted character" );
           employee_log( emp_name, "deleted character" );
           break;
         case TOTAL_REFRESH :
         case PARTIAL_REFRESH :
           tell_object( employee, "Your employment at "+ _shop_name+
             " has been terminated.\n" );
           shop_log( PERSONNEL, capitalize(emp_name), "refreshed character" );
           employee_log( emp_name, "refreshed character" );
           break;
      }
   }

   if ( !_employees[emp_name] )
   {
      return;
   }

   switch ( flag )
   {
      case PLAYER_DELETED :
        shop_log( PERSONNEL, capitalize(emp_name), "deleted character" );
        employee_log( emp_name, "deleted character" );
        remove_employee( emp_name );
        break;
      case TOTAL_REFRESH :
        tell_object( employee, "Your employment at "+ _shop_name+
          " has been terminated.\n" );
        shop_log( PERSONNEL, capitalize(emp_name), "refreshed totally" );
        employee_log( emp_name, "refreshed totally" );
        remove_employee( emp_name );
        break;
      case PARTIAL_REFRESH :
        tell_object( employee, "Your employment level at "+ _shop_name+
          " has been reset.\n" );
        shop_log( PERSONNEL, capitalize(emp_name), "refreshed character" );
        employee_log( emp_name, "refreshed character" );
        _employees[emp_name] = ({1,0,0,0,0,1,0,});
        set_emp_time( emp_name );
        _got_bonus += ({ emp_name });
        save_me();
        break;
   }
}
/* refresh_function() */

/**
 * Overrides the default save dir.
 * @param dir the new dir
 */
void set_save_dir(string dir) {
  _save_dir = dir;
} /* set_save_dir() */