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/
/**
 * @main
 * This is a generalised hospital inheritable for the creation of NPCs.
 * <p>
 * The NPCs within the hospital are defined in the configuration files which
 * are parsed by the hospital. The configuration files can define zones,
 * groups and npcs.
 * <p>
 * A zone is a region of the mud. It can have any name you choose and is then
 * used as the first parameter to load_random_npc(). A zone may be made up of
 * other zones, groups and npcs.
 * 
 * <p>
 * A group is a group of NPCs. It may be made up of other groups and npcs. A
 * group has a name by which it is referenced by zones and other groups.
 * <p>
 * An npc is the definition of a specific type of NPC.
 *
 * @example
 * A typical hospital setup would look like this:
 *
 * void setup() {
 *   set_save_file(SAVE + "/hospital");
 *   set_data_directory(DATA + "/hospital");
 *   set_log_directory(LOG + "/hospital");
 *   set_npc_path(CHARS + "/hospital");
 *   set_not_replaceable(1);
 *
 *   set_short("Our hospital");
 *   set_long("This is the hospital.\n");
 * }
 *
 * @example
 * These are examples of zones, groups & npcs.
 *
 * (zone
 *   (name "desert")
 *   (busy 5)
 *   (group
 *     (name "vultures")
 *     (chance 1)
 *   )
 *   (npc
 *     ("name "lizard")
 *     (chance 1)
 *   )
 * )
 *
 * (group
 *   (name "vultures")
 *   (transient "true")
 *   (npc
 *     (name "vulture")
 *     (quantity 1 + 1d5)
 *   )
 * )
 *
 * (npc
 *   (name "vulture")
 *   (path "vulture")
 *   (transient true)
 * )
 * (npc
 *   (name "lizard")
 *   (path "lizard")
 *   (transient true)
 *   (diurnal true)
 * )
 *
 * The above configuration file defines a zone "desert". The zone has a
 * business factor of 5 which means rooms in the zone will typically get a
 * chance at cloning npcs 5% of the time (every 20 rooms).
 * <p>
 * The zone is made up of a group called "vultures" and an npc called
 * "lizard".
 * <p>
 * The vultures group is transient (ie. the NPCs will be removed from the
 * room when the player leaves the room, rather like wandering monsters) and
 * contains 1 + 1d5 "vulture" npcs.
 * <p>
 * The vulture NPC is transient and can be found in the filename "vulture.c" in
 * the path defined by set_npc_path(). The lizard NPC is also transient and
 * diurnal (it only comes out during the day) and can be found in the filename
 * "lizard.c" in the path defined by set_npc_path().
 *
 * <p>
 * @author Taffyd
 * @changed Ceres November 14th 2002<br>
 * Altered to work as a hospital for terrain NPCs especially the transient
 * ones. It now handles transient npcs as well as conditions for
 * diurnal, nocturnal and seasonal npcs.
 * @changed Pinkfish March 10th<br>
 * Major rewrite of bits to make it use a lisp like data file format.
 * @changed Carmine April 9th<br>
 * Added some alignment-generation code.
 * @started December 3, 1998.
 * @index hospital
 */

#include <armoury.h>
#include <data.h>
#include <weather.h>
#include <hospital.h>

/* Comment this out if you want to disable hospital generation
   of NPC's through the new data compiler system. */
#define CREATE_NPCS 1

// #define REGENERATE_AFTER_DEATH

inherit "/std/room";
inherit HOSPITAL_ALIGNMENT_INHERIT;

public object *load_random_npc(string zone, int wealth, int busy);
protected void load_file();
protected void save_file();
protected void set_save_file(string new_save);

public void hospital_log_file(string file, string format, mixed *args ...);
public int get_item(object destination, string *items);
public string query_save_file();
public int *query_npcs();
public mapping query_hospital_npcs();
public void add_npc_type(string type, string *data);
public void regen_after_death( object dead_npc );

private nosave string _save_file;
private nosave string _hospital_type;
private nosave mixed *_regenerated_npcs;
private nosave int _hospital_call_id;
private nosave mapping _log_file_info;
private nosave string _hospital_log_directory;
private nosave string _data_directory;
private nosave string _npc_path;
private nosave string _domain;
private nosave int _max_cache;

// Used when the system is in debug mode.
private nosave int _disable_npc_generation;

private mapping _unique_npcs;
private mapping _hospital_npcs;
private mapping _cache_inventory;

private int _last_npc_check;
private int _zone_npcs;

private mapping _group_info;
private mapping _npc_info;
private mapping _zone_info;
private mapping _path_to_npc;

// Details on the files to load.
private mapping _file_modified_time;

/** @ignore yes */
void create() {
    seteuid(master()->creator_file(file_name(this_object())));

    do_setup++;
    ::create();
    do_setup--;


    if (!_group_info) {
        _group_info = ([ ]);
    }

    if (!_npc_info) {
        _npc_info = ([ ]);
    }

    if (!_zone_info) {
        _zone_info = ([ ]);
    }

    if (!_path_to_npc) {
        _path_to_npc = ([ ]);
    }

    if (!_file_modified_time) {
        _file_modified_time = ([ ]);
    }

    if (!_unique_npcs) {
        _unique_npcs = ([ ]);
    }

    if (!_cache_inventory) {
      _cache_inventory = ([ ]);
    }
    
    _regenerated_npcs = ({ });
    _log_file_info = ([ ]);

    if(!_max_cache)
      _max_cache = DEFAULT_MAX_CACHE;
    
    if (!do_setup) {
        set_short( "Un-configured Hospital" );
        set_long("This is an unconfigured hospital.  Find the right one.\n");
        add_property("determinate", "the ");
        set_light(60);
        // Setup extra stuff if they wish
        this_object()->setup();
    }
} /* create() */

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

/**
 * This function returns an array of the gods in Ankh-Morpork
 * <p>
 */
public string *query_deities() {
   return ({ "fish", "pishe", "sek", "hat", "gapp",
      "gufnork", "sandelfon" });
}

/**
 * This function provides an interface to the armoury, selecting
 * an item at random from the 'items' array and attempting to
 * clone it.
 * <p>
 * Any items of jewellery, clothing, weapons, armour or scabbards
 * that are available in the armoury can be accessed using this
 * function.
 * <p>
 * @param destination where to place the item that is fetched
 * from the armoury.
 * @param items an array of items, of which one will be selected,
 * cloned, and then moved to destination.
 * <p>
 * @see /obj/handlers/armoury
 * @return 1 if the item was successfully created, 0 if it was not.
 */
public int get_item(object destination, string *items) {
    int i;
    object item;

    if (!objectp(destination)) {
        return 0;
    }

    while (!item) {
        i = random(sizeof(items));
        item = ARMOURY->request_item(items[i], 80 + random(21), _domain);

        if (!item) {
            item = ARMOURY->request_item(items[i], 80 + random(21) );
            if ( !item ) {
                items = items[0..i-1] + items[i+1..];
                continue;
            }
        }

        if (!sizeof(items)) {
            hospital_log_file("BROKEN_ITEMS", "Unable to select any items "
                "for %s in the Ankh-Morpork hospital.\n",
                previous_object()->short());
            break;
        }
    }

    if (objectp(item)) {
        item->move(destination);
        return 1;
    }

    return 0;
} /* get_item() */

/** @ignore yes */
protected void load_file() {
    class npc_info npc;
    class group_info group;
    string name;

    if (file_size(_save_file + ".o") > -1) {
        unguarded((: restore_object, _save_file :));
        if (!_group_info) {
            _group_info = ([ ]);
        } else {
            // Zap all the population data
            foreach (name, group in _group_info) {
                group->storage = 0;
            }
        }

        if (!_npc_info) {
            _npc_info = ([ ]);
        } else {
            // Zap all the population data
            foreach (name, npc in _npc_info) {
                npc->population = ({ });
            }
        }

        if (!_zone_info) {
            _zone_info = ([ ]);
        }

        if (!_path_to_npc) {
            _path_to_npc = ([ ]);
        }

        if (!_file_modified_time) {
            _file_modified_time = ([ ]);
        }

        if (!_unique_npcs) {
            _unique_npcs = ([ ]);
        }
    }
} /* load_file() */

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

/**
 * This method is used to set the save file for the hospital.  This
 * file is where all of the hospitals NPC data is stored while it is
 * not active and in between reboots.
 * <p>
 * If this file does not exist when the hospital is loaded, then the
 * variables will be initialised to their defaults and
 * <b>restore_default_npcs()</b> called.
 * <p>
 * @param new_save the file name to save the hospital data in
 */
protected void set_save_file(string new_save) {
    _save_file = new_save;
    load_file();
} /* set_save_file() */

/**
 * This returns the location where hospital data will be stored.
 *
 * @see set_save_file()
 */
public string query_save_file() {
    return _save_file;
} /* query_save_file() */

/**
 * This method sets the directory to scan for new files. The data directory
 * stores the configuration files for the hospital that define the zones,
 * groups and npcs.
 *
 * @param dir the directory to scan for new files
 */
void set_data_directory(string dir) {
   _data_directory = dir;
   call_out("scan_for_new_data", 2);
} /* set_data_directory() */ 

/**
 * This method returns the directory to scan for new files.
 *
 * @return the directory to scan new data
 * @see set_data_directory()
 */
string query_data_directory() {
   return _data_directory;
} /* query_data_directory() */ 

/**
 * This method sets the directory for npcs.  The filename for npcs defined
 * in the configuration files is realtive to this path.
 *
 * @param dir the director for npcs.
 */
void set_npc_path(string path) {
  _npc_path = path;
} /* set_npc_path() */ 

/**
 * This method sets the domain string used when requesting things
 * from the hospital.
 *
 * @param domain the domain name
 */
void set_domain(string domain) {
  _domain = domain;
} /* set_domain() */ 

/**
 * This method sets the maximum number of transient npcs of a given type
 * that the hospital will cache. 
 *
 * @param num the maximum number
 */
void set_max_cache(int num) {
  _max_cache = num;
} /* set_max_cache() */ 

/**
 * This method sets the flag which enables or disables npc generation. This
 * is mostly used for debugging.
 *
 * @param value the new value of the flag
 */
void set_disable_npc_generation(int value) {
   _disable_npc_generation = value;
} /* set_disable_npc_generation() */ 

/**
 * This method returns the flag that disables the npc generation.
 *
 * @return the flag that disables the npc generation
 * @see set_disable_npc_generation
 */
int query_disable_npc_generation() {
   return _disable_npc_generation;
} /* query_disable_npc_generation() */ 

/**
 * This method returns whether or not it is time to regenerate a
 * unique NPC. The regeneration time is controlled by the REGEN_TIME
 * #define.
 * <p>
 * @param who this is the name or object reference of the NPC
 * @return 1 if it is time to make the unique NPC, or 0 if it is not.
 */
public int make_unique(mixed who) {
    int delay;

    if (!_unique_npcs) {
        return 0;
    }

    if (objectp(who)) {
        who = who->query_name();
    }

    if (!_unique_npcs[who]) {
        _unique_npcs[who] = new(class unique_npc);
    }

    if ( _unique_npcs[who]->next_regen_time > time() ) {
        return 0;
    }

    if (_npc_info[who] && _npc_info[who]->delay) {
        delay = _npc_info[who]->delay;
    } else {
        delay = REGEN_TIME;
    }
    _unique_npcs[who]->next_regen_time = time() + delay;
    save_file();
    return 1;
} /* make_unique() */

/**
 * This method resets the unique status of the npc so it can be tested for
 * loading over again.
 * @param npc the name of the npc
 */
public void reset_unique(string who) {
    if (!_unique_npcs[who]) {
        return ;
    }
    _unique_npcs[who]->next_regen_time = 0;
}

#ifdef REGENERATE_AFTER_DEATH
/**
 * This method is called by the npc.death effect after an NPC that is handled
 * by the hospital has died. It tells the room where it was generated to
 * make a new NPC and to all sorts of funky things.
 * <p>
 * This is not fully implemented yet.
 */
public void regen_after_death( object dead_npc ) {
    object *monsters, destination;
    class regenerated_npc new_npc;
    int i, max;

    if ( !dead_npc )
        return;

    _regenerated_npcs += ({ new( class regenerated_npc,
        type : dead_npc->query_property("monster_type"),
        load_position : previous_object()->query_property("start location") ) });

    if ( !ok_to_clone(_hospital_type) || sizeof(_regenerated_npcs) < MINIMUM_DEATH_REGEN )
        return;

    new_npc = _regenerated_npcs[0];

    if ( !classp( new_npc ) )
        return;

    _regenerated_npcs = _regenerated_npcs[1..];

    if (!new_npc->load_position || !new_npc->type );
        return;

    max = random(5);

    for ( i = 0; i < max; i++ ){
        destination = find_object( new_npc->load_position );

        if ( !objectp( destination ) )
            return;

        monsters = get_monster(new_npc->type);
        destination->replace_monster(previous_object(), monsters);

        call_out((: $1->announce_entry($2) :), 8 + random(6), destination,
            monsters);
    }
} /* regen_after_death() */
#endif

/**
 * This method expands the path for the given npc by combining the npc_path
 * set in set_npc_path with the relative path defined in the configuration
 * file.
 *
 * @param path the path to expand
 * @return the expanded path
 */
string npc_path(string str) {
   return _npc_path + "/" + str;
} /* npc_path() */ 

/**
 * This method provides log_file() functionality but for /d/am/log
 * instead.  You should use this method to record errors and whatever
 * that are domain specific instead of using log_file().
 * @example
 * hospital_log_file("BAD_HOSPITAL_NPC", "%s: Bad NPC hospital data, %O.\n",
 * ctime(time()), _regenerated_npcs);
 * // This would log to /d/am/log/BAD_HOSPITAL_NPC.
 * @param file the file name to log to. This will have /d/am/log/
 * inserted before it.
 * @param format the text to log, can contain sprintf() format specifiers.
 * @param args the arguments for any sprintf format thingies
 */
public void hospital_log_file(string file, string format, mixed *args ...) {
    string filename;

    filename = _hospital_log_directory;

    /* We are restricted to logging in our domain, let's be neat about it. */

    if (file[0] == '/') {
        file = file[ strsrch( file, "/", -1 ) + 1 .. ];
    }

    /* Link to sefun */

    log_file( _hospital_log_directory + "/" + file, ctime(time()) + ": " + format, args ...);
} /* hospital_log_file() */

/**
 * This method sets the directory in to which hospital_log_file will
 * log by default. If the directory does not exist, then it is set to
 * DEFAULT_LOG_DIRECTORY.
 * @param new_dir the directory to log to.
 * @example
 * set_log_directory( "/d/cwc/Bes_Pelargic/log" );
 * // log directory is now /d/cwc/Bes_Pelargic/log
 * @example
 * // (cre) MacChirton: ithinkmykeyboardisbroken
 * set_log_directory( "/d/cWc/BeS_pelarGic/lrg" );
 * // Displays: "/d/cwc/hospital/bes_pelargic: Invalid log
 * // directory, defaulting to /log"
 */
protected void set_log_directory(string new_dir) {

    if (file_size(new_dir) != -2) {
        debug_printf( "%s: Invalid "
            "log directory -- defaulting to %s.\n",
            file_name( this_object() ), DEFAULT_LOG_DIRECTORY );

        new_dir = DEFAULT_LOG_DIRECTORY;
    }

    _hospital_log_directory = new_dir;
} /* set_log_directory() */

/** @ignore yes */
private void parse_zone(string fname, mapping data) {
   string name;
   class zone_info info;
   mapping bing;
   int chance;
   string* bits;

   if (!data["name"]) {
      debug_printf("No name for zone %O\n", data);
      hospital_log_file("COMPILE_ERROR", "No name for zone %O\n", data);
      return 0;
   }

   name = data["name"];
   if (data["group"] && !arrayp(data["group"])) {
      data["group"] = ({ data["group"] });
   } else if (!arrayp(data["groups"])) {
      data["group"] = ({ });
   }
   if (data["npc"] && !arrayp(data["npc"])) {
      data["npc"] = ({ data["npc"] });
   } else if (!arrayp(data["npc"])) {
      data["npc"] = ({ });
   }
   if (data["zone"] && !arrayp(data["zone"])) {
      data["zone"] = ({ data["zone"] });
   } else if (!arrayp(data["zone"])) {
      data["zone"] = ({ });
   }

   info = new(class zone_info, npcs : ([ ]), groups : ([ ]), zones : ([ ]));
   chance = 0;
   foreach (bing in data["npc"]) {
      if (!mapp(bing) || !stringp(bing["name"]) || !intp(bing["chance"])) {
         debug_printf("Invalid data for zone (%s) %O\n", name, bing);
         hospital_log_file("COMPILE_ERROR", "Invalid data for zone (%s) %O\n", name,  bing);
      } else {
         info->npcs[bing["name"]] = bing["chance"];
         chance += bing["chance"];
      }
   }
   info->npc_chance = chance;

   chance = 0;
   foreach (bing in data["group"]) {
      if (!mapp(bing) || !stringp(bing["name"]) || !intp(bing["chance"])) {
         debug_printf("Invalid data for zone (%s) %O\n", name, bing);
         hospital_log_file("COMPILE_ERROR", "Invalid data for zone (%s) %O\n", name,  bing);
      } else {
         info->groups[bing["name"]] = bing["chance"];
         chance += bing["chance"];
      }
   }
   info->group_chance = chance;

   chance = 0;
   foreach (bing in data["zone"]) {
      if (!mapp(bing) || !stringp(bing["name"]) || !intp(bing["chance"])) {
         debug_printf("Invalid data for zone (%s) %O\n", name, bing);
         hospital_log_file("COMPILE_ERROR", "Invalid data for zone (%s) %O\n", name,  bing);
      } else {
         info->zones[bing["name"]] = bing["chance"];
         chance += bing["chance"];
      }
   }
   info->zone_chance = chance;
   info->busy = data["busy"];
   
   bits = keys(data) - ({ "zone", "group", "npc", "name", "busy" });
   if (sizeof(bits)) {
      debug_printf("Unknown keys %s in zone %s\n", query_multiple_short(bits),
                   name);
      hospital_log_file("COMPILE_ERROR", "Unknown keys %s in %s\n",
                         query_multiple_short(bits), name);
   }
   _zone_info[name] = info;
   save_file();
} /* parse_zone() */

private void parse_npc(string fname, mapping data) {
   string name;
   class npc_info info;
   string* bits;

   if (!data["name"]) {
      debug_printf("No name for npc %O\n", data);
      hospital_log_file("COMPILE_ERROR", "No name for npc %O\n", data);
      return 0;
   }
   name = data["name"];
   if (!data["path"]) {
      hospital_log_file("COMPILE_ERROR", "Required path field not present for npc %s\n", name);
      return 0;
   }

   if (file_size(npc_path(data["path"]) + ".c") < 0) {
      hospital_log_file("BAD_NPC_PATH", "Bad path for npc %s (%s)\n", name, data["path"]);
   }

   if (data["command"] && !arrayp(data["command"])) {
      data["command"] = ({ data["command"] });
   }
   if (data["move_zone"] && !arrayp(data["move_zone"])) {
      data["move_zone"] = ({ data["move_zone"] });
   }

   info = new(class npc_info, move_zones : ({ }), commands : ({ }));
   info->unique = data["unique"];
   info->path = data["path"];
   info->delay = data["delay"];
   info->max_population = data["population"];
   
   if(data["transient"])
     info->transient = 1;
   if(data["nocturnal"])
     info->nocturnal = 1;
   if(data["diurnal"])
     info->diurnal = 1;
   if(data["seasonal"]) {
     if(!arrayp(data["group"]))
       info->seasonal = ({ data["seasonal"] });
     else
       info->seasonal = data["seasonal"];
   }

   if (data["move_zone"]) {
      info->move_zones = data["move_zone"];
   }
   if (data["command"]) {
      info->commands = data["commands"];
   }

   bits = keys(data) - ({ "unique", "path", "move_zone", "population", "name", "delay", "transient", "nocturnal" });
   if (sizeof(bits)) {
      debug_printf("Unknown keys %s in npc %s\n", query_multiple_short(bits),
                   name);
      hospital_log_file("COMPILE_ERROR", "Unknown keys %s in npc %s\n",
                         query_multiple_short(bits), name);
   }
   if (_path_to_npc[info->path] && _path_to_npc[info->path] != name) {
      hospital_log_file("NPC_NAME_CLASH", "NPC path %s is used by %s and %s.\n",
                         info->path, name, _path_to_npc[info->path]);
   }
   _path_to_npc[info->path] = name;
   if (_npc_info[name]) {
      info->population = _npc_info[name]->population;
      info->no_created = _npc_info[name]->no_created;
      info->no_reused = _npc_info[name]->no_reused;
      info->no_deaths = _npc_info[name]->no_deaths;
   }
   _npc_info[name] = info;
   save_file();
} /* parse_npc() */

/** @ignore yes */
private void parse_group(string fname, mapping data) {
   string name;
   class group_info info;
   mapping bing;
   int chance;
   string* bits;

   if (!data["name"]) {
      debug_printf("No name for group %O\n", data);
      hospital_log_file("COMPILE_ERROR", "No name for group %O\n", data);
      return 0;
   }

   name = data["name"];

   if (data["npc"] && !arrayp(data["npc"])) {
      data["npc"] = ({ data["npc"] });
   } else if (!arrayp(data["npc"])) {
      data["npc"] = ({ });
   }

   info = new(class group_info, npcs : ([ ]), move_zones : ({ }),
                                npc_commands : ([ ]));
   chance = 0;
   foreach (bing in data["npc"]) {
      if (!mapp(bing) || !stringp(bing["name"]) ||
          (!intp(bing["quantity"]) && !classp(bing["quantity"]))) {
         debug_printf("Invalid data for group (%s) %O\n", name, bing);
         hospital_log_file("COMPILE_ERROR", "Invalid data for group (%s) %O\n", name,  bing);
      } else {
         info->npcs[bing["name"]] = bing["quantity"];
         if (bing["command"]) {
            if (arrayp(bing["command"])) {
               info->npc_commands[bing["name"]] = bing["command"];
            } else {
               info->npc_commands[bing["name"]] = ({ bing["command"] });
            }
         }
      }
   }
   if (data["move_zone"]) {
      info->move_zones = data["move_zone"];
   }
   info->max_population = data["population"];
   info->protect = data["protect"];
   info->defend = data["defend"];
   info->transient = data["transient"];
   
   bits = keys(data) - ({ "npc", "name", "population", "move_zone", "protect", "defend", "transient", });
   if (sizeof(bits)) {
      debug_printf("Unknown keys %s in group %s\n", query_multiple_short(bits),
                   name);
      hospital_log_file("COMPILE_ERROR", "Unknown keys %s in group %s\n",
                         query_multiple_short(bits), name);
   }
   if (_group_info[name]) {
     info->storage = _group_info[name]->storage;
   }
   _group_info[name] = info;
} /* parse_group() */

/**
 * This is called by the data compiler when the system has
 * finished compiling the data file.
 * @param data the data to deal with
 */
protected void finish_compiling(string fname, mapping data) {
   mixed* bits;
   mapping thing;
   string name;

   foreach (name, bits in data) {
      switch (name) {
      case "zone" :
         foreach (thing in bits) {
            parse_zone(fname, thing);
         }
         break;
      case "npc" :
         foreach (thing in bits) {
            parse_npc(fname, thing);
         }
         break;
      case "group" :
         foreach (thing in bits) {
            parse_group(fname, thing);
         }
         break;
      default :
         debug_printf("Unknown data area %s in %s\n", name, fname);
         hospital_log_file("COMPILE_ERROR", "Unknown data area %s in %s\n", name, fname);
         break;
      }
   }

   _file_modified_time[fname] = unguarded( (: stat($(fname)) :))[1];
   save_file();
}

/**
 * This method scans through all the files and sees if anything has
 * changed.
 */
int scan_for_new_data(int force) {
  string *files;
  string fname;
  string *bits;
  
  debug_printf("Scan for new data.");
  if (unguarded( (: stat(__FILE__)[1] :)) !=
      _file_modified_time[__FILE__]) {
    _file_modified_time = ([ ]);
  }
  
  if(force) {
    _npc_info = ([ ]);
    _zone_info = ([ ]);
    _group_info = ([ ]);
  }
  
  files = ({ _data_directory });
  while (sizeof(files)) {
    fname = files[0];
    files = files[1..];
    if (unguarded( (: file_size($(fname)) :)) == -2) {
      // Directory!
      bits = get_dir(fname + "/");
      if (bits) {
        bits -= ({ "RCS", ".", ".." });
        bits = filter(bits, (: $1[0] != '.' :));
        files += map(bits, (: $2 + "/" + $1 :), fname);
      }
    } else {
      if(unguarded((: stat($(fname)) :))[1] != _file_modified_time[fname] ||
         force) {
        debug_printf("Compiling %s", fname);
        DATA_HANDLER->compile_file(fname, (: finish_compiling :));
      }
    }
  }
  _file_modified_time[__FILE__] = unguarded( (: stat(__FILE__)[1] :));
  return 1;
} /* scan_for_new_data() */

/**
 * This method rolls the dice, if a dice is specified.
 * @param dice the dice or number
 * @return the actual number
 */
int roll_dice(mixed die) {
   int result;
   int i;

   if (intp(die)) {
      return die;
   }

   if (classp(die)) {
      for (i = 0; i < die->number; i++) {
         result += random(die->die) + 1;
      }
      result += die->modifier;
      return result;
   }

   return 0;
} /* roll_dice() */ 

/**
 * This method returns the maximum population of this npc.
 * @param npc the name of the npc
 * @return the npc population
 */
int query_npc_max_population( string npc) {
   if (!_npc_info[npc]) {
      return -1;
   }

   return ((class npc_info)_npc_info[npc])->max_population;
} /* query_npc_max_population() */

/**
 * This method returns the current population of this npc
 * @param npc the name of the npc
 * @return the npc population
 */
int query_npc_current_population( string npc, int clean) {
   class npc_info data;

   if (!_npc_info[npc]) {
      return -1;
   }
   data = _npc_info[npc];

   if (!data->population) {
      data->population = children(npc_path(data->path));
      data->next_update = time() + POPULATION_UPDATE_TIME;
   }

   if (clean || data->next_update < time()) {
      data->population = filter(data->population, (: objectp($1) :));
      data->next_update = time() + POPULATION_UPDATE_TIME;
   }

   return sizeof(data->population);
} /* query_npc_current_population() */

/**
 * THis method checks to see if the number of npcs of this
 * type is under the maximum population.
 * @param npc the npc to check to see if it is under population max
 * @return 1 if is under, 0 if is not
 */
int is_npc_under_max_population(string name) {
    class npc_info info;

    info = _npc_info[name];
    if (!info) {
       return 0;
    }

    // There are no population limits for transient NPCs since they
    // are (theoretically) destroyed as soon as the players leave their
    // room.
    if(info->transient)
      return 1;

    if (info->max_population) {
       return query_npc_current_population(name, 1) < info->max_population;
    }
    return 1;
} /* is_npc_under_max_population() */

/**
 * This method checks to see if the number of npcs of this
 * type is under the maximum population.
 * @param npc the npc to check to see if it is under population max
 * @return 1 if is under, 0 if is not
 */
int is_group_under_max_population(string name) {
    class group_info group;

    group = _group_info[name];
    if (!group) {
       return 0;
    }

    if (!group->max_population || group->transient) {
       return 1;
    }

    if (!group->storage) {
        group->storage = ({ });
    } else if (group->next_update < time()) {
        group->storage = map(group->storage, (: filter($1, (: objectp($1) :)) :));
        group->storage = filter(group->storage, (: sizeof($1) :));
        group->next_update = time() + POPULATION_UPDATE_TIME;
    }
    /*
     * If we have more NPC's than the population, then stop right now.
     */
    if (sizeof(group->storage) >= group->max_population) {
        return 0;
    }
    return 1;
} /* is_group_under_max_population() */

/**
 * This method loads the specific npc.  It will do everything it needs
 * to create the npc.  In the case of unique npcs they are not cloned.
 * @param npc_name the name of the npc to load
 * @return the npc object
 */
public object load_npc_object(string npc_name) {
    class npc_info info;
    object npc;
    string zone;
    string cmd;

    if ( undefinedp(_npc_info[npc_name])) {
        debug_printf("Unable to find npc %O", npc_name);
        return 0;
    }

    info = _npc_info[npc_name];

    // Validity checks for given NPCs.
    if(info->diurnal && !WEATHER->query_day(previous_object()))
      return 0;
    if(info->nocturnal && !WEATHER->query_day(previous_object()))
      return 0;
    if(info->seasonal &&
       member_array(WEATHER->query_season(), info->seasonal) == -1)
      return 0;
    
    if (stringp(info->path)) {
        if ( info->unique ) {
            npc = find_object( npc_path(info->path) );
            if ((!npc || !environment(npc)) && make_unique(npc_name)) {
               debug_printf("Unique npc %O", info);
               if (catch(npc = load_object( npc_path(info->path) ))) {
                   hospital_log_file("BAD_NPC_PATH", "Error loading %s (%s)\n", npc_name, info->path);
                   return 0;
               }
               if (!objectp(npc) || npc == 0) {
                   hospital_log_file("BAD_NPC_PATH", "Failed to load %s (%s)\n", npc_name, info->path);
                   return 0;
               }

               /* If the NPC has an environment, then we have to
                  bail because the NPC is already loaded somewhere. */

               if ( environment( npc ) ) {
                   debug_printf("NPC unique and already exists %O", npc_name);
                   return 0;
               }
            }
        } else if(info->transient) {
          if(!_cache_inventory)
            _cache_inventory = ([ ]);
          else if(_cache_inventory[npc_name])
            _cache_inventory[npc_name] -= ({ 0 });
          
          if(sizeof(_cache_inventory[npc_name])) {
            npc = _cache_inventory[npc_name][0];
            _cache_inventory[npc_name] -= ({ npc });
            info->no_reused++;
          } else {
            npc = clone_object( npc_path(info->path) );
            if(npc) {
              npc->add_property("transient", 1);
              npc->add_property("npc_id", npc_name);
              npc->add_property("hospital", base_name(this_object()));
              info->no_created++;
            } else
              debug_printf("Failed to clone %s", npc_path(info->path));
          }
        } else {
            if (is_npc_under_max_population(npc_name)) {
               npc = clone_object( npc_path(info->path) );
               info->no_created++;
               if (!npc) {
                  debug_printf("Npc path does not exist %O (%O)", npc, npc_path(info->path));
               }
            } else {
               debug_printf("NPC over max population %O", npc_name);
            }
        }
    } else {
        debug_printf("Broken npc data %O", info->path);
        return 0;
    }

    if (npc && !info->transient) {
        if( info->population == 0 )
            info->population = ({ });
        info->population += ({ npc });
        npc->add_property("npc_id", npc_name);
        foreach (zone in info->move_zones) {
            npc->add_move_zone(zone);
        }
        foreach (cmd in info->commands) {
            npc->init_command(cmd, 2);
        }
    }

    return npc;
} /* load_npc_object() */

/**
 * This method creates the npcs for the specificed group of npcs.
 * @param group_name the name of the group
 * @return the npcs
 */
public object *load_group_npcs(string group_name) {
    class group_info group;
    int quantity;
    int i;
    object *npcs;
    string npc_name;
    object ob;
    object ob_bing;
    string cmd;

    if ( undefinedp(_group_info[group_name]) ||
         !is_group_under_max_population(group_name)) {
        return 0;
    }

    group = _group_info[group_name];

    /* Determine the population from the class, if this is undefined
     * then we make set it to DEFAULT_POPULATION.
     */

    npcs = ({ });
    foreach (npc_name, quantity in group->npcs) {
        quantity = roll_dice(quantity);
        for (i = 0; i < quantity; i++) {
            // If we have a problem loading one.  Dest them all.
            ob = load_npc_object(npc_name);
            if (!ob) {
               npcs->move("/room/rubbish");
               return ({ });
            }
            npcs += ({ ob });
            if (group->npc_commands[npc_name]) {
               foreach (cmd in group->npc_commands[npc_name]) {
                  ob->init_command(cmd, 2);
               }
            }
        }
    }

    if (!group->storage) {
        group->storage = ({ });
    }

    group->storage += ({ npcs });

    npcs->add_property("group_id", group_name);
    // Do any extra stuff on the npcs.
    foreach (ob in npcs) {
       foreach (ob_bing in npcs) {
          if (ob != ob_bing) {
             ob->add_follower(ob_bing);
             if (group->defend) {
               ob->add_defender(ob_bing);
             }
             if (group->protect) {
                ob->add_protector(ob_bing);
             }
          }
       }
    }

    return npcs;
} /* load_group_npcs() */

/**
 * This returns the npc info class for the npc in question.  THis should
 * only be used for debugging.
 * @param npc the name of the npc to get the info on
 * @return the class of the npc
 */
public class npc_info query_npc_info( string npc ) {
    if ( !_npc_info ) {
        return 0;
    }

    return _npc_info[ npc ];
} /* get_npc_info() */

/**
 * This returns all the npcs in the debug data array.
 * @return this returns the debug information for the npcs
 */
mapping query_debug_npc_info() {
   return _npc_info;
}

/**
 * This returns the group info class for the group in question.  THis should
 * only be used for debugging.
 * @param group the name of the group to get the info on
 * @return the class of the group
 */
public class group_info query_group_info( string group ) {
    if ( !_group_info ) {
        return 0;
    }

    return _group_info[ group ];
} /* query_group_info() */

/**
 * This method is called when a specific npc dies so we can do some
 * tracking on it.
 * @param npc_ob the npc object
 */
void npc_died(object ob) {
   string npc_id;
   class npc_info info;
   int i;
   mixed* stuff;

   npc_id = ob->query_property("npc_id");
   info = _npc_info[npc_id];
   if (!info || !npc_id) {
      return ;
   }

   info->population -= ({ ob });
   info->no_deaths++;

   // Find the npc in thr group if it is set.
   npc_id = ob->query_property("group_id");
   stuff = _group_info[npc_id]->storage;
   if (sizeof(stuff)) {
      for (i = 0; i < sizeof(stuff); i++) {
         stuff[i] -= ({ ob, 0 });
      }
      _group_info[npc_id]->storage = filter(_group_info[npc_id]->storage,
                                             (: sizeof($1) :));
   }
} /* npc_died() */

/** @ignore yes */
mapping query_all_npc_info() {
   return _npc_info;
} /* query_all_npc_info() */ 

/** @ignore yes */
mapping query_all_group_info() {
   return _group_info;
} /* query_all_group_info() */ 

/** @ignore yes */
mapping query_all_zone_info() {
   return _zone_info;
} /* query_all_zone_info() */ 

private int count_chances_in_zone(string zone) {
    class zone_info info;
    int chance;

    info = _zone_info[zone];
    if (!zone) {
       return 0;
    }

    chance = info->npc_chance + info->group_chance + info->zone_chance;
    return chance;
} /* count_chance_in_zone() */

private object* create_npcs_in_zone(string zone, int pos) {
    class zone_info info;
    string extra;
    string npc;
    object ob;
    object* obs;
    int chance;

    info = _zone_info[zone];
    if (!zone) {
       return ({ });
    }

    if (pos < info->npc_chance) {
       foreach (npc, chance in info->npcs) {
          if (pos < chance) {
             ob = load_npc_object(npc);
             if (ob) {
               if(environment(ob) == this_object())
                 debug_printf("Reused %s in %s", npc, zone);
               else
                 debug_printf("Created %s in %s", npc, zone);
                return ({ ob });
             }
          }
          pos -= chance;
       }
    }
    pos -= info->npc_chance;
    if (pos < info->group_chance) {
       foreach (npc, chance in info->groups) {
          if (pos < chance) {
             obs = load_group_npcs(npc);
             if (sizeof(obs)) {
                return obs;
             }
          }
          pos -= chance;
       }
    }
    pos -= info->group_chance;
    foreach (extra, chance in info->zones) {
        if (pos < chance) {
           return load_random_npc(extra, -1, -1);
        }
    }
    return ({ });
} /* create_npcs_in_zone() */

/**
 * This method randomly selects an NPC or a group of NPC's from
 * the list of defined NPC's for the room/region.
 *
 * @param zone This is a zone (as defined in the configuration files).
 * @param wealth How wealthy this particular part of this zone is.
 * @param busy How busy this particular part of this zone is.
 * 
 * @return Returns a group of NPCs randomly selected.
 */
public object *load_random_npc(string zone, int wealth, int busy) {
    int chance;
    int pos;

    if (_disable_npc_generation) {
        return ({ });
    }

    //
    // Try and see if we can find a specific zone for the
    // busyness/wealth of this zone
    //
    if (_zone_info[zone + "-w" + wealth + "-b" + busy]) {
       zone = zone + "-w" + wealth + "-b" + busy;
    } else if (_zone_info[zone + "-w" + wealth]) {
       zone = zone + "-w" + wealth;
    } else if (_zone_info[zone + "-b" + busy]) {
       zone = zone + "-b" + busy;
    }

    if (!_zone_info[zone]) {
        return ({ });
    }

    if(_zone_info[zone]->busy &&
       _zone_info[zone]->busy < random(random(100)))
      return ({ });

    chance = count_chances_in_zone(zone);
    if (!chance) {
       return ({ });
    }
    pos = random(chance);
    return create_npcs_in_zone(zone, pos);
} /* load_random_npc() */

/**
 * Prints a report of all the npcs in the hospital currently.
 */
int do_report() {
    string name;
    class group_info group;
    class npc_info npc;
    string txt;
    int total;
    int max;
    int pop;

    txt = "";
    total = 0;

    txt += "%^WHITE%^NPCs%^RESET%^\n";
    foreach( name, npc in _npc_info ) {
      // Otherwise we can't get any real stats. :(
      reset_eval_cost();
      if(stringp(npc->path) && file_size(npc_path(npc->path) + ".c") > 0) {
        pop = query_npc_current_population(name, 0);
      } else {
        pop = 0;
      }
      if(npc->transient)
        txt += sprintf( "$I$5=Name: %s. (%s) (Transient) Reused %d Created %d Died %d\n", name + "",
                        (stringp(npc->path)?npc->path:"Broken!"),
                        npc->no_reused, npc->no_created, npc->no_deaths);
      else
        txt += sprintf( "$I$5=Name: %s. (%s) Population: (%d/%d) Created %d Died %d\n", name + "",
                        (stringp(npc->path)?npc->path:"Broken!"),
            pop, npc->max_population, npc->no_created, npc->no_deaths );
        total += pop;
        max += npc->max_population;
    }

    txt += "$I$0=%^WHITE%^Groups%^RESET%^\n";
    foreach( name, group in _group_info ) {
      if(group->transient)
        txt += sprintf( "$I$5=Name: %s. (%s) (Transient)\n", name,
            query_multiple_short(keys(group->npcs)));
      else
        txt += sprintf( "$I$5=Name: %s. (%s) Population: (%d/%d)\n", name,
            query_multiple_short(keys(group->npcs)),
            sizeof( group->storage ), group->max_population );
    }

    txt += "$I$0=%^WHITE%^Cache%^RESET%^\n";
    foreach(name in keys(_cache_inventory))
      _cache_inventory[name] -= ({ 0 });
      
    foreach(name, group in _cache_inventory)
      txt += sprintf("$I$5=Name: %s %d\n", name, sizeof(group));

    txt += "\nTotal NPCs in hospital: " + total + "/" +
            max + ".\n";
    write("$P$Report$P$" + txt );

    return 1;

} /* create_hospital_report() */

/** @ignore yes */
void init() {
  object ob;
  ob = this_player();

  add_command("report", "", (: do_report :));
  add_command("scan", "", (: scan_for_new_data :));
  
  // Add returning npcs to our cache.
  if(ob->query_property("npc_id") && ob->query_property("hospital") &&
     ob->query_property("hospital") == base_name(this_object())) {
    if(!_cache_inventory[ob->query_property("npc_id")])
      _cache_inventory[ob->query_property("npc_id")] = ({ ob });
    else if(sizeof(_cache_inventory[ob->query_property("npc_id")]) >
            _max_cache)
      ob->move("/room/rubbish");
    else
      _cache_inventory[ob->query_property("npc_id")] += ({ ob });
  } else if(base_name(ob) == "/obj/corpse")
    ob->move("/room/rubbish");

} /* init() */

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

/** @ignore yes */
mapping query_dynamic_auto_load() {
    string group_name;
    string npc;
    class group_info data;
    class npc_info npc_data;
    mapping tmp;
    mapping tmp2;

    tmp = allocate_mapping( sizeof( _group_info ) );

    foreach( group_name, data in _group_info ) {
        tmp[ group_name ] = data->storage;
    }

    tmp2 = allocate_mapping( sizeof( _npc_info) );
    foreach( npc, npc_data in _npc_info ) {
        tmp2[ npc ] = npc_data->population;
    }

    return ([ file_name( this_object() ) : tmp, "npcs" : tmp2 ]);

} /* query_dynamic_auto_load() */

/** @ignore yes */
void init_dynamic_arg( mapping tmp ) {
    string group_name;
    object *npcs;

    if ( tmp[ file_name( this_object() ) ] ) {
        foreach( group_name, npcs in tmp[ file_name( this_object() ) ] ) {
            if (_group_info[group_name]) {
               _group_info[ group_name ]->storage = npcs;
            }
        }
        foreach( group_name, npcs in tmp[ "npcs" ]) {
            if (_npc_info[group_name]) {
               _npc_info[ group_name ]->population = npcs;
            }
        }
    }
} /* init_dynamic_arg() */

/** @ignore yes
 * This is a debugging function. Beware, if you have a large hospital this
 * may lockup/crash the mud!
 */
mixed query_all_npcs() {
    return values( map( _group_info, (: $2->storage :) ) );
}
/** @ignore yes
 * This is a debugging function.
 */
mapping query_inventory_cache() {
  return copy(_cache_inventory);
}

/**
 * This function returns the ids of all the NPCs which are registered with the 
 * hospital, ie the ones that have to be specified with add_hospital_npc().
 *
 * @return All the ids.
 */
string* query_registered_npcs() {
    return keys( _npc_info );
} /* string* query_registered_npcs() */