skylib_fluffos_v3/
skylib_fluffos_v3/bin/
skylib_fluffos_v3/bin/db/
skylib_fluffos_v3/fluffos-2.9-ds2.04/
skylib_fluffos_v3/fluffos-2.9-ds2.04/ChangeLog.old/
skylib_fluffos_v3/fluffos-2.9-ds2.04/Win32/
skylib_fluffos_v3/fluffos-2.9-ds2.04/compat/
skylib_fluffos_v3/fluffos-2.9-ds2.04/compat/simuls/
skylib_fluffos_v3/fluffos-2.9-ds2.04/include/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/clone/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/command/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/data/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/etc/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/include/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/inherit/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/inherit/master/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/log/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/single/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/single/tests/compiler/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/single/tests/efuns/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/single/tests/operators/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/u/
skylib_fluffos_v3/fluffos-2.9-ds2.04/tmp/
skylib_fluffos_v3/fluffos-2.9-ds2.04/windows/
skylib_fluffos_v3/mudlib/
skylib_fluffos_v3/mudlib/cmds/
skylib_fluffos_v3/mudlib/cmds/admin/
skylib_fluffos_v3/mudlib/cmds/guild-race/
skylib_fluffos_v3/mudlib/cmds/living/broken/
skylib_fluffos_v3/mudlib/cmds/player/group_cmds/
skylib_fluffos_v3/mudlib/cmds/playtester/
skylib_fluffos_v3/mudlib/d/admin/
skylib_fluffos_v3/mudlib/d/admin/room/
skylib_fluffos_v3/mudlib/d/admin/room/we_care/
skylib_fluffos_v3/mudlib/d/admin/save/
skylib_fluffos_v3/mudlib/d/admin/text/
skylib_fluffos_v3/mudlib/d/learning/TinyTown/buildings/
skylib_fluffos_v3/mudlib/d/learning/TinyTown/map/
skylib_fluffos_v3/mudlib/d/learning/TinyTown/roads/
skylib_fluffos_v3/mudlib/d/learning/chars/
skylib_fluffos_v3/mudlib/d/learning/functions/
skylib_fluffos_v3/mudlib/d/learning/handlers/
skylib_fluffos_v3/mudlib/d/learning/help_topics/
skylib_fluffos_v3/mudlib/d/learning/help_topics/npcs/
skylib_fluffos_v3/mudlib/d/learning/help_topics/objects/
skylib_fluffos_v3/mudlib/d/learning/help_topics/rcs_demo/
skylib_fluffos_v3/mudlib/d/learning/help_topics/rcs_demo/RCS/
skylib_fluffos_v3/mudlib/d/learning/help_topics/rooms/
skylib_fluffos_v3/mudlib/d/learning/help_topics/rooms/crowd/
skylib_fluffos_v3/mudlib/d/learning/help_topics/rooms/situations/
skylib_fluffos_v3/mudlib/d/learning/save/
skylib_fluffos_v3/mudlib/d/learning/school/
skylib_fluffos_v3/mudlib/d/learning/school/add_sc/
skylib_fluffos_v3/mudlib/d/learning/school/characters/
skylib_fluffos_v3/mudlib/d/learning/school/general/
skylib_fluffos_v3/mudlib/d/learning/school/getting-started/
skylib_fluffos_v3/mudlib/d/learning/school/getting-started/basic_commands/
skylib_fluffos_v3/mudlib/d/learning/school/getting-started/edtutor/
skylib_fluffos_v3/mudlib/d/learning/school/getting-started/unix_tutor/
skylib_fluffos_v3/mudlib/d/learning/school/items/
skylib_fluffos_v3/mudlib/d/learning/school/npc_school/
skylib_fluffos_v3/mudlib/d/learning/school/room_school/
skylib_fluffos_v3/mudlib/d/learning/school/room_school/room_basic/
skylib_fluffos_v3/mudlib/d/learning/school/room_school/situations/
skylib_fluffos_v3/mudlib/d/learning/school/room_school/terrain_tutor/
skylib_fluffos_v3/mudlib/d/learning/text/
skylib_fluffos_v3/mudlib/d/liaison/
skylib_fluffos_v3/mudlib/d/mudlib/
skylib_fluffos_v3/mudlib/d/mudlib/changes/
skylib_fluffos_v3/mudlib/d/playtesters/
skylib_fluffos_v3/mudlib/d/playtesters/effects/
skylib_fluffos_v3/mudlib/d/playtesters/handlers/
skylib_fluffos_v3/mudlib/d/playtesters/items/
skylib_fluffos_v3/mudlib/d/sage/
skylib_fluffos_v3/mudlib/doc/
skylib_fluffos_v3/mudlib/doc/creator/
skylib_fluffos_v3/mudlib/doc/driver/
skylib_fluffos_v3/mudlib/doc/driver/efuns/arrays/
skylib_fluffos_v3/mudlib/doc/driver/efuns/buffers/
skylib_fluffos_v3/mudlib/doc/driver/efuns/calls/
skylib_fluffos_v3/mudlib/doc/driver/efuns/compile/
skylib_fluffos_v3/mudlib/doc/driver/efuns/filesystem/
skylib_fluffos_v3/mudlib/doc/driver/efuns/floats/
skylib_fluffos_v3/mudlib/doc/driver/efuns/functions/
skylib_fluffos_v3/mudlib/doc/driver/efuns/general/
skylib_fluffos_v3/mudlib/doc/driver/efuns/mappings/
skylib_fluffos_v3/mudlib/doc/driver/efuns/mixed/
skylib_fluffos_v3/mudlib/doc/driver/efuns/mudlib/
skylib_fluffos_v3/mudlib/doc/driver/efuns/numbers/
skylib_fluffos_v3/mudlib/doc/driver/efuns/parsing/
skylib_fluffos_v3/mudlib/doc/login/
skylib_fluffos_v3/mudlib/doc/lpc/basic_manual/
skylib_fluffos_v3/mudlib/doc/lpc/intermediate/
skylib_fluffos_v3/mudlib/doc/new/add_command/
skylib_fluffos_v3/mudlib/doc/new/events/
skylib_fluffos_v3/mudlib/doc/new/handlers/
skylib_fluffos_v3/mudlib/doc/new/living/race/
skylib_fluffos_v3/mudlib/doc/new/living/spells/
skylib_fluffos_v3/mudlib/doc/new/object/
skylib_fluffos_v3/mudlib/doc/new/player/
skylib_fluffos_v3/mudlib/doc/new/room/guild/
skylib_fluffos_v3/mudlib/doc/new/room/outside/
skylib_fluffos_v3/mudlib/doc/new/room/storeroom/
skylib_fluffos_v3/mudlib/doc/object/
skylib_fluffos_v3/mudlib/doc/playtesters/
skylib_fluffos_v3/mudlib/doc/policy/
skylib_fluffos_v3/mudlib/doc/weapons/
skylib_fluffos_v3/mudlib/global/
skylib_fluffos_v3/mudlib/global/creator/
skylib_fluffos_v3/mudlib/handlers/
skylib_fluffos_v3/mudlib/include/casino/
skylib_fluffos_v3/mudlib/include/cmds/
skylib_fluffos_v3/mudlib/include/effects/
skylib_fluffos_v3/mudlib/include/npc/
skylib_fluffos_v3/mudlib/include/room/
skylib_fluffos_v3/mudlib/include/shops/
skylib_fluffos_v3/mudlib/net/daemon/
skylib_fluffos_v3/mudlib/net/daemon/chars/
skylib_fluffos_v3/mudlib/net/inherit/
skylib_fluffos_v3/mudlib/net/obj/
skylib_fluffos_v3/mudlib/net/obj/BACKUPS/
skylib_fluffos_v3/mudlib/obj/amulets/
skylib_fluffos_v3/mudlib/obj/armours/plate/
skylib_fluffos_v3/mudlib/obj/b_day/
skylib_fluffos_v3/mudlib/obj/clothes/transport/horse/
skylib_fluffos_v3/mudlib/obj/faith/symbols/
skylib_fluffos_v3/mudlib/obj/fungi/
skylib_fluffos_v3/mudlib/obj/gatherables/
skylib_fluffos_v3/mudlib/obj/instruments/
skylib_fluffos_v3/mudlib/obj/media/
skylib_fluffos_v3/mudlib/obj/misc/player_shop/
skylib_fluffos_v3/mudlib/obj/monster/godmother/
skylib_fluffos_v3/mudlib/obj/monster/transport/
skylib_fluffos_v3/mudlib/obj/rings/
skylib_fluffos_v3/mudlib/obj/scabbards/
skylib_fluffos_v3/mudlib/obj/spells/
skylib_fluffos_v3/mudlib/obj/stationery/
skylib_fluffos_v3/mudlib/obj/stationery/envelopes/
skylib_fluffos_v3/mudlib/obj/toys/
skylib_fluffos_v3/mudlib/obj/vessels/
skylib_fluffos_v3/mudlib/obj/weapons/axes/
skylib_fluffos_v3/mudlib/obj/weapons/chains/
skylib_fluffos_v3/mudlib/obj/weapons/maces/BACKUPS/
skylib_fluffos_v3/mudlib/save/autodoc/
skylib_fluffos_v3/mudlib/save/book_handler/
skylib_fluffos_v3/mudlib/save/books/history/calarien/
skylib_fluffos_v3/mudlib/save/mail/
skylib_fluffos_v3/mudlib/save/new_soul/data/
skylib_fluffos_v3/mudlib/save/parcels/
skylib_fluffos_v3/mudlib/save/playerinfo/
skylib_fluffos_v3/mudlib/save/players/d/
skylib_fluffos_v3/mudlib/save/players/s/
skylib_fluffos_v3/mudlib/save/random_names/
skylib_fluffos_v3/mudlib/save/random_names/data/
skylib_fluffos_v3/mudlib/save/terrains/
skylib_fluffos_v3/mudlib/save/terrains/tutorial_desert/
skylib_fluffos_v3/mudlib/save/terrains/tutorial_grassy_field/
skylib_fluffos_v3/mudlib/save/terrains/tutorial_mountain/
skylib_fluffos_v3/mudlib/save/todo_lists/
skylib_fluffos_v3/mudlib/secure/
skylib_fluffos_v3/mudlib/secure/cmds/admin/
skylib_fluffos_v3/mudlib/secure/cmds/lord/
skylib_fluffos_v3/mudlib/secure/config/
skylib_fluffos_v3/mudlib/secure/handlers/autodoc/
skylib_fluffos_v3/mudlib/secure/handlers/intermud/
skylib_fluffos_v3/mudlib/secure/include/global/
skylib_fluffos_v3/mudlib/secure/save/
skylib_fluffos_v3/mudlib/secure/save/handlers/
skylib_fluffos_v3/mudlib/secure/std/
skylib_fluffos_v3/mudlib/secure/std/classes/
skylib_fluffos_v3/mudlib/secure/std/modules/
skylib_fluffos_v3/mudlib/std/creator/
skylib_fluffos_v3/mudlib/std/dom/
skylib_fluffos_v3/mudlib/std/effects/
skylib_fluffos_v3/mudlib/std/effects/external/
skylib_fluffos_v3/mudlib/std/effects/fighting/
skylib_fluffos_v3/mudlib/std/effects/magic/
skylib_fluffos_v3/mudlib/std/effects/magic/BACKUPS/
skylib_fluffos_v3/mudlib/std/effects/other/BACKUPS/
skylib_fluffos_v3/mudlib/std/effects/priest/
skylib_fluffos_v3/mudlib/std/effects/room/
skylib_fluffos_v3/mudlib/std/environ/
skylib_fluffos_v3/mudlib/std/guilds/
skylib_fluffos_v3/mudlib/std/guilds/old/
skylib_fluffos_v3/mudlib/std/languages/
skylib_fluffos_v3/mudlib/std/liquids/
skylib_fluffos_v3/mudlib/std/npc/
skylib_fluffos_v3/mudlib/std/npc/goals/
skylib_fluffos_v3/mudlib/std/npc/goals/basic/
skylib_fluffos_v3/mudlib/std/npc/goals/misc/
skylib_fluffos_v3/mudlib/std/npc/plans/
skylib_fluffos_v3/mudlib/std/npc/plans/basic/
skylib_fluffos_v3/mudlib/std/npc/types/
skylib_fluffos_v3/mudlib/std/npc/types/helper/
skylib_fluffos_v3/mudlib/std/npcs/
skylib_fluffos_v3/mudlib/std/outsides/
skylib_fluffos_v3/mudlib/std/races/shadows/
skylib_fluffos_v3/mudlib/std/room/basic/BACKUPS/
skylib_fluffos_v3/mudlib/std/room/basic/topography/
skylib_fluffos_v3/mudlib/std/room/controller/
skylib_fluffos_v3/mudlib/std/room/inherit/topography/
skylib_fluffos_v3/mudlib/std/room/topography/area/
skylib_fluffos_v3/mudlib/std/room/topography/iroom/
skylib_fluffos_v3/mudlib/std/room/topography/milestone/
skylib_fluffos_v3/mudlib/std/shadows/curses/
skylib_fluffos_v3/mudlib/std/shadows/disease/
skylib_fluffos_v3/mudlib/std/shadows/fighting/
skylib_fluffos_v3/mudlib/std/shadows/healing/
skylib_fluffos_v3/mudlib/std/shadows/magic/
skylib_fluffos_v3/mudlib/std/shadows/poison/
skylib_fluffos_v3/mudlib/std/shadows/room/
skylib_fluffos_v3/mudlib/std/shops/controllers/
skylib_fluffos_v3/mudlib/std/shops/objs/
skylib_fluffos_v3/mudlib/std/shops/player_shop/
skylib_fluffos_v3/mudlib/std/socket/
skylib_fluffos_v3/mudlib/std/soul/d/
skylib_fluffos_v3/mudlib/std/soul/e/
skylib_fluffos_v3/mudlib/std/soul/i/
skylib_fluffos_v3/mudlib/std/soul/j/
skylib_fluffos_v3/mudlib/std/soul/k/
skylib_fluffos_v3/mudlib/std/soul/l/
skylib_fluffos_v3/mudlib/std/soul/n/
skylib_fluffos_v3/mudlib/std/soul/o/
skylib_fluffos_v3/mudlib/std/soul/q/
skylib_fluffos_v3/mudlib/std/soul/r/
skylib_fluffos_v3/mudlib/std/soul/u/
skylib_fluffos_v3/mudlib/std/soul/v/
skylib_fluffos_v3/mudlib/std/soul/y/
skylib_fluffos_v3/mudlib/std/soul/z/
skylib_fluffos_v3/mudlib/std/stationery/
skylib_fluffos_v3/mudlib/w/
skylib_fluffos_v3/mudlib/w/default/
skylib_fluffos_v3/mudlib/w/default/armour/
skylib_fluffos_v3/mudlib/w/default/clothes/
skylib_fluffos_v3/mudlib/w/default/item/
skylib_fluffos_v3/mudlib/w/default/npc/
skylib_fluffos_v3/mudlib/w/default/room/
skylib_fluffos_v3/mudlib/w/default/weapon/
skylib_fluffos_v3/mudlib/www/
skylib_fluffos_v3/mudlib/www/java/
skylib_fluffos_v3/mudlib/www/secure/
skylib_fluffos_v3/mudlib/www/secure/lpc/advanced/
skylib_fluffos_v3/mudlib/www/secure/lpc/intermediate/
skylib_fluffos_v3/win32/
/**
 * This is the main NPC inheritable.
 * @author Pinkfish
 * @change Modified to properly work on Divided Sky and
 * fixed skills/stats - Sandoz, 20th July 2001.
 * @see /std/npc/npc_base
 */

#include <player.h>
#include <route.h>
#include <skills.h>
#include <top_ten_tables.h>

inherit "/std/living/living";
inherit "/global/player/new_parse";
inherit "/std/living/response_mon";
inherit "/global/player/guild-race";

class chat_data {
    int chance;
    int total_w;
    int *weight;
    mixed chats;
    int last_chat;
}

nosave class chat_data chats, a_chats;

nosave int added_lang,
           follow_speed,
           chats_off,
           cannot_change_position,
           always_return_to_default_position,
           *move_after,
           aggressive,
           death_xp,
           death_xp_set,
           join_fight_type,
           _level,
           chase_id;

nosave string *move_zones,
              *following_route,
              *enter_commands,
              _home,
              join_fight_mess,
              *_greets,
              cur_lang;

nosave object *to_greet;

nosave mixed queued_commands, doing_story, combat_actions;

string find_rel( string word, int from );
void do_move_after( int running_away );
void do_route_move();
void add_language( string lang );
int query_chats_off();
int check_env();

/** @ignore yes */
int query_cols() { return 79; }

void create() {
    do_setup++;
    living::create();
    new_parse::create();
    response_mon::create();
    do_setup--;
    reset_get();

    follow_speed = 3;

    // Do they won't get silly commands like "rearrange".
    known_commands = ({ });

    living_commands();
    parser_commands();

    add_property("npc", 1 );
    add_property("no ambiguous", 1 );

    set_con(12);
    set_dex(12);
    set_int(12);
    set_str(12);
    set_wis(12);
    set_max_hp(10000);
    set_hp(10000);
    set_max_gp(1000);
    set_gp(1000);

    // Default to 5 seconds.
    always_return_to_default_position = 5;
    cur_lang = "common";

    // This is done only if the NPC doesn't have a language set after setup.
    // Check their env here as well.
    call_out( function() {
        if( !added_lang ) {
            add_language("common");
        if( clonep() && !ENV(TO) )
            call_out( function() {
                if( !ENV(TO) )
                    move("/room/rubbish");
            }, 60 );
        } }, 2);

    if( !do_setup ) {
        TO->setup();
        TO->reset();
    }

} /* create() */

/**
 * This method sets the unique propety on the NPC.
 * Unique NPCs give no xp when killed and will generate
 * informs when dying amongst other things.
 */
void set_unique() { add_property("unique", 1 ); }

/**
 * This method will fetch an item from the armoury and move it into the NPC.
 * @param the item to get
 * @param the condition of the item
 * @return the object of the new item
 */
object get_item( string item, int cond ) {
    object ob;

    if( !sizeof(item) || cond < 1 )
        error("No item name specified or condition less than 1.\n");

    if( !ob = ARMOURY_H->request_item( item, cond ) )
        error( sprintf("Failed to clone %s for %s (%s).\n", item, short(),
            file_name(TO) ) );

    ob->move(TO);

    return ob;

} /* get_item() */

/**
 * This method adds a language to the NPC.
 * @param lang the language to add
 */
void add_language( string lang ) {
    int lvl;
    string skill;

    if( !LANGUAGE_H->test_language( lang ) )
        return ;

    if( LANGUAGE_H->query_language_spoken( lang ) ) {
        skill = LANGUAGE_H->query_language_spoken_skill( lang );
        lvl = TO->query_skill( skill );
        TO->add_skill_level( skill, 100 - lvl );
    }

    if( LANGUAGE_H->query_language_written( lang ) ||
        LANGUAGE_H->query_language_magic( lang ) ) {
        skill = LANGUAGE_H->query_language_written_skill( lang );
        lvl = TO->query_skill( skill );
        TO->add_skill_level( skill, 100 - lvl );
    }

    cur_lang = lang;
    added_lang = 1;

} /* add_language() */

/**
 * This method sets the current language of the NPC.
 * @param lang the language to set as current
 */
int set_language( string lang ) {
    if( !LANGUAGE_H->test_language( lang ) )
        return 0;
    cur_lang = lang;
    return 1;
} /* set_language() */

/**
 * This method returns the current language of the NPC.
 * @return the current language
 */
string query_current_language() { return cur_lang; }

/**
 * This method allows you to control the NPC and get it to do
 * actions.  This can be used for NPC control and intelligence.
 * @param words the action to preform
 * @see init_equip()
 * @see init_command()
 * @example
 * ob = clone_object(NICE_HAIRY_APE);
 * ob->do_command("'I am a hairy ape!");
 * ob->do_command("emote apes around the room.");
 */
int do_command( string words ) {
    if( TO->query_property(PASSED_OUT) )
        return -1;

    if( stringp( words ) )
        return command( words );

    printf("Invalid parameter to do_command: %O for NPC %O in %O.\n",
           words, TO, ENV(TO) );
    return -1;

} /* do_command() */

/**
 * This method allows you submit delayed commands to the NPC via a call_out.
 * @param str the action to perform
 * @param tim the time delay after which to perform the action
 * @see do_command()
 * @see queue_command()
 * @see delay_command()
 */
varargs void init_command( string str, int tim ) {
    call_out("do_command", tim, str );
} /* init_command() */

/**
 * @ignore yes
 * No need to print any messages to an NPC.
 * Less functions will be called if we don't :)
 */
void add_message( string message, mixed things );

/** @ignore yes */
varargs string fix_string(string str, int width, int indent, mixed *args ...) {
     return "/global/player/events"->fix_string( str, width, indent, args ...);
} /* fix_string() */

/** @ignore yes */
string evaluate_message( mixed stuff ) {
    return "/global/player/events"->evaluate_message( stuff );
} /* evaluate_message() */

/** @ignore yes */
string fit_message( string message ) {
     return "/global/player/events"->fit_message( message );
} /* fit_message() */

/** @ignore yes */
string convert_message( string message ) {
    return "/global/player/events"->convert_message( message );
} /* convert_message() */

/** @ignore yes */
void comm_event( mixed thing, string type, string start, string rest,
                 string lang ) {
    if( !objectp( thing ) && !pointerp( thing ) )
        return;
    event( thing, type, start, rest, lang );
    call_other( INV(TO), "event_"+type, TO, start, rest, lang );
} /* comm_event() */

/**
 * @ignore yes
 * We need to be able to whisper as it generates event_whisper().
 */
void do_whisper( object ob, string event_type, string start, string type,
                string words, object *others, string lang, object me ) {
    event( ob, event_type, start, type, words, others, lang, me );
} /* do_whisper() */

/**
 * This method is used to expand the message strings used in the
 * npc messages.  It is used for chat strings and such things like
 * that.  The strings it expands are of the form:<br>
 * $lname$, $mname$, $aname$, $itheshort$ ...<br>
 * The first letter determines the type of object being referenced.
 * They are:
 * <dl>
 * <dt>m
 * <dd>Me!  The NPC itself.
 * <dt>l
 * <dt>A random living object in the NPC's environment.
 * <dt>i
 * <dt>A random interactive object (player) in the NPC's environment.
 * <dt>a
 * <dd>Chooses a random attacker from those attacking the NPC.
 * <dt>o
 * <dd>Choose a random object in the inventory of the NPC.
 * </dl>
 * After the first letter is a type of information being request.
 * <dl>
 * <dt>name
 * <dd>The name of the selected object.
 * <dt>cname
 * <dd>The capitalised name of the selected object.
 * <dt>gender
 * <dd>The gender string of the selected object (male, female, neuter).
 * <dt>poss
 * <dd>The possessive string of the selected object.
 * <dt>obj
 * <dd>The objective string of the selected object.
 * <dt>pronoun
 * <dd>The pronoun string of the selected object.
 * <dt>gtitle
 * <dd>The guild title of the selected object (only useful on livings).
 * <dt>ashort
 * <dd>The a_short() call.
 * <dt>possshort
 * <dd>The poss_short() call.
 * <dt>theshort
 * <dd>The the_short() call.
 * <dt>oneshort
 * <dd>The one_short() call.
 * </dl>
 * @see set_chat_string()
 * @see expand_mon_string()
 * @param in_str the input string
 * @param on the object to use for the 'o' matching
 */
string expand_string( string in_str, object on ) {
    string *str, ret;
    int i, add_dollar;
    object liv, *obs, ob;

    in_str = convert_message( in_str );
    str = explode( in_str, "$" );
    ret = "";
    for( i = 0; i < sizeof( str ); i++ ) {
        if( i % 2 == 0 ) {
            if( add_dollar )
                ret += "$";
            ret += str[i];
            add_dollar = 1;
            ob = 0;
        }
        else switch (str[i][0]) {
        case 'm' :
            ob = TO;
        case 'l' :
            if( !ob ) {
                if( !liv ) {
                    obs = INV( ENV(TO) ) - ({ TO });
                    if( sizeof( obs = filter( obs, (: living($1) :) ) ) )
                        liv = choice( obs );
                }
                if( !liv )
                    break;
                ob = liv;
            }
        case 'i' :
            if( !ob ) {
                if( !liv ) {
                    obs = INV( ENV(TO) ) - ({ TO });
                    if( sizeof( obs = filter( obs, (: interactive($1) :) ) ) )
                        liv = choice( obs );
                }
                if( !liv )
                    break;
                ob = liv;
            }
        case 'a' :
            if( !ob ) {
                if( !sizeof( obs = (object *)TO->query_attacker_list() ) )
                    break;
                ob = choice( obs );
            }
        case 'o' :
            if( !ob ) {
                if( !on ) {
                    obs = INV( ENV(TO) );
                    if( sizeof( obs = filter( obs, (: !living( $1 ) :) ) ) )
                        on = choice( obs );
                }
                ob = on;
            }
            switch (str[ i ][ 1 .. ]) {
            case "theshort" :
                ret += (string)ob->the_short();
                add_dollar = 0;
                break;
            case "ashort" :
                ret += (string)ob->a_short();
                add_dollar = 0;
                break;
            case "oneshort":
                ret += (string)ob->one_short();
                add_dollar = 0;
                break;
            case "possshort" :
                ret += (string)ob->poss_short();
                add_dollar = 0;
                break;
            case "name" :
                ret += (string)ob->query_name();
                add_dollar = 0;
                break;
            case "cname" :
                ret += (string)ob->query_cap_name();
                add_dollar = 0;
                break;
            case "gender" :
                ret += (string)ob->query_gender_string();
                add_dollar = 0;
                break;
            case "poss" :
                ret += (string)ob->query_possessive();
                add_dollar = 0;
                break;
            case "obj" :
                ret += (string)ob->query_objective();
                add_dollar = 0;
                break;
            case "gtitle" :
                ret += (string)ob->query_gender_title();
                add_dollar = 0;
                break;
            case "pronoun" :
                ret += (string)ob->query_pronoun();
                add_dollar = 0;
                break;
            default :
                if( add_dollar )
                    ret += "$";
                ret += str[i];
                add_dollar = 1;
                break;
            }
            ob = 0;
            break;
        default :
            if( add_dollar )
                ret += "$";
            ret += str[i];
            add_dollar = 1;
            ob = 0;
            break;
        }
    }

    if( strlen( ret ) && ret[ strlen( ret ) - 1 ] == '$' )
        return ret[0..strlen(ret)-2];

    return ret;

} /* expand_string() */

/**
 * This method executes the string passed in.  It handles all the
 * stuff which is needed from the chat_string stuff.
 *
 * If the input is a function pointer then it is evaluated with one
 * parameter, being the NPC.
 *
 * If the input is a string then the first letter determines what will
 * be done with it.  All these are passed through expand_string
 * so that exciting things can be done.
 * <ul>
 * <li># - A call_other will be generated.  The parameters are separated by
 * ':'s, so "#bing:fred:chicken" would call<pre>
 * TO->bing("fred", "chicken");
 * </pre>.
 * <ul>', ", : - These will generate a 'say', 'lsay' or 'emote'.
 * <ul>@ - This will run the passed in command.  Eg: "@frog" would cause the
 *     soul command frog to be used.
 * <ul>Anything else will be used as a message to be sent to everyone in the
 *     room.
 * </ul>
 * @param str the thing to execute
 * @see expand_string()
 */
void expand_mon_string( mixed str ) {
    string *args;

    if( functionp( str ) )
        evaluate( str, TO );
    else {
        if( !stringp( str ) && ENV( TO ) ) {
            tell_room( ENV(TO), "%^RED%^"+ TO->the_short()+" says: please "
                "bugreport me, I have a bad load_chat.%^RESET%^\n");
        }
        switch( str[ 0 ] ) {
          case '#' :
            args = explode( str[ 1..], ":" );
            switch( sizeof( args ) ) {
              case 1 :
                call_other( TO, args[0] );
              break;
              case 2 :
                call_other( TO, args[0], args[1] );
              break;
              case 3 :
                call_other( TO, args[0], args[1], args[2] );
              break;
              case 4 :
                call_other( TO, args[0], args[1], args[2], args[3] );
              break;
              default :
                call_other( TO, args[0], args[1], args[2], args[3], args[4] );
              break;
            }
          break;
          case ':' :
          case '\'' :
          case '"' :
            init_command( expand_string( str, 0 ), 1 );
            break;
          case '@' :
            init_command( expand_string( str[ 1 .. ], 0 ), 1 );
            break;
          default :
            tell_room( ENV(TO), expand_string( str, 0 ) +"\n" );
        }
    }
} /* expand_mon_string() */

/** @ignore yes */
void set_name( string name ) {
    if( query_name() && query_name() != "object" )
        return;
    ::set_name( name );
    add_plural( pluralize( name ) );
    set_short( name );
    set_long("This is a half-fabricate NPC.\n");
    set_living_name( name );
} /* set_name() */

/** @ignore yes */
int query_sp() { return 50; }

/** @ignore yes */
int adjust_sp( int number ) { return 50; }

/** @ignore yes */
void event_whisper( object thing, string start, string mess, object *obs,
                    string lang, object me) {
    response_mon::event_whisper( thing, mess, obs, lang, me);
} /* event_whisper() */

/**
 * This method sets up the basic abilities and race of the critter.
 * @see query_race()
 * @see query_level()
 * @see query_gender()
 * @param race this is the race of the character.  It should be
 * a race that's understood by the /std/race.c
 * @param level this is the level of the NPC.  The number is used
 * by the race object to set ability scores.
 * @param gender this is the gender of the NPC.  It can be either a
 * number ( 0 - neuter, 1 - male or 2 - female ) or a string ("neuter",
 * "male", "female")
 * @example
 * basic_setup("human", 200, "male");
 */
void basic_setup( string race, int level, mixed gender ) {
    RACE_OBJ->set_level( level, race );
    _level = level;
    set_gender( gender );
} /* basic_setup() */

/**
 * This method returns the level of the NPC.
 * @return the level of the NPC
 * @see basic_setup()
 */
int query_level() { return _level; }

/**
 * This method sets the message to use when joining into fights.
 * @param str the message to print when joining a fight
 * @see query_join_fights()
 * @see set_join_fight_type()
 */
void set_join_fights(string str) { join_fight_mess = str; }

/**
 * This method returns the message to use when joining into fights.
 * @return the message to print when joining a fight
 * @see set_join_fights()
 * @see set_join_fight_type()
 */
string query_join_fights() { return join_fight_mess; }

/**
 * This method sets the flag which allows the NPC to join into fights.
 * If this is set to a non-zero value then the NPC will join into
 * fights in progress using the fight joining message.
 * @param i 1 if the npc is to join fights, 0 if not
 * @see set_join_fights()
 * @see query_join_fight_type()
 */
void set_join_fight_type( int i ) { join_fight_type = i; }

/**
 * This method returns the flag which allows the NPC to join into fights.
 * @return 1 if the NPC is to join fights, 0 if not
 * @see set_join_fights()
 * @see query_join_fight_type()
 */
int query_fight_type() { return join_fight_type; }

/**
 * This method check to see if the NPC should start attacking someone
 * when they enter the NPC's environment.  It is called from inside
 * init().  The NPC will only attack if the agressive is set and the
 * person is visible to be attacked.  The property
 * <pre>"no attack"</pre> can be set on the npc (or player) to
 * stop them being attacked.
 * @param who the person to potentially start attacking
 * @see set_aggressive()
 */
void start_attack( object who ) {
    if( !who || !aggressive )
        return;

    if( !who->query_visible(TO) || who->query_auto_loading() ||
        file_name(who) == DEATH || who->query_property("guest") ||
        ( userp(who) && !interactive(who) ) ||
        who->query_property("no attack") ) {
        return;
    }

    // Use a call_other() just in case there are shadows.
    if( aggressive > 1 || interactive(who) )
        TO->attack_ob( who );

} /* start_attack() */

/**
 * This method returns the current aggressive level of the NPC.
 * If the aggressive is set to 1, then the NPC will attack all players
 * that enter its environment.  If the aggressive is set to 2 then
 * the NPC will attack everything (including other NPCs).
 * <p>
 * See the function start_attack() for information about things you
 * can do to stop an aggressive NPC from attacking things.
 * @return the aggressive level of the NPC
 * @see set_aggressive()
 * @see start_attack()
 */
int query_aggressive() { return aggressive; }

/**
 * This method sets the current aggressive level of the NPC.
 * If the aggressive is set to 1, then the NPC will attack all players
 * that enter its environment.  If the aggressive is set to 2 then
 * the NPC will attack everything (including other NPCs).
 * <p>
 * See the function start_attack() for information about things you
 * can do to stop an aggressive NPC from attacking things.
 * @see query_aggressive()
 * @see start_attack()
 * @see set_join_fights()
 * @see set_throw_out()
 * @param level the new aggressive level
 */
void set_aggressive( int level ) {
    aggressive = level;
    // this added to make aggressive npcs join in fights if they aren't
    // already set that way.  This is needed coz otherwise you can sneak
    // into a room and fight the aggressive NPCs one at a time
    // - Ceres 10/97
    if( level && !join_fight_mess )
        join_fight_mess = TO->one_short()+" joins in the fight!";
} /* set_aggressive() */

/** @ignore yes */
void init() {
    set_heart_beat( 1 );

    if( !ENV(TO) || file_name( ENV(TO) )[1..4] == "room" )
        return;

    if( aggressive )
        call_out("start_attack", 1, TP );

    if( _greets && TP && !TP->query_property("dead") &&
        TP->query_visible(TO) && !TO->is_fighting(TP) ) {
        if( pointerp(to_greet) ) {
            if( member_array( TP, to_greet ) == -1 )
                to_greet += ({ TP });
        } else
            to_greet = ({ TP });
    }

} /* init() */

/**
 * This method makes the NPC to equip whatever they have.
 * @see do_command()
 */
void init_equip() { command("equip"); }

/** @ignore yes */
string long( string str, int dark ) {
    string s;

    if( dark < -1 )
        s = "You can only make out a rough shape in the gloom.\n";
    else if( dark > 1 )
        s = "You can only make out a rough shape in the glare.\n";
    else
        s = query_long();

    if( !dark ) {
        s += CAP(HE)+" "+health_string(TO,0)+".\n";
        s += CAP(HE)+" is " + query_position_short() + ".\n";
        s += ( query_property( PASSED_OUT ) ?
             CAP(HE)+" appears to be passed out.\n" : "" );
        s += calc_extra_look();
        s += query_living_contents(0);
    }

    return s;

} /* long() */

/**
 * This method returns 1 if it is ok to turn of the NPC's heart beat.
 * This can be overridden for times when the heart beat needs to be
 * kept on for some reason.
 * @return 1 if the heart beat should go off, 0 if it should stay on
 */
int query_ok_turn_off_heart_beat() {
    return 1;
} /* query_ok_turn_off_heart_beat() */

/** @ignore yes */
varargs int adjust_hp( int num, object attacker, object wep, string attack ) {
    set_heart_beat( 1 );
    return ::adjust_hp( num, attacker, wep, attack );
} /* adjust_hp() */

/** @ignore yes */
int adjust_gp( int number ) {
    set_heart_beat( 1 );
    return ::adjust_gp( number );
} /* adjust_gp() */

/**
 * This method can be temporarily overwritten in NPCs to stop them
 * wandering under certain circumstances.  For example if someone is
 * fighting in the room with the NPC, and we want the NPC to watch.
 * Alternatively to overwriting the function, you can set a "no wander"
 * property on the NPC.
 * @return should return 1 if we don't want to wander
 */
int do_not_wander() { return query_property("no wander"); }

/**
 * This method adds a move zone onto the NPC.  The move zones control
 * which areas the NPC will wander into, a move zone is set on the
 * room and the NPC will only enter rooms which have a matching
 * move zone.  If there is no move zone, then the NPC will enter
 * any room.
 * <p>
 * If the parameter is an array each of the elements of the array
 * will be added as a move zone.
 * @param zone the zone(s) to add
 * @see remove_move_zone()
 * @see query_move_zones()
 * @see set_move_after()
 * @see do_not_wander()
 */
void add_move_zone( mixed zone ) {
    if( !sizeof(zone) )
        return;

    if( !pointerp(move_zones) )
        move_zones = ({ });

    if( pointerp( zone ) ) {
        foreach( zone in zone )
            add_move_zone( zone );
    } else if( stringp(zone) && member_array( zone, move_zones ) == -1 )
        move_zones += ({ zone });

} /* add_move_zone() */

/**
 * This method removes a move zone from the NPC.
 * @param zone the zone(s) to remove
 * @see add_move_zone()
 * @see query_move_zones()
 * @see set_move_after()
 */
void remove_move_zone( mixed zone ) {
    if( !sizeof( move_zones ) )
        return;

    if( pointerp( zone ) ) {
        move_zones -= zone;
        return;
    }

    move_zones -= ({ zone });

} /* remove_move_zone() */

/**
 * This method returns the current list of move zones on the NPC
 * @return the current list of move zones
 * @see add_move_zone()
 * @see remove_move_zones()
 * @see set_move_after()
 */
string *query_move_zones() { return move_zones || ({ }); }

/**
 * This method sets the speed at which the NPC will randomly
 * wander around.  The NPC will wander at the speed:<pre>
 * speed = after + random(rand)
 * </pre>This is called every time the NPC sets up for its next move.
 * <p>
 * The move zones control which areas the NPC will wander into,
 * a move zone is set on the room and the NPC will only enter
 * rooms which have a matching move zone.  If there is no move
 * zone, then the NPC will enter any room.
 * @param after the fixed amount of time
 * @param rand the random amount of time
 * @see remove_move_zone()
 * @see query_move_zones()
 * @see add_move_zone()
 * @see do_not_wander()
 */
void set_move_after( int after, int rand ) {
    move_after = ({ after, rand });
    if( after != 0 && rand != 0 )
        do_move_after(0);
} /* set_move_after() */

/**
 * This method returns the current move after values.
 * It returns an array of the form:<pre>
 *    ({
 *      after,
 *      rand,
 *     })
 * </pre>
 * @return the move after values
 * @see set_move_after()
 */
int *query_move_after() { return move_after; }

/**
 * This is called when the NPC decides it must continue down
 * a certain route.  This will be called by the wander handler
 * and can be used to force the NPC to wander along a route
 * faster.
 * @param running_away this is 1 if the npc is running away
 * @see set_move_after()
 * @see do_not_wander()
 */
void do_move_after( int running_away ) {
    if( sizeof( following_route ) )
        do_route_move();
    else
        WANDER_H->move_after( running_away );
} /* do_move_after() */

/** @ignore yes */
void dest_me() {
    WANDER_H->delete_move_after(TO);
    ::dest_me();
} /* dest_me() */

/** @ignore yes */
string expand_nickname(string str) { return str; }

/**
 * This method throws away any queued commands.
 * It doesn't remove the call_out, however if no
 * new commands are added there will be no effect.
 * @see init_equip()
 * @see init_command()
 * @see delay_command()
 * @see do_command()
 * @see queue_command()
 */
void delete_queued_commands() { queued_commands = 0; }

/**
 * This method returns the queued command list.
 * @see init_equip()
 * @see init_command()
 * @see delay_command()
 * @see do_command()
 * @see queue_command()
 */
mixed query_queued_commands() { return queued_commands || ({ }); }

/** @ignore yes */
private void next_queued_command() {
    mixed next;

    if( !sizeof( queued_commands ) )
        return;

    // The interval of the next command on queue.
    next = queued_commands[0];

    if( intp( next ) ) {
        queued_commands = queued_commands[1..];
        if( !sizeof( queued_commands ) )
            return;
        next = queued_commands[0];
    }
    while( stringp( next ) ) {
      if( TO->queue_commands() ) {
          call_out( (: next_queued_command :), 2 );
          return;
      }
      do_command( next );
      queued_commands = queued_commands[1..];
      if( !sizeof( queued_commands ) )
          return;
      next = queued_commands[0];
    }
    call_out( (: next_queued_command :), next );
} /* next_queued_command */

/**
 * This method allows you to control the NPC and get it to do
 * actions where they are queued as for players.   The command
 * is always delayed by delay even if there are no commands pending
 * unlike queue_command(). This function
 * is 100% compatible with queue_command() and init_command().
 * @param words the action to perform
 * @param interval to wait before the command.
 * @see queue_command()
 * @see query_queued_commands()
 * @see init_command()
 * @see do_command()
 * @example
 * ob = clone_object(NICE_HAIRY_APE);
 * ob->delay_command("'I am a hairy ape!", 10 );
 * ob->delay_command("emote apes around the room.", 2 );
 * ob->queue_command("emote get banana.", 3 );
 * ob->queue_command("emote get apple.");
 * After 10 seconds it says "I am a hariy ape",
 * 2 seconds after that it apes around the room,
 * immediately following that it gets a banana
 * and 3 seconds after that it gets an apple.
 */
int delay_command( string words, int interval ) {
    if( query_property( PASSED_OUT ) )
        return -1;
    if( stringp( words ) ) {
        // If there are no queued commands, start the unqueuing process.
        if( !sizeof( queued_commands ) )
            call_out( (: next_queued_command :), interval );
        queued_commands = query_queued_commands() + ({ interval, words });
        return 1;
    }
    printf( "Invalid parameter to delay_command: %O for monster %O in %O.\n",
            words, TO, ENV(TO) );
    return -1;
} /* delay_command() */

/**
 * This method allows you to control the NPC and get it to do
 * actions where they are queued as for players.  If there are no
 * commands pending the command is executed immediately.  This function
 * is 100% compatible with delay_command() and init_command().
 * @param words the action to perform
 * @param interval the time to wait before processing another command
 * If omitted defaults to 2 seconds as per players
 * @see delay_command()
 * @see query_queued_commands()
 * @see init_command()
 * @see do_command()
 * @example
 * ob = clone_object(NICE_HAIRY_APE);
 * ob->queue_command("'I am a hairy ape!");
 * ob->queue_command("emote apes around the room.", 5 );
 * ob->queue_command("get banana", 10 );
 * ob->delay_command("emote get apple.", 3 );
 * Right away it says "I am a hairy ape",
 * 2 seconds later it apes around the room,
 * 5 seconds after that it gets a banana
 * and 13 seconds (10+3) after that it gets an apple.
 */
varargs int queue_command( string words, int interval ) {
    if( query_property( PASSED_OUT ) )
        return -1;
    if( stringp( words ) ) {
        if( undefinedp( interval ) )
            interval = 2;
        /* if there are no queued commands, start the unqueuing process */
        if( !sizeof( queued_commands ) ) {
            queued_commands = ({ words, interval });
            next_queued_command();
            return 1;
        }
        /* there are queued commands, so just add them to the existing ones */
        queued_commands = queued_commands + ({ words, interval });
        return 1;
    }
    printf( "Invalid parameter to queue_command: %O for monster %O in %O.\n",
            words, TO, ENV(TO) );
    return -1;
} /* queue_command() */

/** @ignore yes */
varargs int move( mixed dest, string messin, string messout ) {
    int result;
    object before = ENV(TO);
    result = living::move( dest, messin, messout );
    if( !result )
        me_moveing( before );
    return result;
} /* move() */

/** @ignore yes */
void room_look() {
    ::room_look();

    if( !sizeof( enter_commands ) )
        return;

    foreach( string cmd in enter_commands ) {
        if( functionp( cmd ) ) {
            call_out( cmd, 1 + random( sizeof( enter_commands ) + 1 ), TO );
            continue;
        }
        if( cmd[ 0 .. 0 ] == "#" )
            call_out( cmd[1..], 1 + random( sizeof( enter_commands ) + 1 ) );
        else
            init_command( cmd, 1 + random( sizeof( enter_commands ) + 1 ) );
    }
} /* room_look() */

/**
 * This method is used to make the NPC run away.  This will be
 * called by the combat code for wimpy when the NPC is bellow the
 * number of points used to trigger the wimpy action.
 * @return 1 if successfuly ran away
 */
int run_away() {
    if( query_property( "run away" ) == -1 )
        return 0;
    /* just to make npcs a bit trickier... */
    do_command("lose all");
    become_flummoxed();
    if( sizeof( following_route ) ) {
        do_route_move();
        return 1;
    }
    if( query_property( "run away" ) ) {
        do_move_after( 1 );
        return 1;
    }
    return ::run_away();
} /* run_away() */

/** @ignore yes */
int rand_num( int min, int max ) {
    return min + random( max - min + 1 );
} /* rand_num() */

/**
 * This method sets the stats for the NPC to some exciting random
 * values.
 * @param min the minimum value of stats
 * @param max the maximum value of stats
 */
void set_random_stats( int min, int max ) {
    set_str( rand_num( min, max ) );
    set_dex( rand_num( min, max ) );
    set_int( rand_num( min, max ) );
    set_con( rand_num( min, max ) );
    set_wis( rand_num( min, max ) );
} /* set_random_stats() */

/**
 * This method adds a command to be called whenever the npc enters
 * a room.  If the command is a string, then it will be executed
 * as if they had typed it.  If it is a function then the function
 * will be evaluated and one argument (the NPC itself) will be passed
 * in.
 * @param str the enter commands to add
 * @see reset_enter_commands()
 * @see query_enter_commands()
 */
void add_enter_commands( mixed str ) {
    if( !enter_commands )
        enter_commands = ({ });

    if( stringp( str ) || functionp( str ) )
        enter_commands += ({ str });
    else if( pointerp( str ) )
        foreach( str in str )
            add_enter_commands( str );

} /* add_enter_commands() */

/**
 * This method returns the current array of enter commands.
 * @return the current array of enter commands
 * @see reset_enter_commands()
 * @see add_enter_commands()
 */
string *query_enter_commands() { return enter_commands || ({ }); }

/**
 * This method resets the array of enter commands back to nothing.
 * @see add_enter_commands()
 * @see query_enter_commands()
 */
void reset_enter_commands() { enter_commands = 0; }

/** @ignore yes */
void event_enter( object dest, string mess, object from ) {
    // Stop massive spam in /room/rubbish and /room/void.
    if( ENV(TO) && file_name( ENV(TO) )[1..4] == "room" )
        return;
    living::event_enter( dest, mess, from );
} /* event_enter() */

/**
 * This method is used to make the NPC follow after attackers
 * when they leave the room.
 * @param who the person we are chasing
 * @param dir the direction to follow them in
 */
void do_follow_move( object who, string dir ) {
    chase_id = 0;

    // Only chase if we are not following a route,
    // and are not fighting anyone here.
    if( !sizeof(following_route) && !TO->query_fighting() )
        do_command(dir);

} /* do_follow_move() */

/** @ignore yes */
void event_exit( object who, string mess, object to ) {
    string fname;
    mixed exits;
    int i;

    new_parse::event_exit( who, mess, to );
    living::event_exit( who, mess, to );

    // Don't move if move_after isn't set, or if following a route.
    if( !to || !move_after || sizeof( following_route ) )
        return;

    // Chase the sucker, if they're on our attacker list.
    if( member_array( who, TO->query_attacker_list() ) == -1 )
        return;

    exits = ENV(TO)->query_dest_dir();
    fname = file_name(to);

    // No exit to destination.
    if( ( i = member_array( fname, exits ) ) == -1 )
        return;

    // Don't follow if we are only supposed to wander in move_zones and
    // don't have unrestricted follow set.
    if( !TO->query_property("unrestricted follow") &&
        !sizeof( to->query_zones() & query_move_zones() ) )
        return;

    if( chase_id )
        remove_call_out( chase_id );

    chase_id = call_out( (: do_follow_move :), 3 + random(follow_speed),
        who, exits[i-1] );

} /* event_exit() */

/**
 * This method will set up a 'home' location for the NPC.
 * This is the file name of the room the NPC
 * should return to whenever it is moved.
 * It can be used to make unique NPCs returns
 * to their default location etc.
 * @param loc the filename of the room to set as home
 */
void set_home_location( string loc ) {
    _home = loc;
} /* set_home_location() */

/**
 * This method will return the NPC's home location.
 * @return the home location
 */
string query_home_location() { return _home; }

/** @ignore yes */
void event_move_object( mixed from, mixed to ) {
    ::event_move_object( from, to );

    if( chase_id )
        remove_call_out( chase_id );

    if( _home && find_call_out("check_home_location") == -1 )
        call_out("check_home_location", 4 + random( 4 ) );

} /* event_move_object() */

/** @ignore yes */
void check_home_location() {
    string where;

    if( ENV(TO) && ( where = base_name(ENV(TO)) ) &&
        where[1..4] != "room" && where != _home &&
        !sizeof( TO->query_following_route() ) )
        TO->move_me_to( _home );

} /* check_home_location() */

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

/**
 * This function makes the more intelligent NPCs pick up their
 * weapons after being disarmed.
 * It can be overwritten if you don't want the default disarm
 * response to work for your NPC.
 * @param disarmer the person who disarmed us
 * @param weapon the weapon that was disarmed
 */
void event_disarm( object disarmer, object weapon ) {
    int i, time;

    if( !objectp(weapon) )
        return;

    i = (int)TO->query_int();
    if( ( time = 10 - sqrt( i ) ) < 4 )
        time = 4;

    if( i >= 20 || i > random( 60 ) ) {
        if( ENV(weapon) == ENV(TO) )
            init_command("get "+file_name(weapon), time++ );
        init_command("hold "+file_name(weapon), time );
    }

} /* event_disarm() */

/**
 * This method queries the greetings of the NPC,
 * if there are any set.
 * @return the greetings array
 */
string *query_greetings() { return _greets; }

/**
 * This method sets up the greeting strings to use when
 * someone enters the room the NPC is in.
 * This is useful for shopkeepers and more unique NPCs.
 * $ob$ in greeting strings will be replaced with
 * the people that are being greeted.
 *
 * To use a greeting in form of a soul command, please
 * use an "@" in front of the greeting string. Soul greetings
 * will be called on each person individually, and it is
 * possible to set up random souls by separating different
 * soul commands by a ":" in the greeting string (see example).
 *
 * It is also possible to use a function instead of a string
 * greeting.  The function can either be a function
 * pointer or a string of the following format -
 * "#file_name:function", and will then be called with
 * the following arguments - the thing (object) doing
 * the greeting, and the array of objects to greet.
 *
 * NOTE : The NPC will only greet those livings he can see,
 * and that are not ghosts or otherwise dead.
 *
 * @param str the greeting or array of greetings to use
 * @example set_greeting( ({"Greetings, $ob$!", (: do_greet :) }) );
 * @example set_greeting( ({"'Hello $ob$!", "#"+PUB+"pub:do_greet"}) );
 * @example set_greeting("@salute $ob$:bow $ob$:bing $ob$");
 */
void set_greeting( mixed str ) {
    string *tmp;

    if( !_greets )
        _greets = ({ });

    if( stringp(str) && sizeof(str) ) {
        if( str[0] == "#" ) {
            str = str[1..];
            if( sizeof( tmp = explode( str, ":" ) ) < 2 ) {
                write("Greeting function missing a function name in "+
                      file_name(TO)+".\n");
                return;
            }
            if( !find_object(tmp[0]) && !file_exists( tmp[0]+".c" ) ) {
                write("Greeting function pointing to a non-existent "
                      "file ("+tmp[0]+".c) in "+file_name(TO)+".\n");
                return;
            }
        }
        _greets += ({ str });
    } else if( pointerp(str) )
        _greets += str;
    else if( functionp(str) )
        _greets += ({ str });

} /* set_greeting() */

/** @ignore yes */
object *query_to_greet() { return to_greet; }

/**
 * The main heart beat function.  This is called by the driver
 * every 2 seconds.  Does all the maintenance stuff like fixing
 * up hps, greeting people and stuff like that.
 */
void heart_beat() {
    int i, j, d;
    mixed using;
    string *tmp;

    living::heart_beat();

    if( hp < 0 || query_property("dead") )
        d = 1;

    i = check_env();

    if( !d && i && _greets && sizeof( to_greet ) && sizeof( to_greet = filter(
        to_greet, (: $1 && ENV($1) == ENV(TO) :) ) ) ) {
        if( functionp( using = choice(_greets) ) ) {
            evaluate( using, TO, to_greet );
        } else if( sizeof( to_greet = filter( to_greet,
            (: !TO->is_fighting($1) :) ) ) ) {
            switch( using[0] ) {
              case '#' :
                tmp = explode( using[1..], ":");
                call_other( tmp[0], tmp[1], TO, to_greet );
              break;
              case '@' :
                tmp = explode( using[1..], ":");
                foreach( object ob in to_greet )
                    do_command( replace( choice(tmp), "$ob$",
                        ob->query_name() ) );
              break;
              default :
                do_command( replace( using, "$ob$",
                    query_multiple_short( map( to_greet,
                        (: $1->short() :) ) ) ) );
            }
        }
    }

    to_greet = 0;

    if( hp == max_hp && gp == max_gp && TO->query_ok_turn_off_heart_beat() ) {
        if( !i || ( !chats && !a_chats ) ) {
            set_heart_beat( 0 );
            return;
        }
    }

    if( !d && !query_chats_off() && i ) {
        if( TO->query_fighting() ) {
            if( sizeof(doing_story) && sizeof( doing_story[ 1 ] ) ) {
                expand_mon_string( doing_story[ 1 ][ 0 ] );
                doing_story[ 1 ] = doing_story[ 1 ][ 1 .. ];
            } else if( a_chats && random( 1000 ) < a_chats->chance &&
                sizeof( a_chats->weight ) ) {
                i = random( a_chats->total_w + 1 );
                while( ( i -= a_chats->weight[ j ] ) > 0 )
                    j++;
                if( j == a_chats->last_chat )
                    j = ( j + 1 ) % sizeof( a_chats->weight );
                a_chats->last_chat = j;
                if( pointerp( a_chats->chats[ j ] ) ) {
                    expand_mon_string( a_chats->chats[ j ][ 0 ] );
                    if( !doing_story )
                        doing_story = allocate( 2, ({ }) );
                    doing_story[ 1 ] = a_chats->chats[ j ][ 1 .. ];
                } else
                    expand_mon_string( a_chats->chats[ j ] );
            }
        } else if( chats ) {
            if( sizeof(doing_story) && sizeof( doing_story[ 0 ] ) ) {
                expand_mon_string( doing_story[ 0 ][ 0 ] );
                doing_story[ 0 ] = doing_story[ 0 ][ 1 .. ];
            } else if( chats && random( 1000 ) < chats->chance &&
                sizeof( chats->weight ) ) {
                i = random( chats->total_w + 1 );
                while( ( i -= chats->weight[ j ] ) > 0 )
                    j++;
                if( j == chats->last_chat )
                    j = ( j + 1 ) % sizeof( chats->weight );
                chats->last_chat = j;
                if( pointerp( chats->chats[ j ] ) ) {
                    expand_mon_string( chats->chats[ j ][ 0 ] );
                    if( !doing_story )
                        doing_story = allocate( 2, ({ }) );
                    doing_story[ 0 ] = chats->chats[ j ][ 1 .. ];
                } else
                    expand_mon_string( chats->chats[ j ] );
            }
        }
    }
} /* heart_beat() */

/**
 * This method queries the speed at which the NPC will follow
 * after a player, when they leave combat.
 * @return the current follow speed
 * @see set_follow_speed()
 */
int query_follow_speed() { return follow_speed; }

/**
 * This method sets the speed at which the NPC will follow
 * after a player, when they leave combat.
 * @param speed the new follow speed
 * @see query_follow_speed()
 */
void set_follow_speed( int speed ) { follow_speed = speed; }

/**
 * This method checks to see if there are any players in the environment
 * of the NPC.  This should be used to determine if chats should
 * be turned off and other things which should only work in the
 * presence of players.
 * @return 1 if there is a player in the room, 0 if not
 */
int check_env() {
    object thing;

    if( !ENV(TO) || file_name( ENV(TO) )[1..4] == "room")
        return 0;

    if( ENV(TO)->query_linked() )
        return 1;

    foreach( thing in INV( ENV(TO) ) )
      if( interactive( thing ) || thing->query_slave() )
          return 1;

    return 0;

} /* check_env() */

/**
 * This method causes the NPC to move in the given direction.
 * @param move the direction to move in
 */
void do_move( string move ) {
    command( move );
} /* do_move() */

/**
 * This method gets the next direction to go in the route which
 * is currently being followed and will remove this direction
 * from the array.
 * @return the next direction to go to
 * @see query_following_route()
 * @see do_route_move()
 */
string get_next_route_direction() {
    string direc;
    if( !sizeof( following_route ) )
        return 0;
    direc = following_route[0];
    following_route = following_route[1..];
    return direc;
} /* get_next_route_direction() */

/**
 * This method returns the current array of directions we are following
 * as a route.
 * @see get_next_route_direction()
 * @see do_route_move()
 */
string *query_following_route() { return following_route || ({ }); }

/**
 * This method moves the NPC one more location along the route it
 * is following.
 * @see query_following_route()
 * @see get_next_route_direction()
 */
void do_route_move() {
    if( !sizeof( following_route ) )
         return;
    do_command( get_next_route_direction() );
} /* do_route_move() */

/**
 * This method is called by the move_me_to function after the
 * route handler has successfuly discovered the route to follow.
 * @param route the route to follow
 * @param delay the delay to follow it with
 * @param dest route destination
 * @see move_me_to()
 */
protected void got_the_route( string *route, int delay, string dest ) {
    following_route = route;
    if( sizeof(route) ) {
        WANDER_H->move_me_please( delay, dest );
        do_route_move();
    } else
        TO->stopped_route();
} /* got_the_route() */

/**
 * This method will move the NPC to the specified destination.  The
 * NPC will walk from where they currently are to the destination using
 * the time delay specified between the movements.
 * <p>
 * If the location is reached then the function "stopped_route" will
 * be called on the npc.  The first argument to the function will
 * be 0 if the npc did not reach its destination and 1 if it did.
 * @param dest the destination to go to
 * @param delay the time delay between each move
 * @example
 * inherit "/std/npc";
 *
 * void go_home() {
 *    move_me_to(HOME_LOCATION, 2);
 * } /\* go_home() *\/
 *
 * void stopped_route(int success) {
 *    if (success) {
 *       do_command("emote jumps for joy.");
 *    } else {
 *       do_command("emote looks sad and lost.");
 *    }
 * } /\* stopped_route() *\/
 * @see get_next_route_direction()
 * @see got_the_route()
 * @see query_following_route()
 * @see do_route_move()
 */
varargs void move_me_to( string dest, int delay ) {
    string *dest_dir, *start_dir;

    if( !ENV(TO) )
        return ;

    // Very likely to have no route.
    if( !MAP_H->static_query_short( dest ) ||
        !MAP_H->static_query_short( file_name( ENV(TO) ) ) ) {
        // One of the rooms has no exits.
        if( !sizeof( dest_dir = dest->query_dest_dir() ) ||
            !sizeof( start_dir = ENV(TO)->query_dest_dir() ) ) {
            call_out("stopped_route", 1 );
            return;
        }
        call_out("stopped_route", 1 );
        return;
    }

    ROUTE_HANDLER->get_route( dest, file_name( ENV(TO) ),
                (: got_the_route( $1, $(delay), $(dest) ) :) );

} /* move_me_to() */

/** @ignore yes */
int clean_up( int parent ) {
    if( parent || query_property("unique") ||
        query_hp() < query_max_hp() || check_env() )
        return 1;
    move("/room/rubbish");
    return 1;
} /* clean_up() */

/**
 * This method sets the value of the unable to change position flag.
 * This flag will be checked by the soul, and by anything else which
 * deliberatly changes someones position.
 * @param flag the unable to change position flag
 * @see /std/living/living->set_default_position()
 */
void set_cannot_change_position( int flag ) {
    cannot_change_position = flag;
} /* set_cannot_change_position() */

/**
 * This method returns the current value of the unable to change
 * position flag.
 * @return the unable to change position flag
 * @see /std/living/living->set_default_position()
 */
int query_cannot_change_position() {
    return cannot_change_position;
} /* query_cannot_change_position() */

/**
 * This method overrides the position code, so that if the position
 * is changed we are changed back to the default position.
 * @param new_pos the new position
 * @ignore yes
 */
void set_position( string new_pos ) {
    if( always_return_to_default_position && !query_property(PASSED_OUT) &&
        new_pos != query_position() && TP != TO )
        call_out( (: return_to_default_position(0) :),
                     always_return_to_default_position );
    ::set_position( new_pos );
} /* set_position() */

/**
 * This method sets the status of the flag that makes the NPC return
 * to the default position if its position is changed.  The flag
 * specifies the length of time to wait before causing the
 * default position to be restored.
 * @param tim the time to wait before the position is restored
 * @see /std/living/living->return_to_default_position()
 * @see query_always_return_to_default_position()
 */
void set_always_return_to_default_position( int tim ) {
    always_return_to_default_position = tim;
} /* set_always_return_to_default_position() */

/**
 * This method returns the status of the flag that makes the NPC return
 * to the default position if its position is changed.  The flag
 * specifies the length of time to wait before causing the
 * default position to be restored.
 * @return the time to wait before the position is restored
 * @see /std/living/living->return_to_default_position()
 * @see set_always_return_to_default_position()
 */
int query_always_return_to_default_position() {
    return always_return_to_default_position;
} /* query_always_return_to_default_position() */

/**
 * This method can be used to turn the chats of the NPC on/off.
 * @param i 1 to turn the chats off, 0 to turn them on
 */
void set_chats_off( int i ) { chats_off = i; }

/**
 * This method queries whether or not the chats of the NPC
 * have been turned off.
 * @return 1 if the chats are off, 0 if chats are on
 */
int query_chats_off() { return chats_off; }

/**
 * This method loads up the chat strings for the NPC.  This will be
 * an array containing pairs of elements, the first pair is the
 * weighting of the chat and the second is the chat to use.
 * <p>
 * All the weights in the array are added up and then a random
 * number is chosen in the weighting.  Then that element is looked
 * up in the array.  This way you can control a chat and make it
 * rare.
 * <p>
 * If the chat string is an array then this a story, the story will be
 * executed one after another and no other chats will be executed
 * in between.  The chat chance is still used to determine when the
 * chats will occur. Special strings can be used which will replace with
 * object names, see expand_mon_string() for further information.
 * <p>
 * The chat chance is a chance (in 1000) of the chat occuring.  You
 * will need to play with this yourself to see which frequency of
 * chatting you wish for your NPC.
 * @example
 * load_chat(60,
 *    ({
 *       1, "'I am a chicken!",
 *       // Make this one more likely to occur.
 *       2, ":clucks like a chicken."
 *       1, ":pecks at $lpossshort$ foot."
 *     }));
 * @example
 * load_chat(100,
 *    ({
 *       1, "'I am a simple farmer."
 *       1, ":waves $mposs$ pitchfork around."
 *       // A story, they will always occur in this order
 *       1, ({
 *            "'Once upon a time there was a rabbit.",
 *            "'It was a nice rabbit and hung around in bars.",
 *            "'It sung little songs about fruit.",
 *           }),
 *      }));
 * @see expand_mon_string()
 * @param chance the chance in 1000 of a chat working every 2 seconds
 * @param data the chats and chat weights to use
 * @see load_a_chat()
 * @see query_chat_chance()
 * @see query_chat_string()
 */
void load_chat( int chance, mixed data ) {
    int i;

    if( chance < 1 )
        error("Invalid chat chance in load_chat().\n");

    if( !i = sizeof(data) / 2 )
        error("Invalid chats in load_chat().\n");

    chats = new( class chat_data, chance : chance,
            chats  : allocate( i ), weight : allocate( i ) );

    while( i-- ) {
        chats->total_w += data[i*2];
        chats->weight[i] = data[i*2];
        chats->chats[i] = data[i*2+1];
    }

} /* load_chat() */

/**
 * This method sets the current chat chance for the NPC.
 * @param i the chat chance
 * @see load_chat()
 * @see query_chat_chance()
 */
void set_chat_chance( int i ) {
    if( !chats )
        error("Trying to specify a chat chance with no chats.\n");
    chats->chance = i;
} /* set_chat_chance() */

/**
 * This method returns the current chat chance for the NPC.
 * @return the current chat chance
 * @see set_chat_chance()
 * @see load_chat()
 */
int query_chat_chance() {
    if( chats )
        return chats->chance;
    return 0;
} /* query_chat_chance() */

/**
 * This method queries the current chat strings for the NPC.
 * The return value is an array of three elements where the
 * first member is the sum of the weights of all chats, the
 * second member contains the weights of chats, and the third
 * member contains the chat strings.
 * chat string is formatted.
 * @return the current chat strings
 * @see load_chat()
 * @see query_chat_string()
 */
mixed query_chat_string() {
    if( chats )
        return ({ chats->total_w, chats->weight, chats->chats });
    return 0;
} /* query_chat_string() */

/**
 * This method adds an array of chats or a single chat string into
 * the current list of chat strings.  See load_chat() for a longer
 * description of the chat string.
 * @param weight the weight of the chat, or an array of weights and chats
 * @param chat the new chat string
 * @see load_chat()
 * @see remove_chat_string()
 */
varargs void add_chat_string( mixed weight, mixed chat ) {
    int i;

    if( !chats )
        error("Cannot use add_chat_string() on an NPC with no chats.\n");

    if( pointerp( weight ) ) {
        i = sizeof( weight ) / 2;
        while( i-- )
            add_chat_string( weight[i*2], weight[i*2+1] );
    } else if( member_array( chat, chats->chats ) == -1 ) {
        chats->total_w += weight;
        chats->weight += ({ weight });
        chats->chats += ({ chat });
    }

} /* add_chat_string() */

/**
 * This method attempts to remove the given chat string from the
 * current list of chat strings.  The chat message is checked
 * to see if it exists in the array, the weighting of the
 * string is ignored.
 * @param chat the chat string to remove
 * @see add_chat_string()
 * @see load_chat()
 */
void remove_chat_string( mixed chat ) {
    int i;

    if( !chats )
        return;

    if( pointerp( chat ) ) {
        map( chat, (: remove_chat_string($1) :) );
    } else if( ( i = member_array( chat, chats->chats ) ) != -1 ) {
        chats->total_w -= chats->weight[i];
        chats->weight = delete( chats->weight, i, 1 );
        chats->chats = delete( chats->chats, i, 1 );
    }

}  /* remove_chat_string() */

/**
 * This method loads up the set of chat strings to use while in combat.
 * @param chance the chance of the chat occuring
 * @param data the chat strings and weights to use
 * @see load_chat()
 * @see query_achat_chance()
 * @see query_achat_string()
 */
void load_a_chat( int chance, mixed data ) {
    int i;

    if( chance < 1 )
        error("Invalid chat chance in load_a_chat().\n");

    if( !i = sizeof(data) / 2 )
        error("Invalid chats in load_a_chat().\n");

    a_chats = new( class chat_data, chance : chance,
              chats  : allocate( i ), weight : allocate( i ) );

    while( i-- ) {
        a_chats->total_w += data[i*2];
        a_chats->weight[i] = data[i*2];
        a_chats->chats[i] = data[i*2+1];
    }

} /* load_a_chat() */

/**
 * This method sets the current aggressive chat chance the NPC.
 * @param i the attack message chat chance
 * @see load_chat()
 * @see load_achat()
 * @see query_achat_chance()
 */
void set_achat_chance( int i ) {
    if( !a_chats )
        error("Trying to specify an achat chance with no achats.\n");
    a_chats->chance = i;
} /* set_achat_chance() */

/**
 * This method returns the current attack chat chance for the NPC.
 * @return the current chat chance
 * @see set_achat_chance()
 * @see load_chat()
 * @see load_a_chat()
 */
int query_achat_chance() {
    if( a_chats )
        return a_chats->chance;
    return 0;
} /* query_achat_chance() */

/**
 * This method queries the current aggressive chat strings for the NPC.
 * The return value is an array of three elements where the
 * first member is the sum of the weights of all chats, the
 * second member contains the weights of chats, and the third
 * member contains the chat strings.
 * chat string is formatted.
 * @return the current chat strings
 * @see load_chat()
 * @see query_chat_string()
 */
mixed query_achat_string() {
    if( a_chats )
        return ({ a_chats->total_w, a_chats->weight, a_chats->chats });
    return 0;
} /* query_achat_string() */

/**
 * This method adds an array of attack chats or a single attack chat
 * string into the current list of achat strings.
 * See load_chat() for a longer description of the chat string.
 * @param weight the weight of the chat, or an array of weights and chats
 * @param chat the new chat string
 * @see load_chat()
 * @see load_a_chat()
 * @see remove_achat_string()
 */
varargs void add_achat_string( mixed weight, mixed chat ) {
    int i;

    if( !a_chats )
        error("Cannot use add_achat_string() on an NPC with no achats.\n");

    if( pointerp( weight ) ) {
        i = sizeof( weight ) / 2;
        while( i-- )
            add_achat_string( weight[i*2], weight[i*2+1] );
    } else if( member_array( chat, a_chats->chats ) == -1 ) {
        a_chats->total_w += weight;
        a_chats->weight += ({ weight });
        a_chats->chats += ({ chat });
    }

} /* add_achat_string() */

/**
 * This method attempts to remove the given chat string from the
 * current list of attack message chat strings.  The chat message
 * is checked to see if it exists in the array, the weighting of
 * the string is ignored.
 * @param chat the chat string to remove
 * @see add_achat_string()
 * @see load_chat()
 * @see load_achat()
 */
void remove_achat_string( mixed chat ) {
    int i;

    if( !a_chats )
        return;

    if( pointerp( chat ) ) {
        map( chat, (: remove_achat_string($1) :) );
    } else if( ( i = member_array( chat, a_chats->chats ) ) != -1 ) {
        a_chats->total_w -= a_chats->weight[i];
        a_chats->weight = delete( a_chats->weight, i, 1 );
        a_chats->chats = delete( a_chats->chats, i, 1 );
    }

}  /* remove_achat_string() */

/**
 * This method adds a combat action to the NPC.  This is an action which
 * has a chance of occuring during combat.  The name is an identifier
 * which can be used to remove the action with later.  The action
 * itself can be a string, then that command will be executed.  If
 * the action is a function pointer then it will be evaluated with
 * two arguments, the first being the attacker, the second being the
 * target.
 * <p>
 * If the action is an array, if it is one element then the function
 * specified will be called on the attacked with the same arguments
 * as above.  If the size of the array is two then the function will
 * be called on the specified object with the arguments as above.
 * @see remove_combat_action()
 * @see query_combat_actions()
 * @see /std/effects/fighting/combat.c
 */
void add_combat_action( int chance, string name, mixed action ) {
    int i;

    if( !combat_actions )
        combat_actions = ({ 0 });

    if( ( i = member_array( name, combat_actions ) ) == -1 ) {
        combat_actions += ({ chance, name, action });
        combat_actions[ 0 ] += chance;
    } else {
        combat_actions[ 0 ] += chance - combat_actions[ i - 1 ];
        combat_actions[ i - 1 ] = chance;
        combat_actions[ i + 1 ] = action;
    }

} /* add_combat_action() */

/**
 * This method will remove the combat action with the specified name.
 * @return 1 if it is found and removed, 0 if not
 * @see add_combat_action()
 * @see query_combat_actions()
 * @see /std/effects/fighting/combat.c
 */
int remove_combat_action( string name ) {
    int i;

    if( !combat_actions )
        return 0;

    if( ( i = member_array( name, combat_actions ) ) == -1 )
        return 0;

    combat_actions[ 0 ] -= combat_actions[ i - 1 ];
    combat_actions = delete( combat_actions, i - 1, 3 );

    return 1;

} /* remove_combat_action() */

/**
 * This method returns the current array of combat actions on the
 * NPC.
 * <p>
 * The array will have the format of:
 * <pre>
 *    ({
 *        total_chance,
 *        action1_chance,
 *        action1_name,
 *        action1_action,
 *        ...
 *     })
 * </pre>
 * @return the combat action array
 * @see add_combat_action()
 * @see remove_combat_actions()
 * @see /std/effects/fighting/combat.c
 */
mixed query_combat_actions() { return copy( combat_actions ) || ({ 0 }); }

/**
 * This method returns the amount of death experiecne that would be
 * gained by killing the NPC.
 * @return the amount of death experience for the npc
 */
int query_death_xp() {
    int amount;

    if( TO->query_property("dead") || TO->query_property("unique") )
        return 0;

    if( death_xp_set )
        return death_xp;

    amount = (int)TOP_TEN_HANDLER->calculate_rating( TO );
    amount += (int)TO->query_max_hp();

    return amount / 4;

} /* query_death_xp() */

/**
 * This method sets the amount of death experience that would be
 * gained by killing the NPC.
 */
int set_death_xp( int amount ) {
    death_xp = amount;
    death_xp_set = 1;
} /* set_death_xp() */

/**
 * This event is called when a fight is in progress.  It will
 * be used for things like joining into currently running
 * fights and initiating combat with spell casters.
 * @param me the person initiating the attack
 * @param him the person being attacked
 */
void event_fight_in_progress( object me, object him ) {
    if( !me || ! him )
        return;

    if( him == TO && TO->query_property("see_caster") && !userp(me) &&
        random( him->query_property("see_caster") ) < him->query_int() )
        TO->attack_ob( me->query_owner() );

    if( !join_fight_mess || him == TO )
        return;

    // Only attack NPCs if join_fight_type is not 0.
    if( !join_fight_type && !interactive( him ) )
        return;

    if( member_array( him, (object *)TO->query_attacker_list() ) == -1 &&
        !him->query_property("no attack") ) {
        if( join_fight_mess )
            expand_mon_string( join_fight_mess );
        TO->attack_ob( him );
    }
} /* event_fight_in_progress() */

/** @ignore yes */
object *query_ignoring( object *people ) { return ({ }); }

/** @ignore yes */
object *query_ignored_by( object *people ) { return ({ }); }

/** @ignore yes */
mixed stats() {
    mixed zones;
    int i;

    zones = allocate( i = sizeof(zones) );

    while( i-- )
        zones[i] = ({"move zone "+i, move_zones[i] });

    if( !query_move_after() ) {
        return living::stats() + ({
          ({ "death xp", query_death_xp() }),
          ({ "aggressive", query_aggressive() }),
          ({ "join fights", query_join_fights() }),
          ({ "race", query_race() }),
          ({ "race ob", query_race_ob() }),
          ({ "level", query_level() }),
          ({ "follow speed", query_follow_speed() }),
          ({ "home", query_home_location() }),
          ({ "chats off", query_chats_off() }),
          ({ "chat chance", query_chat_chance() }),
          ({ "achat chance", query_achat_chance() }),
          ({ "route", ( sizeof( following_route ) ?
                        implode( following_route, ", ") :
                        "not going anywhere" ) }),
          }) + zones;
    }
    return living::stats() + ({
      ({ "death xp", query_death_xp() }),
      ({ "aggressive", query_aggressive() }),
      ({ "join fights", query_join_fights() }),
      ({ "race", query_race() }),
      ({ "race ob", query_race_ob() }),
      ({ "level", query_level() }),
      ({ "follow speed", query_follow_speed() }),
      ({ "home", query_home_location() }),
      ({ "chats off", query_chats_off() }),
      ({ "chat chance", query_chat_chance() }),
      ({ "achat chance", query_achat_chance() }),
      ({ "move after-fix", query_move_after()[0] }),
      ({ "move after-rand", query_move_after()[1] }),
      ({ "route", ( sizeof( following_route ) ?
                    implode( following_route, ", " ) :
                    "not following anyone") }),
      }) + zones;

} /* stats() */

#if !efun_defined(add_action)
/** @ignore yes */
protected string _process_input( string str ) {
    object ob;

    if( !str || str == "" )
        return 0;

    ob = TP;
    set_this_player(TO);
    _notify_fail(0);

    if( !sizeof( explode( str, " " ) - ({ "", 0 }) ) || str[0] == ',' ) {
        set_this_player(ob);
        return 0;
    }

    if( !TO->drunk_check(str) && !TO->exit_command(str) && !TO->cmdAll(str) &&
        !TO->new_parser(str) && !TO->lower_check(str) ) {
        set_this_player(ob);
        return 0;
    }

    set_this_player(ob);
    return "bing";

} /* _process_input() */
#endif