dw_fluffos_v2/
dw_fluffos_v2/fluffos-2.9-ds2.05/
dw_fluffos_v2/fluffos-2.9-ds2.05/ChangeLog.old/
dw_fluffos_v2/fluffos-2.9-ds2.05/Win32/
dw_fluffos_v2/fluffos-2.9-ds2.05/compat/
dw_fluffos_v2/fluffos-2.9-ds2.05/compat/simuls/
dw_fluffos_v2/fluffos-2.9-ds2.05/include/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/clone/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/command/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/data/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/etc/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/include/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/inherit/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/inherit/master/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/log/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/single/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/single/tests/compiler/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/single/tests/efuns/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/single/tests/operators/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/u/
dw_fluffos_v2/fluffos-2.9-ds2.05/tmp/
dw_fluffos_v2/fluffos-2.9-ds2.05/windows/
dw_fluffos_v2/lib/
dw_fluffos_v2/lib/binaries/cmds/
dw_fluffos_v2/lib/binaries/cmds/creator/
dw_fluffos_v2/lib/binaries/cmds/living/
dw_fluffos_v2/lib/binaries/cmds/player/
dw_fluffos_v2/lib/binaries/d/admin/obj/
dw_fluffos_v2/lib/binaries/d/liaison/
dw_fluffos_v2/lib/binaries/global/virtual/
dw_fluffos_v2/lib/binaries/global/virtual/setup_compiler/
dw_fluffos_v2/lib/binaries/obj/handlers/autodoc/
dw_fluffos_v2/lib/binaries/obj/handlers/terrain_things/
dw_fluffos_v2/lib/binaries/obj/misc/
dw_fluffos_v2/lib/binaries/obj/misc/buckets/
dw_fluffos_v2/lib/binaries/obj/monster/
dw_fluffos_v2/lib/binaries/obj/reactions/
dw_fluffos_v2/lib/binaries/obj/reagents/
dw_fluffos_v2/lib/binaries/secure/cmds/creator/
dw_fluffos_v2/lib/binaries/secure/master/
dw_fluffos_v2/lib/binaries/std/
dw_fluffos_v2/lib/binaries/std/dom/
dw_fluffos_v2/lib/binaries/std/effects/object/
dw_fluffos_v2/lib/binaries/std/guilds/
dw_fluffos_v2/lib/binaries/std/languages/
dw_fluffos_v2/lib/binaries/std/races/
dw_fluffos_v2/lib/binaries/std/room/
dw_fluffos_v2/lib/binaries/std/room/basic/
dw_fluffos_v2/lib/binaries/std/shops/
dw_fluffos_v2/lib/binaries/std/shops/inherit/
dw_fluffos_v2/lib/binaries/www/
dw_fluffos_v2/lib/cmds/guild-race/
dw_fluffos_v2/lib/cmds/guild-race/crafts/
dw_fluffos_v2/lib/cmds/guild-race/other/
dw_fluffos_v2/lib/cmds/playtester/
dw_fluffos_v2/lib/cmds/playtester/senior/
dw_fluffos_v2/lib/d/admin/
dw_fluffos_v2/lib/d/admin/log/
dw_fluffos_v2/lib/d/admin/mapper/31-10-01/mapmaker/event/
dw_fluffos_v2/lib/d/admin/meetings/
dw_fluffos_v2/lib/d/admin/obj/
dw_fluffos_v2/lib/d/admin/room/we_care/
dw_fluffos_v2/lib/d/admin/save/
dw_fluffos_v2/lib/d/dist/
dw_fluffos_v2/lib/d/dist/mtf/
dw_fluffos_v2/lib/d/dist/pumpkin/
dw_fluffos_v2/lib/d/dist/pumpkin/chars/
dw_fluffos_v2/lib/d/dist/pumpkin/desert/
dw_fluffos_v2/lib/d/dist/pumpkin/gumboot/
dw_fluffos_v2/lib/d/dist/pumpkin/hospital/
dw_fluffos_v2/lib/d/dist/pumpkin/inherit/
dw_fluffos_v2/lib/d/dist/pumpkin/map/
dw_fluffos_v2/lib/d/dist/pumpkin/plain/
dw_fluffos_v2/lib/d/dist/pumpkin/pumpkin/
dw_fluffos_v2/lib/d/dist/pumpkin/save/
dw_fluffos_v2/lib/d/dist/pumpkin/squash/
dw_fluffos_v2/lib/d/dist/pumpkin/terrain/
dw_fluffos_v2/lib/d/dist/pumpkin/woods/
dw_fluffos_v2/lib/d/dist/start/
dw_fluffos_v2/lib/d/learning/TinyTown/buildings/
dw_fluffos_v2/lib/d/learning/TinyTown/map/
dw_fluffos_v2/lib/d/learning/TinyTown/roads/
dw_fluffos_v2/lib/d/learning/add_command/
dw_fluffos_v2/lib/d/learning/arms_and_weps/
dw_fluffos_v2/lib/d/learning/chars/
dw_fluffos_v2/lib/d/learning/cutnpaste/
dw_fluffos_v2/lib/d/learning/examples/npcs/
dw_fluffos_v2/lib/d/learning/examples/player_houses/npcs/
dw_fluffos_v2/lib/d/learning/examples/terrain_map/basic/
dw_fluffos_v2/lib/d/learning/functions/
dw_fluffos_v2/lib/d/learning/handlers/
dw_fluffos_v2/lib/d/learning/help_topics/npcs/
dw_fluffos_v2/lib/d/learning/help_topics/objects/
dw_fluffos_v2/lib/d/learning/help_topics/rcs_demo/
dw_fluffos_v2/lib/d/learning/help_topics/rooms/
dw_fluffos_v2/lib/d/learning/help_topics/rooms/crowd/
dw_fluffos_v2/lib/d/learning/help_topics/rooms/situations/
dw_fluffos_v2/lib/d/learning/items/
dw_fluffos_v2/lib/d/learning/save/
dw_fluffos_v2/lib/d/liaison/
dw_fluffos_v2/lib/d/liaison/NEWBIE/doc/
dw_fluffos_v2/lib/d/liaison/NEWBIE/save/oldlog/
dw_fluffos_v2/lib/db/
dw_fluffos_v2/lib/doc/
dw_fluffos_v2/lib/doc/creator/
dw_fluffos_v2/lib/doc/creator/autodoc/include/reaction/
dw_fluffos_v2/lib/doc/creator/autodoc/include/ritual_system/
dw_fluffos_v2/lib/doc/creator/autodoc/include/talker/
dw_fluffos_v2/lib/doc/creator/autodoc/include/terrain_map/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/baggage/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/clock/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/clothing/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/cont_save/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/corpse/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/money/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/monster/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/scabbard/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/service_provider/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/state_changer/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/wand/
dw_fluffos_v2/lib/doc/creator/autodoc/std/book_dir/
dw_fluffos_v2/lib/doc/creator/autodoc/std/key/
dw_fluffos_v2/lib/doc/creator/autodoc/std/learning/
dw_fluffos_v2/lib/doc/creator/autodoc/std/map/
dw_fluffos_v2/lib/doc/creator/autodoc/std/race/
dw_fluffos_v2/lib/doc/creator/autodoc/std/weapon_logic/
dw_fluffos_v2/lib/doc/creator/files/
dw_fluffos_v2/lib/doc/creator/policy/
dw_fluffos_v2/lib/doc/creator/room/
dw_fluffos_v2/lib/doc/effects/
dw_fluffos_v2/lib/doc/ideas/
dw_fluffos_v2/lib/doc/known_command/
dw_fluffos_v2/lib/doc/lpc/basic_manual/
dw_fluffos_v2/lib/doc/lpc/intermediate/
dw_fluffos_v2/lib/doc/new/add_command/
dw_fluffos_v2/lib/doc/new/handlers/
dw_fluffos_v2/lib/doc/new/living/
dw_fluffos_v2/lib/doc/new/living/race/
dw_fluffos_v2/lib/doc/new/living/spells/
dw_fluffos_v2/lib/doc/new/player/
dw_fluffos_v2/lib/doc/new/room/guild/
dw_fluffos_v2/lib/doc/new/room/outside/
dw_fluffos_v2/lib/doc/new/room/storeroom/
dw_fluffos_v2/lib/doc/object/
dw_fluffos_v2/lib/doc/playtesters/
dw_fluffos_v2/lib/doc/policy/
dw_fluffos_v2/lib/doc/weapons/
dw_fluffos_v2/lib/global/handlers/
dw_fluffos_v2/lib/global/virtual/setup_compiler/
dw_fluffos_v2/lib/include/
dw_fluffos_v2/lib/include/cmds/
dw_fluffos_v2/lib/include/effects/
dw_fluffos_v2/lib/include/npc/
dw_fluffos_v2/lib/include/shops/
dw_fluffos_v2/lib/net/daemon/chars/
dw_fluffos_v2/lib/net/inherit/
dw_fluffos_v2/lib/net/intermud3/
dw_fluffos_v2/lib/net/intermud3/services/
dw_fluffos_v2/lib/net/obj/
dw_fluffos_v2/lib/net/save/
dw_fluffos_v2/lib/net/smnmp/
dw_fluffos_v2/lib/net/snmp/
dw_fluffos_v2/lib/obj/amulets/
dw_fluffos_v2/lib/obj/b_day/
dw_fluffos_v2/lib/obj/examples/
dw_fluffos_v2/lib/obj/food/alcohol/
dw_fluffos_v2/lib/obj/food/chocolates/
dw_fluffos_v2/lib/obj/food/fruits/
dw_fluffos_v2/lib/obj/food/meat/
dw_fluffos_v2/lib/obj/food/nuts/
dw_fluffos_v2/lib/obj/food/seafood/
dw_fluffos_v2/lib/obj/food/vegetables/
dw_fluffos_v2/lib/obj/fungi/
dw_fluffos_v2/lib/obj/furnitures/artwork/
dw_fluffos_v2/lib/obj/furnitures/bathroom/
dw_fluffos_v2/lib/obj/furnitures/beds/
dw_fluffos_v2/lib/obj/furnitures/cabinets/
dw_fluffos_v2/lib/obj/furnitures/chairs/
dw_fluffos_v2/lib/obj/furnitures/chests/
dw_fluffos_v2/lib/obj/furnitures/clocks/
dw_fluffos_v2/lib/obj/furnitures/crockery/
dw_fluffos_v2/lib/obj/furnitures/cupboards/
dw_fluffos_v2/lib/obj/furnitures/cushions/
dw_fluffos_v2/lib/obj/furnitures/fake_plants/
dw_fluffos_v2/lib/obj/furnitures/lamps/
dw_fluffos_v2/lib/obj/furnitures/mirrors/
dw_fluffos_v2/lib/obj/furnitures/outdoor/
dw_fluffos_v2/lib/obj/furnitures/safes/
dw_fluffos_v2/lib/obj/furnitures/shelves/
dw_fluffos_v2/lib/obj/furnitures/sideboards/
dw_fluffos_v2/lib/obj/furnitures/sofas/
dw_fluffos_v2/lib/obj/furnitures/stoves/
dw_fluffos_v2/lib/obj/furnitures/tables/
dw_fluffos_v2/lib/obj/furnitures/wardrobes/
dw_fluffos_v2/lib/obj/handlers/
dw_fluffos_v2/lib/obj/handlers/autodoc/
dw_fluffos_v2/lib/obj/jewellery/anklets/
dw_fluffos_v2/lib/obj/jewellery/bracelets/
dw_fluffos_v2/lib/obj/jewellery/earrings/
dw_fluffos_v2/lib/obj/jewellery/misc/
dw_fluffos_v2/lib/obj/jewellery/necklaces/
dw_fluffos_v2/lib/obj/jewellery/rings/
dw_fluffos_v2/lib/obj/media/
dw_fluffos_v2/lib/obj/misc/buckets/
dw_fluffos_v2/lib/obj/misc/jars/
dw_fluffos_v2/lib/obj/misc/papers/
dw_fluffos_v2/lib/obj/misc/player_shop/
dw_fluffos_v2/lib/obj/misc/shops/
dw_fluffos_v2/lib/obj/misc/traps/
dw_fluffos_v2/lib/obj/monster/
dw_fluffos_v2/lib/obj/monster/godmother/
dw_fluffos_v2/lib/obj/monster/transport/
dw_fluffos_v2/lib/obj/plants/inherit/
dw_fluffos_v2/lib/obj/potions/
dw_fluffos_v2/lib/open/boards/
dw_fluffos_v2/lib/save/autodoc/
dw_fluffos_v2/lib/save/bank_accounts/
dw_fluffos_v2/lib/save/boards/frog/
dw_fluffos_v2/lib/save/books/bed_catalog/
dw_fluffos_v2/lib/save/creators/
dw_fluffos_v2/lib/save/mail/
dw_fluffos_v2/lib/save/mail/p/
dw_fluffos_v2/lib/save/soul/data/
dw_fluffos_v2/lib/save/tasks/
dw_fluffos_v2/lib/save/vaults/
dw_fluffos_v2/lib/secure/cmds/lord/
dw_fluffos_v2/lib/secure/config/
dw_fluffos_v2/lib/secure/items/
dw_fluffos_v2/lib/secure/player/
dw_fluffos_v2/lib/soul/
dw_fluffos_v2/lib/soul/i/
dw_fluffos_v2/lib/soul/j/
dw_fluffos_v2/lib/soul/k/
dw_fluffos_v2/lib/soul/o/
dw_fluffos_v2/lib/soul/q/
dw_fluffos_v2/lib/soul/to_approve/
dw_fluffos_v2/lib/soul/u/
dw_fluffos_v2/lib/soul/v/
dw_fluffos_v2/lib/soul/wish_list/
dw_fluffos_v2/lib/soul/y/
dw_fluffos_v2/lib/soul/z/
dw_fluffos_v2/lib/std/creator/
dw_fluffos_v2/lib/std/effects/
dw_fluffos_v2/lib/std/effects/attached/
dw_fluffos_v2/lib/std/effects/external/
dw_fluffos_v2/lib/std/effects/fighting/
dw_fluffos_v2/lib/std/effects/other/
dw_fluffos_v2/lib/std/environ/
dw_fluffos_v2/lib/std/guilds/
dw_fluffos_v2/lib/std/hospital/
dw_fluffos_v2/lib/std/house/
dw_fluffos_v2/lib/std/house/onebedhouse/
dw_fluffos_v2/lib/std/house/onebedhut/
dw_fluffos_v2/lib/std/house/tworoomflat/
dw_fluffos_v2/lib/std/languages/
dw_fluffos_v2/lib/std/liquids/
dw_fluffos_v2/lib/std/nationality/
dw_fluffos_v2/lib/std/nationality/accents/
dw_fluffos_v2/lib/std/nationality/accents/national/
dw_fluffos_v2/lib/std/nationality/accents/regional/
dw_fluffos_v2/lib/std/npc/goals/
dw_fluffos_v2/lib/std/npc/goals/basic/
dw_fluffos_v2/lib/std/npc/goals/misc/
dw_fluffos_v2/lib/std/npc/inherit/
dw_fluffos_v2/lib/std/npc/plans/
dw_fluffos_v2/lib/std/npc/plans/basic/
dw_fluffos_v2/lib/std/outsides/
dw_fluffos_v2/lib/std/races/shadows/
dw_fluffos_v2/lib/std/room/basic/topography/
dw_fluffos_v2/lib/std/room/controller/
dw_fluffos_v2/lib/std/room/controller/topography/
dw_fluffos_v2/lib/std/room/furniture/games/
dw_fluffos_v2/lib/std/room/furniture/inherit/
dw_fluffos_v2/lib/std/room/inherit/carriage/
dw_fluffos_v2/lib/std/room/inherit/topography/
dw_fluffos_v2/lib/std/room/punishments/
dw_fluffos_v2/lib/std/room/topography/area/
dw_fluffos_v2/lib/std/room/topography/iroom/
dw_fluffos_v2/lib/std/room/topography/milestone/
dw_fluffos_v2/lib/std/shadows/
dw_fluffos_v2/lib/std/shadows/attached/
dw_fluffos_v2/lib/std/shadows/curses/
dw_fluffos_v2/lib/std/shadows/disease/
dw_fluffos_v2/lib/std/shadows/fighting/
dw_fluffos_v2/lib/std/shadows/room/
dw_fluffos_v2/lib/std/shops/controllers/
dw_fluffos_v2/lib/std/shops/objs/
dw_fluffos_v2/lib/std/shops/player_shop/
dw_fluffos_v2/lib/std/shops/player_shop/office_code/
dw_fluffos_v2/lib/std/socket/
dw_fluffos_v2/lib/www/
dw_fluffos_v2/lib/www/external/autodoc/
dw_fluffos_v2/lib/www/external/java/telnet/Documentation/
dw_fluffos_v2/lib/www/external/java/telnet/Documentation/images/
dw_fluffos_v2/lib/www/external/java/telnet/examples/
dw_fluffos_v2/lib/www/external/java/telnet/tools/
dw_fluffos_v2/lib/www/pics/
dw_fluffos_v2/lib/www/secure/creator/
dw_fluffos_v2/lib/www/secure/editors/
dw_fluffos_v2/lib/www/secure/survey_results/
dw_fluffos_v2/win32/
/*  -*- LPC -*-  */
/*
 * $Locker:  $
 * $Id: playtesters.c,v 1.29 2002/08/11 16:26:47 drakkos Exp $
 * $Log: playtesters.c,v $
 * Revision 1.29  2002/08/11 16:26:47  drakkos
 * Various updates
 *
 * Revision 1.28  2002/07/19 02:16:35  drakkos
 * Adjust quota fix
 *
 * Revision 1.27  2002/07/16 22:35:49  pinkfish
 * Fix up the remove stuff to actually delete empty lists.
 *
 * Revision 1.26  2002/07/16 21:39:13  pinkfish
 * make it handle multiple destinations from the current location.
 *
 * Revision 1.25  2002/07/16 21:00:24  pinkfish
 * Add in jump points.
 *
 * Revision 1.24  2002/07/16 20:49:18  drakkos
 *  Forcibly unlocked by pinkfish
 *
 * Revision 1.23  2002/07/04 10:40:21  pinkfish
 * Fix up the logging.
 *
 * Revision 1.22  2002/07/04 10:35:22  drakkos
 *  Forcibly unlocked by pinkfish
 *
 * Revision 1.21  2002/02/21 21:27:20  arienne
 * Every IDEA report, every tenth BUG report and no TYPO reports are
 * now posted to the "playtesters" board.
 *
 *
 * Revision 1.20  2001/11/03 10:29:54  arienne
 * Added check that they actually are a playtester before putting them on
 * leave.  Ditto with off leave.
 *
 * Revision 1.17  2001/10/23 18:47:20  arienne
 * Added query_show_list_colour() -- like query_show_list(), except it Burns
 * your Eyes with Fluorescent Goodness.
 *
 * Revision 1.16  2001/05/13 16:59:42  arienne
 * Fixed up monthly roll-over so that it adds the new month to 'months'.
 *
 * Revision 1.11  2001/03/25 01:33:45  ceres
 * Stopped the 'ptesters' command loaded the player files of every playtesters
 *
 * Revision 1.9  2000/11/15 16:41:42  pinkfish
 * Make it wrap the other way.
 *
 * Revision 1.8  2000/11/14 20:28:51  pinkfish
 * Make it post wrapped note to the board.
 * .
 *
 * Revision 1.7  2000/11/14 20:22:53  terano
 *  Forcibly unlocked by pinkfish
 *
 * Revision 1.6  2000/03/29 06:05:21  ceres
 * rcsi /obj/handlers/playtesters.c
 * Modified to use the player handler directly.
 *
 * Revision 1.5  1999/12/01 09:28:21  taffyd
 * Added super new functions! Bonus functionality for no cost.
 *
 * Revision 1.4  1999/03/17 01:42:53  gruper
 * Added query_tester()
 *
 * Revision 1.3  1998/06/04 01:53:57  presto
 * Made it check the date every time a report is filed so that reports at the
 * beginning of a month go onto the correct total.  Long uptimes were causing
 * most of Jun 1st bugs to be filed under May, etc.
 *
 * Revision 1.2  1998/02/04 05:01:37  sin
 * test_age() sometimes returns a negative value (when the player is still logged in).  I added a wrapper that takes the absolute value.
 *
 * Revision 1.1  1998/01/06 05:03:33  ceres
 * Initial revision
 * 
*/
/**
 * Keeps track of all the current playtesters.  This keeps track of all
 * the current playtesters.
 */
#include <board.h>
#include <project_management.h>
#include <player_handler.h>
#include <playtesters.h>
#include <login.h>

#define SAVE_FILE "/save/playtesters"
#define LEVEL 150

#define NORMAL_PLAYTESTER 1
#define SENIOR_PLAYTESTER 2

// every IDEA report is being posted

// post every tenth BUG report
#define BUG_SKIP 10

// not posting any TYPO reports, or other sorts of report

// this is the counter for BUG reports, nothing else
private int count = 0;

private nosave int updating = 0;

private string* months = ({});
private mapping pts = ([]);
private mapping playtesters = ([]);
private mapping ages = ([]);
private mapping guilds = ([]);
private mapping bugs = ([]);
private mapping leave = ([]);
private mapping tmp_pts = ([ ]);
private mapping _jump_points;
private string *executives = ({ });

int query_leave (string);

/** @ignore yes */
void save_file() {
    unguarded( (: save_object, SAVE_FILE :) );
} /* save_file() */


void create() {
   seteuid( master()->creator_file( file_name( this_object() ) ) );

   months = ({ });
   pts = ([ ]);
   ages = ([ ]);
   guilds = ([ ]);
   bugs = ([ ]);
   playtesters = ([ ]); /// XXX temporary
   leave = ([ ]);
   tmp_pts = ([ ]);
   _jump_points = ([ ]);
   executives = ({ });
   
   if ( file_size( SAVE_FILE + ".o" ) > 0 )
      unguarded( (: restore_object, SAVE_FILE, 1 :) );
 
     
   call_out( "reset", 2 );
} /* create() */


/**
 * This method returns a list of the current play testers.  This is
 * a list of string names in an array.
 * @return the list of current playtesters
 */
string* query_playtesters() {
    return copy( keys( pts ) );
} /* query_playtesters() */

/**
 * This method tests to see if the given name is a playtester.
 * @return 1 if they are a playtester, 0 if they are not.
 * @see add_playtester()
 * @see remove_playtester()
 * @see query_senior_playtester()
 */
int query_playtester( string name ) {
  return !undefinedp( pts[ name ] );
} /* query_playtester() */

/**
 * This method should never ever be used.  Use of this call is meant to be
 * broken.  Old code that uses check_playtester() probably meant to use
 * query_playtester().  Use this instead (or better, query_tester()).  If you
 * wish to hire a playtester using this call (which is what it actually did!),
 * please look at hire_playtester() and add_playtester().
 * @see query_playtester()
 * @see query_tester()
 * @see hire_playtester()
 * @see add_playtester()
 * @return Always returns 0.
 */
int check_playtester( string ) {
    log_file( "PLAYTESTERS", file_name( previous_object() ) + " using "
              "deprecated check_playtester() call\n" );
    return 0;
} /* check_playtester() */

/**
 * This method tests to see if "person" is an authorized tester,
 * i.e. a playtester, creator or has the "test character" property.
 * @param person A string or object to determine tester status for.
 * @return 1 if they are a tester, 0 if they are not.
 * @see query_playtester()
 */
int query_tester( mixed person ) {
    object ob;
    string str;

    if ( stringp( person ) ) {
        ob = find_player( person );
        if ( !ob ) {
            return 0;
        }
        str = person;
    } else if ( objectp( person ) ) {
        ob = person;
        str = person->query_name();
    } else {
        return 0;
    }

    return ( ob->query_creator() ||
             ob->query_property( "test character" ) ||
             query_playtester( str ) );
} /* query_tester() */

/**
 * This method finds the age of the playtester from the player handler.  NOTE:
 * This causes the playerfile to be loaded if it hasn't been already - this
 * will cause LOTS OF LAG if done on a lot of playerfiles.
 * @param name the name of the playtester to check
 * @return an integer representing the age of the playtester in seconds.
 * Always >= 0.
 */ 
protected int get_age_raw( string name ) {
    int fu = PLAYER_HANDLER->test_age( name );
    if ( fu < 0 )
        fu = -fu;
    return fu;
} /* get_age_raw() */

/**
 * This method will get the age of a playtester from this handlers' cache.
 * @param name the name of the playtester to check
 * @return an integer representing the age of the playtester in
 * seconds.  Always >= 0.
 */
int get_age_cached( string name ) {
    if ( ages[ name ] < 0 )
        ages[ name ] = -ages[ name ];
    return ages[ name ];
} /* get_age_cached() */

/**
 * This method will update the playtesters' age if they are online and return
 * the value from the cache.
 * @param name the name of the playtester to check
 * @return an integer representing the age of the playtester in seconds.
 * Always >= 0.
 */
int get_age_uncached( string name ) {
    if ( find_player( name ) )
        ages[ name ] = find_player( name )->query_time_on();
    return get_age_cached( name );
} /* get_age_uncached() */

/**
 * This method will update the playtesters' age from the player handler.
 * NOTE: This causes the playerfile to be loaded if it hasn't been already -
 * this will cause LOTS OF LAG if done on a log of playerfiles.
 * @param name the name of the playtester to check
 * @return an integer representing the age of the playtester in seconds.
 * Always >= 0.
 */
int get_age_uncached_offline( string name ) {
    ages[ name ] = get_age_raw( name );
    return get_age_cached( name );
} /* get_age_uncached_offline() */

/**
 * This method determines if they are a senior playtester.
 * @param name the name of the playtester to check
 * @return 1 if they are a senior playteser, 0 if they are not
 * @see add_senior_playtester()
 * @see remove_senior_playtester()
 * @see query_playtester()
 */
int query_senior_playtester( string name ) {
    return ( pts[ name ] == SENIOR_PLAYTESTER );
} /* query_senior_playtester() */


/**
 * This method adds the playtester to the array of current play
 * testers.  NOTE: This uses get_age_raw(), which will cause lag if called on
 * lots of playerfiles.
 * @param name the name of the player to add
 * @return 1 if successful, 0 on failure
 * @see remove_playtester()
 * @see query_playtester()
 * @see add_senior_playtester()
 * @see valid_playtester()
 */
int add_playtester( string name ) {
    if ( pts[ name ] )
        return 0;
    log_file( "PLAYTESTERS", ctime( time() ) + ": " + name +
              " added as a playtester by %s\n", this_player()->query_name() );
    pts[ name ] = NORMAL_PLAYTESTER;
    ages[ name ] = get_age_uncached_offline( name );
    guilds[ name ] = PLAYER_HANDLER->test_guild( name );
    bugs[ name ] = ([ ]);
    if (tmp_pts[name]) {
      if (tmp_pts[name] == SENIOR_PLAYTESTER) {
        pts[name] = SENIOR_PLAYTESTER;
      }
      map_delete (tmp_pts, name);
    }
    PT_APPLICATION_HANDLER->delete_applicant (name);
    save_file();
    return 1;
} /* add_playtester() */

/**
 * This method adds the player as a senior playtester.  The playtester
 * needs to already be added as a normal playtester before becoming a
 * senior playtester.
 * @param name the name of the player to add
 * @return 1 on success, 0 on failure
 * @see remove_senior_playtester()
 * @see add_playtester()
 * @see query_senior_playtester()
 */
int add_senior_playtester( string name ) {
    if ( pts[ name ] != NORMAL_PLAYTESTER )
        return 0;
    log_file( "PLAYTESTERS", ctime( time() ) + ": " + name +
              " promoted to senior playtester by %s\n",
              this_player()->query_name() );
    pts[ name ] = SENIOR_PLAYTESTER;
    save_file();
    return 1;
} /* add_senior_playtester() */

/**
 * This method adds the player as a senior playtester.  The playtester
 * needs to already be added to become a senior playtester.
 * @param name the name of the player to remove
 * @return 1 on success, 0 on failure
 * @see add_senior_playtester()
 * @see remove_playtester()
 * @see query_senior_playtester()
 */
int remove_senior_playtester( string name, string reason ) {
    if ( pts[ name ] != SENIOR_PLAYTESTER )
        return 0;
    log_file( "PLAYTESTERS", ctime( time() ) + ": " + name +
              " demoted from senior playtester; Reason: " + reason +
              " by "+ this_player()->query_name() + "\n" );
    pts[ name ] = NORMAL_PLAYTESTER;
    save_file();
    return 1;
} /* remove_senior_playtester() */

/**
 * This method will remove the playtester from the current list of
 * play testers.
 * @param name the name of the player to remove
 * @param reason the reason for the removal
 * @return 1 on success, 0 on failure
 * @see add_playtester()
 * @see remove_senior_playtester()
 * @see query_playtester()
 */
int remove_playtester( string name, string reason ) {
    string str;
    if ( !query_playtester( name ) )
        return 0;
    str = ctime( time() ) +": "+ name + " removed as a playtester";
    if ( reason ) {
        str += "; Reason: " + reason;
    }
    str += " by "+ this_player()->query_name() + "\n" ;
    log_file( "PLAYTESTERS", str + "\n" );
    map_delete( pts, name );
    map_delete( ages, name );
    map_delete( guilds, name );
    map_delete( bugs, name );
    PROJECT_HANDLER->clear_playtesting_projects (({name }));
    save_file();
    return 1;
} /* remove_playtester() */

/**
 * THis method returns a string reason why they are an invalid play
 * tester.  NOTE: This uses the player_handler, which will cause lag if
 * called on lots of playerfiles.
 * @param name the player to check
 * @return the string reason, 0 if none
 * @see valid_playtester()
 */
string reason_invalid_playtester(string name) {
    if ( !PLAYER_HANDLER->test_user( name ) ) {
        return "not a user";
    }
    if ( PLAYER_HANDLER->test_creator( name ) ) {
        return "currently a creator";
    }
    if ( PLAYER_HANDLER->test_level( name ) < LEVEL ) {
        return "level is too low";
    }
    return 0; 
} /* reason_invalid_playtester() */

/**
 * This method determins if they are a valid playtester or not.  NOTE: This
 * uses reason_invalid_playtester(), which will cause lag if called on lots of
 * playerfiles.
 * @param name the name of the player to test
 * @return 1 if they are, 0 if they are not
 * @see reason_invalid_playtester()
 */
int valid_playtester( string name ) {
    return ( !stringp( reason_invalid_playtester( name ) ) );
} /* valid_playtester() */

/**
 * This method checks to make sure an object can be a playtester, and if so,
 * adds them.
 * @param thing The player object to add as a playtester
 * @return 1 on success, 0 on failure.
 */
int hire_playtester( object thing ) {
    string name;

    if ( !objectp( thing ) )
        return 0;
    name = thing->query_name();
    if ( !stringp( name ) )
        return 0;
    if ( !valid_playtester( name ) )
        return 0;
    return add_playtester( name );
} /* check_playtester() */

/**
 * This adjusts the quota of a playtester.
 *
 * @param name The query_name() of the playtester.
 * @param amount A integer to adjust by.
 */
void adjust_quota( string name, int amount ) {
    string* bits;
    string right_now;
    int* bing;
    int age;
    
    bits = explode( ctime( time() ), " " ) - ({ "" });
    right_now = bits[ 1 ] + " " + bits[ 4 ];

    if (!bugs[name][right_now]) {
      bugs[name][right_now] = 0;
    }
    bing = bugs[ name ][ right_now ];
    age = get_age_uncached( name );
    
    if ( arrayp( bing ) && ( sizeof( bing ) == 3 ) ) {
        bing[ 0 ] += amount;
        bing[ 1 ] += age - bing[ 2 ];
        bing[ 2 ] = age;
    } else {
        bing = ({ amount, 0, age });
    }
    bugs[ name ][ right_now ] = bing;
    save_file();
} /* adjust_quota() */

/**
 * This method determines if a playtester is valid or not, and if not will
 * automagically remove them (does not remove senior playtesters
 * automatically).  NOTE: This uses valid_playtester() and
 * reason_invalid_playtester(), which will cause lag if called on lots of
 * playerfiles.
 * @param name the name of the player to test
 * @see reason_invalid_playtester()
 */
protected void check_playtesters_one( string name ) {
    if ( !valid_playtester( name ) ) {
        remove_playtester( name, reason_invalid_playtester( name ) +
                           " [automatic]" );
    } else {
        adjust_quota( name, 0 );
    }
} /* check_playtesters_one() */

/**
 * This goes through the list of playtesters and checks to make
 * sure they are all still valid.  It does so slowly, using a call_out until
 * it gets to the end.
 * @param who A string* of the query_name()'s of the playtesters to check.
 * @see valid_playtester()
 * @see remove_playtester()
 */
void check_playtesters( string* who ) {
    string* bits;
    string right_now;
    
    if ( sizeof( who ) ) {
        check_playtesters_one( who[ 0 ] );
        if ( sizeof( who ) > 1 ) {
            call_out( "check_playtesters", random( 10 ) + 5, who[ 1.. ] );
        } else {
            bits = explode( ctime( time() ), " " ) - ({ "" });
            right_now = bits[ 1 ] + " " + bits[ 4 ];
            if ( member_array( right_now, months ) < 0 ) {
                months += ({ right_now });
                save_file();
            }
        }            
    }
} /* check_playtesters() */


/**
 * @ignore yes
 * This just fixes up the quotas to contain zeroes for months that they are
 * present but do not file reports.  It does this every 12 resets.
 */
void reset() {
    if ( !updating )
        call_out( "check_playtesters", 30, copy( keys( pts ) ) );
    
    updating++;
    updating %= 12;
} /* reset() */

/**
 * This is called when a playtester makes a bug report.
 * @param name the name of the playtester
 * @param type the type of report
 * @param file the file the report is on
 * @param text the text associated with the report
 */
void report_made( string name, string type, string file, string text ) {
    int post = 0;
   
    if ( !query_playtester( name ) )
        return;

    if ( regexp( type, "IDEA" ) ) {
        post = 1;
    } else if ( regexp( type, "BUG" ) ) {
        count = ( count + 1 ) % BUG_SKIP;
        post = ( count == 0 );
    }

    if ( post ) {
        BOARD_HAND->add_message( "playtester_bugs", capitalize( name ),
                                 type + " for " + file,
                                 sprintf( "%75-=s", text ) );
    }
    
    adjust_quota( name, 1 );
} /* report_made() */

/**
 * This is a nice way of showing a list of the current play testers.
 * It prints a table of the previous four and current months, with columns
 * of bug reports made and number of hours online spent.
 */
string query_show_list() {
    string result;

    result = "              ";
    foreach ( string month in months[ <5.. ] ) {
        result += "    " + month;
    }
    result += "\n";
   
    foreach ( string name in sort_array( query_playtesters(), 1 ) ) {
        if ( query_senior_playtester( name ) ) {
            result += "S ";
        } else {
            result += "  ";
        }
        
        if ( stringp( leave[ name ] ) )
            result += sprintf( "%-14s", "[" + name + "]" );
        else
            result += sprintf( " %-12s ", name );
        
        foreach ( string month in months[ <5.. ] ) {
            if ( sizeof( bugs[ name ][ month ] ) >= 2 )
                result += sprintf( "   %4d %4d", bugs[ name ][ month ][ 0 ],
                                   ( bugs[ name ][ month ][ 1 ] / 3600 ) );
            else
                result += "   ---- ----";
        }
        result += "\n";
    }
    return result;
} /* show_list() */

/**
 * This is a nice way of showing a list of the current play testers.
 * It prints a table of the previous four and current months, with columns
 * of bug reports made and number of hours online spent.
 */
string query_show_list_colour() {
    string result;
    string colour;
    int online, reports;
    string temp;

    result = "              ";
    foreach ( string month in months[ <5.. ] ) {
        result += "    " + month;
    }
    result += "\n";
   
    foreach ( string name in sort_array( query_playtesters(), 1 ) ) {
        reports = 0;
        online = 0;

        if ( query_senior_playtester( name ) ) {
            temp = "S ";
        } else {
            temp = "  ";
        }
        temp += sprintf( "%%s%-12s%%%%^RESET%%%%^", name );
        foreach ( string month in months[ <5.. ] ) {
	          if (query_leave (name)) {
                    colour = "%%^WHITE%%^";	            
	          }        			
            else if ( sizeof( bugs[ name ][ month ] ) >= 2 ) {

                reports += bugs[ name ][ month ][ 0 ];
                online += bugs[ name ][ month ][ 1 ];
                
                if ( bugs[ name ][ month ][ 1 ] == 0 )
                    colour = ( stringp( leave[ name ] ) ?
                               "%%^MAGENTA%%^" :
                               "%%^BOLD%%^%%^MAGENTA%%^" );
                else if ( bugs[ name ][ month ][ 0 ] >=
                     ( bugs[ name ][ month ][ 1 ] / 7200 ) )
                    colour = ( stringp( leave[ name ] ) ?
                               "%%^GREEN%%^" :
                               "%%^BOLD%%^%%^GREEN%%^" );
                else if ( bugs[ name ][ month ][ 0 ] <
                          ( bugs[ name ][ month ][ 1 ] / 24000 ) )
                    colour = ( stringp( leave[ name ] ) ?
                               "%%^RED%%^" :
                               "%%^BOLD%%^%%^RED%%^" );
                else
                    colour = ( stringp( leave[ name ] ) ?
                               "%%^ORANGE%%^" :
                               "%%^YELLOW%%^" );
                
                temp += sprintf( "   %s%4d %4d%%%%^RESET%%%%^",
                                 colour,
                                 bugs[ name ][ month ][ 0 ],
                                 ( bugs[ name ][ month ][ 1 ] / 3600 ) );
            } else
                temp += "   ---- ----";
        }
        temp += "\n";
        
        if ( online == 0 )
            colour = ( stringp( leave[ name ] ) ?
                       "%^BOLD%^%^WHITE%^" :
                       "%^BOLD%^%^MAGENTA%^" );
        else if ( reports >= ( online / 7200 ) )
            colour = ( stringp( leave[ name ] ) ?
                       "%^GREEN%^" :
                       "%^BOLD%^%^GREEN%^" );
        else if ( reports < ( online / 24000 ) )
            colour = ( stringp( leave[ name ] ) ?
                       "%^RED%^" :
                       "%^BOLD%^%^RED%^" );
        else
            colour = ( stringp( leave[ name ] ) ?
                       "%^ORANGE%^" :
                       "%^YELLOW%^" );
        
        result += sprintf( temp, colour );
    }
    return result;
} /* query_show_list_colour() */

/**
 * This method returns a mapping which contains which Guilds
 * the playtesters belong to.
 * @return a lovely mapping
 */
mapping query_playtester_guilds() {
    mapping r = ([]);
    foreach ( string pt, string guild in guilds ) {
        if ( arrayp( r[ guild ] ) )
            r[ guild ] += ({ pt });
        else
            r[ guild ] = ({ pt });
    }
    return r;
} /* query_playtester_guilds() */

/**
 * This method returns a mapping which contains which the playtesters mapped
 * to their guild objects.
 * @return a lovely mapping
 */
mapping query_guilds() {
    return copy( guilds );
} /* query_guilds() */

/** 
 * @ignore yes
 * This small utility function is used to update the guilds...
*/ 
void update_guilds() {
  string guild;
    foreach( string player, mapping data in playtesters ) { 
        if ( !sizeof (data[ "guild" ]) ) { 
            guild = PLAYER_HANDLER->test_guild( player );
            if (!sizeof (guild)) {
              guild = "/std/guilds/standard";
            }
            
            data[ "guild" ] = guild;
        }
    }

    save_file(); 
} /* update_guilds() */ 

/**
 * Returns the raw playtester data for processing!  DEPRECATED AND WILL NOT
 * RETURN UP TO DATE INFORMATION!
 * @see query_bugs()
 * @see query_months()
 */
mixed query_pt_data( string name ) {
    if ( !undefinedp( playtesters[ name ] ) )
        return copy( playtesters[ name ] );
    return 0;
} /* query_pt_data() */

/**
 * This will return bug information for a given playtester.  The mapping
 * returned will map months to arrays of ints.  The format of these arrays is:
 * ({ bugs, online_that_month, age_at_end_of_month }).
 * @see query_months()
 */
mapping query_bugs( string name ) {
    return copy( bugs[ name ] );
} /* query_bugs() */

/**
 * This returns a sorted list of months, earliest month first.
 */
string* query_months() {
    return copy( months );
} /* query_months() */

/**
 * This marks a playtester as "on leave", logs it, with a reason.
 */
int set_leave( string name, string reason ) {
    if ( pts[ name ] ) {
        leave[ name ] = reason;
        log_file( "PLAYTESTERS", ctime( time() ) + ": " + name + " marked as "
                  "on leave: " + reason + "\n" );
        save_file();
        return 1;
    } else {
        return 0;
    }
} /* set_leave */

/**
 * This marks a playtester as no longer "on leave", and logs it.
 */
int reset_leave( string name ) {
    if ( leave[ name ] ) {
        map_delete( leave, name );
        log_file( "PLAYTESTERS", ctime( time() ) + ": " + name + " no longer "
                  "marked as on leave\n" );
        save_file();
        return 1;
    } else {
        return 0;
    }
} /* reset_leave */

/**
 * This tests whether a playtester is "on leave".
 */
int query_leave( string name ) {
    return stringp( leave[ name ] );
} /* query_leave */



/* What follows is a load of spam for converting from the old playtesters'
   handler save file to the new one.  You should never ever ever need to call
   this lot.  That's why there's a dodgy #ifdef around it.  Cool?  Cool.
   -- Arienne */

#ifdef PLAYTESTERS_HANDLER_OLD_STYLE_CONVERSION

/**
 * @ignore yes
 * This is a utility to transfer over the data from the old-type (buggy and
 * inefficient handler) to the new type.
 */
void transfer_data_callout( string* pts ) {
    string pt;
    mixed bing;
    int lastage;
    int nowage;
    
    if ( sizeof( pts ) ) {
        pt = pts[ 0 ];
        
        nowage = get_age_uncached_offline( pt );
        lastage = nowage - playtesters[ pt ][ "age" ];

        guilds[ pt ] = PLAYER_HANDLER->test_guild( pt );
        ages[ pt ] = nowage;
        bugs[ pt ] = ([ ]);
        
        foreach ( string s in keys( playtesters[ pt ] ) -
                  ({ "senior", "age", "current age", "guild" }) ) {

            bing = playtesters[ pt ][ s ];

            if ( arrayp( bing ) ) {
                bugs[ pt ][ s ] = ({ bing[ 0 ], bing[ 1 ], nowage });
            } else if ( intp( bing ) ) {
                bugs[ pt ][ s ] = ({ bing, lastage, nowage });
            }
        }

        tell_creator( "arienne", "Done " + pt + "!\n" );
        
        if ( sizeof( pts ) > 1 ) {
            call_out( "transfer_data_callout", 5, pts[ 1.. ] );
        } else {
            save_file();
            tell_creator( "arienne", "Done the transfer!\n" );
        }
    }
}

/**
 * @ignore yes
 * This is a utility to transfer over the data from the old-type (buggy and
 * inefficient handler) to the new type.
 */
void transfer_data_to_new_system() {
    foreach ( string pt in keys( playtesters ) ) {
        if ( playtesters[ pt ][ "senior" ] )
            pts[ pt ] = SENIOR_PLAYTESTER;
        else
            pts[ pt ] = NORMAL_PLAYTESTER;
    }
    call_out( "transfer_data_callout", 5, copy( keys( playtesters ) ) );
} /* transfer_data_to_new_system() */

#endif

/**
 * This method returns the current mapping of jump points.
 * @return the jump points
 */
mapping query_jump_points() {
   if (!_jump_points) {
      _jump_points = ([ ]);
   }
   return copy(_jump_points);
}

/**
 * This method adds a jump point to the list.
 * @param from the from location
 * @param to the to location
 */
void add_jump_point(string from, string to) {
   if (!_jump_points[from]) {
      _jump_points[from] = ({ });
   }
   _jump_points[from] += ({ to });
   save_file();
}

/**
 * This method removes a specific jump point from the list.
 * @param from the specific jump point to remove
 */
void remove_jump_point_from(string from) {
   map_delete(_jump_points, from);
   save_file();
}

/**
 * This method removes a specific jump point from the list.
 * @param from the from location to remove
 * @param to the to location to remove
 */
void remove_jump_point(string from, string to) {
   if (_jump_points[from]) {
      _jump_points[from] -= ({ to });
      if (!sizeof(_jump_points[from])) {
         map_delete(_jump_points, from);
      }
      save_file();
   }
}

/**
 * This method finds the destination from the start location.
 * @param from the location to find the jump point too
 */
string* query_jump_destination(string from) {
   return _jump_points[from];
}

/**
 * This method adds the player as a playtester executive.  The playtester
 * needs to already be added as a playtester before becoming a
 * playtester executive.
 * @param name the name of the player to add
 * @return 1 on success, 0 on failure
 * @see remove_pt_exec()
 * @see add_playtester()
 * @see query_pt_exec()
 */
int add_pt_exec( string name ) {
    if ( undefinedp (pts[ name ]))
        return 0;
    log_file( "PLAYTESTERS", ctime( time() ) + ": " + name +
              " promoted to playtester executive by %s\n",
              this_player()->query_name() );
    executives += ({ name });
    save_file();
    return 1;
} /* add_senior_playtester() */

/**
 * This method removes the player as a playtester executive.  
 * @param name the name of the player to add
 * @return 1 on success, 0 on failure
 * @see add_pt_exec()
 * @see add_playtester()
 * @see query_pt_exec()
 */
int remove_pt_exec( string name ) {
    if ( !undefinedp (pts[ name ]))
        return 0;
    if (member_array (name, executives) == -1) {
      return 0;
    }
    
    log_file( "PLAYTESTERS", ctime( time() ) + ": " + name +
              " removed from playtester executive by %s\n",
              this_player()->query_name() );
    executives -= ({ name });
    save_file();
    return 1;
} /* add_senior_playtester() */

int query_pt_exec(string name) {
  if (member_array (name, executives) == -1) {
    return 0;
  }
  return 1;
}

int query_exec_access (string name) {
  if (master()->query_senior(name)) {
    return 1;
  }
  if (query_pt_exec (name)) {
    return 1;
  }
  if (query_senior_playtester (name)) {
    return 1;
  }
  if (load_object ("/d/playtesters/master")->query_deputy (name)) {
    return 1;
  }  

  return 0;
}