[go: up one dir, main page]

Menu

[897c37]: / mlib / RO / living.lpc  Maximize  Restore  History

Download this file

2365 lines (2099 with data), 62.0 kB

/*
 * Shattered Worlds Mudlib
 * Living object
 *
 * From a number of wizards.
 */

/*
 * 950617 Hunter      	added familyname functions. added query_death_hook()
 * 950824 Hunter	added "RO/hooks" to inherit tree
 * 951019 Hunter	added ping_new_soul() code for soul_init()'s
 * 990611 Jenna		let NPCs see where they're moving
 * 990721 Jenna		Allowed for multiple death-hooks
 * 990830 Jenna		Added protections, new hit_player() & damage()
 * 990904 Jenna		Added set_damage_type()
 * 000225 Jenna		Added my_name()
 * 000307 Jenna		Added set_feeble(int time), query_feeble()
 * 000311 Jenna		Added can_cut()
 * 000330 Jenna		Moved in brief stuff from secure/body
 * 000808 Jenna		Added 'has_hands' (references same fun in handle)
 * 001013 Jenna		Added 'receive' to stop ghosts getting things
 * 010330 Jenna		Failure to supply a damage type arg to hit_player()
 *			or damage() now errors out
 * 010821 Jenna		set_feeble now takes an arg for auo-message
 *			broadcasting
 * 010821 Jenna		sever_limb() now returns the object
 */

inherit "RO/Olight";		/* get rid of this shit... */
inherit "RO/ioprocess";
inherit "RO/Root/Loaded";	/* soul names, etc */
inherit "RO/living/stats";	/* new stats stuff. */
inherit "RO/store";			/* dredd's general store, split out */
inherit "RO/affiliation";	/* base memory for social affiliations */
inherit "RO/Servers";
inherit "RO/living_defs";
inherit "RO/hooks";					/* Hunter's Hook code */
inherit	"RO/persist/inh/mob_cont";	/* mobile persistent container */
inherit	"RO/living/points_store";	/* regenerative points store */
inherit "RO/parse/action_parser";
inherit "RO/modules";

#include <Config.h>
#include "/RO/gender.h"
#include "/RO/living_vars.h"

/*
 * If you are going to copy this file think about how quickly
 * you will lose your wizhood - DON'T DO IT.
 *
 * First try one of the following:
 *
 * 0. Use the monsterer for its specified purpose!
 *
 * 1. Do clone_object(), and then configure it. This object is specially
 *    prepared for configuration.
 *
 * 2. If you still is not pleased with that, create a new empty
 *    object, and make an inheritance of this object on the first line.
 *    This will automatically copy all variables and functions from the
 *    original object. Then, add the functions you want to change. The
 *    original function can still be accessed with '::' prepended on the
 *    name.
 *
 */


#include <security.h>

/*
 * The following routines are defined for usage:
 * stop_fight		Stop a fight.  Good for scroll of taming, etc.
 * hit_player		Called when fighting (unfortunetly - hook to fight).
 * move_player		Called by the object that moves the monster.
 * query_name		Gives the name to external objects.
 * query_real_name	Give the uncapitalized name, even if invisible.
 * my_name		Returns query_name() for players & short() for npcs.
 * attacked_by		Tells us who are attacking us.
 * show_stats		Dump local status. (hook to stat server)
 * stop_wielding	Called when we drop a weapon (stupid functions).
 * stop_wearing		Called when we drop an armour.
 * set_feeble		Enfeeble a creature (can't dodge attacks)
 * query_feeble		Return the number of seconds until unfeebled
 * query_hp		Query the number of hit points we have
 * query_max_hp		Query our maximum number of hit points.
 * query_level		Give our level to external objects (monsters mainly).
 * query_value		Always return 0. Can't sell this object.
 * query_npc		Return 1 if npc otherwise 0.
 * get			Always return 0. Can't take this object.
 * attack		Should be called from heart_beat. (hook to fight)
 * query_attack		Stupid name for query_attacker()
 * wield		Called by weapons (more stupid functions).
 * wear			Called by armour.
 * add_weight		Used when picking up things.
 * heal_self		Enable wizards to heal this object.
 * can_put_and_get	Can look at inventory, but not take things from it.
 * can_cut		Returns 1 if damage_type of hands or wielded ob=="cut"
 * attack_object	Called when starting to attack an object.
 * test_if_any_here	For monsters. Call this one if you suspect no enemy
 *			is here any more. This will be checked, and the
 *			heart beat turned off in that case.
 *			Return 1 if anyone there, 0 if none.
 * force_us		Force us to do a command (hmm).
 * query_sp		Return how many spell points the character has.
 * query_living	 returns 1 if the object is inherited from living
 * query_base_armour    Returns the basic armour modifer.
 * set_base_armour	  - typical uses are hard skin, rings of protection
 * query_armour	(locn)  Returns the amount of armour on that body location
 * query_all_armour     returns the armour array (doesnt show base_armour)
 * query_protections    Returns an array of registered protections 
 * absorb_damage	Returns the amt of damage that would be sent to damage()   
 * query_brief		Returns 1 if in brief mode
 */

query_living()		{ return 1; }
query_sp()		{ return sp; }
query_creation_date()	{ return creation_time; }

/* Silly to save livings as inventory */

query_no_external_save() { return 1; }
query_no_external_restore() { return 1; }

/* courtesy function */
reset() 
{ 
    "RO/parse/action_parser"::reset();
    return 1; 
}

query_destruct()
{
    array souls;
    int i;

    "RO/parse/action_parser"::query_destruct();
    LIVING_SERVER->remove_living(this_object(), query_real_name());
    "RO/Root/Loaded"::query_destruct();
}

receive(ob)
{
	if (query_ghost()) return 1;
}

add_bleeding(int am) 
{
    if ((bleeding += am) < 0) 
	bleeding = 0;
    if (bleeding > 0 && find_call_out ("auto_bleed") < 0) 
	call_out("auto_bleed", 2);
}

query_bleeding() { return bleeding; }

auto_bleed()
{
    if (query_ghost()) return;
    if (!bleeding)  return;
   	if (query_ghost()) return;
#ifdef TRACK_ACTION
    track_action("HEALTH", "bleed", "bleeding="+bleeding);
#endif
    damage("bleeding",bleeding);
		tell_object(this_object(), "You are bleeding");
    if (bleeding > 2) {
       tell_object(this_object(), " profusely.\n");
       checked_say(query_name() +"'s life blood gushes out over the ground.\n");
        }
     else {
       tell_object(this_object(), ".\n");
       checked_say(cap_name + " seems to be bleeding.\n");
       }
    if (!present("blood_pool", environment(this_object()))) 
    {
        object o;
        o = clone_object("obj/blood_pool");
        o->set_type(query_race());
        catch move_object(o, environment(this_object())) ;;
    }
    call_out("auto_bleed", 4);
}

/* Type of damage done by bare hands -- usually "impact" */
set_damage_type(string dtype) { dam_type = dtype; }
query_damage_type() { return dam_type; }

/* Return 1 if creature's weaponless damage type is "cut"
 * (which is the case with monsters with claws, etc), or
 * if it's currently wielded weapon can cut. For use with
 * dismember, skin, etc.    - Jenna Mar 2000
 */ 
can_cut() {
    array a;
    object w;
	
    if (dam_type == "cut") return 1;
    w = query_wielded();
    if (w) {
	a = w->query_damage_type();
	if (a)
	    return index(a, "cut") != -1;
	}
    return 0;
    }

has_hands()
{
    object h = query_soul("generic/handle");
    if (!h) h = present("handle", this_object());
    if (h) return h->has_hands();
}

has_head()
{
    int i;
 
    for (i = 0; i < sizeof(body_locations); i+=2)
	if (body_locations[i] == "head" ||
	    index(body_locations[i], " head") != -1)
		return 1;
   return 0;
}

/*
 * This function is called from other players when they want to make
 * damage to us. We return how much damage we received, which will
 * change the attackers score. This routine is probably called from
 * heart_beat() from another player. Takes into account armour.
 * The hit stuff should really be in another function *sigh* 
 * really an icky hack for backward compatability.- geoff
 */

/* a hit player stub for backward compat shit */
hit_player(string dtype, dam, locn, callb) {
    return (FIGHT_SERV->hit_player(this_object(), dtype, dam, locn, callb));
}

/*
 * doing damage to a player, don't want armour? call this function directly
 *
 * No, don't! See 'man lfun/damage' first  - Jenna 99
 */

damage(string dtype, int amount, locn, callb)
{
int temp;

    if (!valid_hit()) return 0;
    amount = amount * 2;
#ifdef TRACK_ACTION
    track_action("HEALTH", "damage-chk", "amount="+amount + "; locn="+locn);
#endif
    if (locn && intp(temp = damage_body(dtype, amount, locn, callb)) && temp >= 0)
	amount = temp;
    hit_point = hit_point - amount;
    if (hit_point<0) return this_object()->death();
    return amount;
}

/*
 *        Standard body location classes -
 *       '1'-'9' - relative size.
 *       '*' - critical location (die if double negative in this area).
 *       'm' - motor limb etc. (50% maim)
 *       'h' - handling limb (ie. arm) (100% main)
 *       's' - sight/feeler/sensitive appendage etc. (100% maim)
 *       'f' - flying limb.
 *
 *       format : size + anything.
 *
 * Critical Limb Hits: when a single hit does limb max_hp damage,
 * or reaches -max_hp hit points.
 *  "cut"        - severs limb
 *  "impact"     - stun
 *  "puncture"   - bleed, can cause instant death
 *
 * Should have a callback-object: when message-inducing hit is 
 * done (often a death-inducing hit), will call:
 *  callb->hit_result(pob, result);
 * allow messages to be sent in the right order, e.g.
 * > You slash Ogre viciously in the abdomen.
 * > Ogre's abdomen is laid open! His intestines spill onto the
 * > ground!
 * > Ogre died.
 */
damage_body(string dtype, int amount, locn, callb)
{
    int x;

    if (!body_locations) 
        return 0;
    x = index(body_locations, locn);
    if (x == -1) 
    {
        locn = random_location();
        x = index(body_locations, locn);
    }
    if (!body_hps)
        setup_hps();
    if (!intp(body_hps[x+1]))
        return 0;
    body_hps[x+1] -= amount;

#ifdef TRACK_ACTION
    track_action("HEALTH", "body-damage", "amount="+amount + "; locn="+locn);
#endif

    /* check classes and do disabilities here.. */
    if (dtype == "cut" &&
        (body_hps[x+1] <= -body_hps[x])) /* limb is out of juice; or */
#if 0
/* no crit hits - Jen Dec 99 */
         || amount >= body_hps[x]))     /* critical hit on limb */
#endif
    {        
        body_hps[x+1] = "severed";
        if (index(body_locations[x+1], '*') != -1)
        {
            if (callb)
                callb->hit_result(this_object(), dtype, amount, locn, "death");
            if (body_locations[x] == "head")
            {
                notify_hit("Your head is severed!\n",
                    cap_name+"'s head is severed!\n");
		sever_limb("head");
            }
            else
            {
                notify_hit("Your "+body_locations[x]+" is laid open! " +
                    "Your internal organs spill onto the ground!\n",
                    cap_name+"'s "+body_locations[x]+" is laid open! " +
                    capitalize(GENDER->pos(this_object())) + 
		    " internal organs spill onto the ground!\n");
		object guts = clone_object("obj/intestines");
		move_object(guts, environment(this_object()));
            }
            return this_object()->death();
        }
        else
	    sever_limb(body_locations[x]);	
    }
    else if (dtype == "puncture")
    {
        int how_bad = (amount * 50) / body_hps[x];
        int r = random(100) + 1;

        if (body_hps[x+1] <= -body_hps[x]) /* limb is out of juice; or */
#if 0
            || r <= how_bad)              /* critical hit on limb */
#endif
        {
            body_hps[x+1] = "speared";
            if (index(body_locations[x+1], '*') != -1)
            {
                if (callb)
                    callb->hit_result(this_object(), dtype, amount, locn, "death");
                notify_hit("Your " + body_locations[x]+" is speared! "+
                    "A terrible pain fills you...\n",
                    cap_name+"'s "+body_locations[x]+" is speared! A strange "+
                    "expression crosses " + GENDER->pos(this_object()) + 
		    " face...\n");
                return this_object()->death();
            }
            add_bleeding(MAIM_BLOOD);  /* Should be about 2-3 */
            do_disabilities();
        }
        else if (how_bad / 25 > 0)
            add_bleeding(how_bad/25);
    }
    else if (dtype == "impact")
    {
        int how_bad = (amount * 50) / body_hps[x];
        int r = random(100) + 1;

        if (body_hps[x+1] <= -body_hps[x]) /* limb is out of juice; or */
#if 0
            || amount >= body_hps[x])     /* critical hit on limb */ 
#endif
        {
            body_hps[x+1] = "crushed";
            if (index(body_locations[x+1], '*') != -1)
            {
                if (callb)
                    callb->hit_result(this_object(), dtype, amount, locn, "death");
                notify_hit("Your " + body_locations[x]+" is crushed!\n",
                    cap_name+"'s "+body_locations[x]+" is crushed!\n");
                return this_object()->death();
            }
            add_bleeding(CRUSHED_BLOOD);
            do_disabilities();
            }
        else if (r <= how_bad)
        {
            if (index(body_locations[x+1], 's') != -1)
            {
                if (callb)
                    callb->hit_result(this_object(), dtype, amount, locn, "stunned");
		notify_hit("You feel stunned and unable to focus!\n",
                    cap_name+" looks stunned and confused.\n");
		this_object()->set_feeble((how_bad/5)+1, 1);
//		call_out("broadcast_recovery", (how_bad/5)+1.1);
            }
            else
            {
            /* something else? drop weapon if 'h'?*/
            }
        }
    }
    else if (dtype == "sonic" && body_hps[x+1] <= -body_hps[x])
	body_hps[x+1] = "imploded";

    /* record damage type now - J */
    record_new_damage(dtype, amount, locn);

    if (stringp(body_hps[x+1]))
    {
	int res;

        if (callb)
            res = callb->hit_result(this_object(), dtype, amount, locn, body_hps[x+1]);

	if (!res)
	  notify_hit("Your "+body_locations[x]+" is "+
	    body_hps[x+1]+"!\n",
            cap_name+"'s "+body_locations[x]+" is "+
	    body_hps[x+1]+"!\n");
    }
    return amount;
}

// Sends messages to FIGHT_SERV for display; done this way
// primarily so the appropriate colour tags can be attached
// Jenna Feb 2001
notify_hit(msg, msg2)
{
    object att_who = this_object()->query_attack();

    checked_say(msg2, att_who);
    FIGHT_SERV->combat_msg(this_object(), msg);
    if (att_who)
	FIGHT_SERV->combat_msg(att_who, msg2);
}

broadcast_recovery() 
{
    if (query_feeble() || query_ghost()) return;
    tell_object(this_object(), "You recover from the stunning blow.\n");
    checked_say(cap_name+" seems to regain focus.\n");
}

sever_limb(string loc) 
{
	int x = index(body_locations, loc);
	object limb;
	string tmpname;

	if (x == -1) return 0;

#ifdef TRACK_ACTION
    track_action("HEALTH", "limb-sever", "locn="+loc);
#endif

    tmpname = this_object()->short();
    if (this_object()->is_player() || !tmpname || sizeof(tmpname) > 12)
                tmpname = capitalize(name);
    add_bleeding (SEVER_BLOOD);
    do_disabilities();
    limb = clone_object("obj/corpse");
    limb->set_corpse_name(tmpname);
    limb->set_was_player(!is_npc);  /* for butcher, bounty */
    catch limb->set_body(loc);;
    move_object(limb, environment(this_object())); 
	return limb; 
}     

/* body_damage stores the amount of each type of damage
 * currently received. Rather than storing absolute amounts,
 * it stores a percentage of the current damage that is due
 * to each type. Thus it doesn't require updating every time
 * the creature's hit points changes; instead, each time new
 * damage is received, the old percentages are downgraded
 * accordingly, until they reach zero and are removed. - Jenna
 */
record_new_damage(dtype, amt, locn) {
    int x, perc, i, flag, j;
    array new;

    if (!pointerp(body_damage))
	body_damage = allocate(sizeof(body_locations));
    x = index(body_locations, locn);
    if (x == -1)
	return;
    if (body_hps[x+1] == body_hps[x]
	|| stringp(body_hps[x+1]))
	perc = 100;
    else
        perc = (100 * amt) / (body_hps[x] - body_hps[x+1]);
    if (!body_damage[x] || perc == 100) {
	body_damage[x] = [ dtype, 100 ];
	return;
	}
    new = allocate(sizeof(body_damage[x]));
    for (i = 0, j = 0, flag = 0; i < sizeof(body_damage[x]); i+=2) {
	new[j] = body_damage[x][i];
	new[j+1] = (body_damage[x][i+1] * (100-perc)) / 100;
	if (new[j] == dtype) {
		new[j+1] += perc;
		flag = 1;
		}
	if (new[j+1] > 0)
		j += 2;
	}
    if (!flag) {
	new += [ dtype, perc ];
	j += 2;
	}
    body_damage[x] = new[..(j-1)];
    }

query_body_damage(locn, dtype) {
    int x, i, tmp, limbdam;
    array ret = [ ];

    if (!pointerp(body_damage))
        body_damage = allocate(sizeof(body_locations));
    if (!locn)
	return body_damage;
    x = index(body_locations, locn);
    if (x == -1)
	return 0;
    if (!body_damage[x])
	return 0;
    limbdam = body_hps[x] - body_hps[x+1];
    if (dtype) {
	i = index(dtype, body_damage[x]);
	if (i == -1)
		return 0;
	return (body_damage[x][i+1] * limbdam) / 100;
	}
    for (i = 0; i < sizeof(body_damage[x]); i+=2) {
	tmp = (body_damage[x][i+1] * limbdam) / 100;
	if (tmp > 0)
		ret += [ body_damage[x][i], tmp ];
	}
    return ret;
    }

/*
  	Defined disabilities so far -
	move, fly, handle, sense.

	Goes through body array and check to see if 
	more than half of a type is damaged then add disability
	(or remove).

	if >= 50% of motor limbs maimed; no movement.
	if 100% of perception limbs maimed; no perception.
	if 100% of handling limbs maimed; no handling.
	(no of actions should depend on number of handling limbs).
*/
do_disabilities()
{
int m_maim, m_num, h_maim, h_num, p_maim, p_num, f_num, f_main;
int i,z, flag;
	if (!body_locations) return;
	disable = [ ];
	z = sizeof(body_locations);
	for (i = 1; i < z; i+=2) {
		flag = stringp(body_hps[i]);
		if (index(body_locations[i], 'm') != -1) {
			m_num++;
			if (flag) m_maim++;
		}
		if (index(body_locations[i], 's') != -1) {
			p_num++;
			if (flag) p_maim++;
		}
		if (index(body_locations[i], 'h') != -1) {
			h_num++;
			if (flag) h_maim++;
		}
		if (index(body_locations[i], 'f') != -1) {
		  f_num++;
		  if (flag) f_main ++;
		}
	}
	if ( ((m_maim-1) * 2) >= (m_num / 2) || !m_num ) disable += [ "motor" ];
	if ( h_maim == h_num ) disable += [ "handle" ];
	if ( p_maim == p_num ) disable += [ "perception" ];
	if ( f_main == f_num ) disable += [ "flight" ];
	return disable;
}

setup_hps()
{
    int i, x, z;
    string bl;

	if (!body_locations) return;

	body_hps = allocate(sizeof(body_locations));
	z = sizeof(body_locations);
	for (i = 0; i < z; i+=2) 
    {
		bl = body_locations[i+1];
		x = 0;	
		if (stringp(bl)) x = atoi(bl);
		else x = 1; /* bl; */
		x = x*(max_hp() / 10 +1);
			/* probably need a better fn than this */
		body_hps[i] = x; /* max hp of location */
		if (!max_hp) {
		    max_hp = hit_point;
#if 0
		    LOG->log("MONSTERS", "No max HP: " + 
			file_name(this_object()) + ", " + short() + "\n");
#endif
		    }
		body_hps[i + 1] = x*hit_point/max_hp(); /* current hp of loc */
	}
#ifdef TRACK_ACTION
    track_action("HEALTH", "set-hp", "hp="+hit_point);
#endif
}

recalc_hps()
{
    int i, x, z;
    
    if (!body_hps) return setup_hps();
    z = sizeof(body_locations);
    for (i = 0; i < z; i+=2)
    {
	    x = atoi(body_locations[i+1]);
        // Going to use max_hp, instead of max_hp() because max_hp() calls 
        // this fn and is the only thing that calls this fn 
	    x = x*(max_hp/10 + 1);

        // Shit this is FUCKED. Well, lets hack *grumble* 
        // it cant handle all the severed stuff 
        if (!stringp(body_hps[i+1])) 
        {
            if (body_hps[i])
                body_hps[i + 1] = x*body_hps[i + 1]/body_hps[i];
            else
                body_hps[i + 1] = x;
	    }
	    body_hps[i] = x;
    }
}

random_location()
{
int i, total, sz, size;
	if (!body_locations) return -1;
	if (!body_hps) setup_hps();
	total = 0;
	
	for (i = 1; i < sizeof(body_locations); i += 2)
	    if (intp(body_hps[i]))
		if (sz = atoi(body_locations[i]))
		    total+= sz;
	if (!total) return 0;
	total = random(total);
	for (i = 1; i < sizeof(body_locations); i += 2)
	{
	    if (intp(body_hps[i]))
		if (sz = atoi(body_locations[i]))
		    total -= sz;
	    if (total <= 0)
		return body_locations[i - 1];
	}
}

query_body_location(loc)
{
    if (!body_locations || !loc) return 0;
    return (index(body_locations, loc) >= 0);
}

set_death_hook(ob, fn) 
{
    int x;

    if (!ob) 
	return;
    if (!fn) 
	fn = "death";
    if (!death_hook)
	death_hook = [ ];
    query_death_hook(); // to weed out old dested hooks - Jenna Mar 01
    x = index(death_hook, ob);
    if (x != -1)
	remove_death_hook(ob);
    death_hook += [ ob, fn ];
    return 1;
}

/* from ratty - G */
int remove_death_hook(object ob)
{
    int x;

    if (!death_hook)
	death_hook = [ ];
    x = index(death_hook, ob);
    if (x == -1)
	return;
    death_hook = ARRAY->delete(death_hook, x, 1);
    return 1;
}

query_death_hook()
{
    int i;

    if (arrayp(death_hook))
	for (i = sizeof(death_hook)-2; i >= 0; i-=2)
	    if (!death_hook[i])
		death_hook = death_hook[..(i-1)] + death_hook[(i+2)..];
    return death_hook;
}

death()
{
        int i, rtmp = 0;

        if (death_hook && sizeof(death_hook) > 1)
            for (i = sizeof(death_hook)-2; i >= 0; i -= 2)
                if (death_hook[i])
                    catch { rtmp += death_hook[i]->(death_hook[i+1])(); } ;

        if (rtmp) return;

        death_no_hook();
}

death_no_hook() 
{
object o, cor;
int x;
    if (!(this_object() ->creature_immortal()))
    { /* this creature is killable so die */
        /* We died ! */
#ifdef TRACK_ACTION
	if (attacker_ob) 
    {
	    track_action("HEALTH", "death",
			"age="+query_age()
			+ "; killer=" + file_name(attacker_ob)
			+ "; killer-name=" + attacker_ob->query_name()
			+ "; killer-class=" + attacker_ob->query_npc());
	}
	else
	    track_action("HEALTH", "death", "age="+query_age());
#endif

        cor = make_corpse();
        make_ghost();
/*        if (!is_npc)  */ /* Jenna April 200 */
	    death_adjust();
        if (allies) 
	    stop_being_ally();
        if (o = query_soul("generic/support")) o->stop_follow (0, 1);  
        if (attacker_ob) attacker_ob->notify("DEATH", this_object());
    /* If this is a player, log the death for future reference */
        checked_say(cap_name + " died.\n");
        if (!is_npc)
        {
            if (attacker_ob)
            {
                string aname = attacker_ob->query_real_name();
		array o = attacker_ob->query_owner();

		if (aname)
		    aname = capitalize(aname);
		else
		    aname = "unknown";	
                if (o) {
		    if (stringp(o[0]))
	                aname += " (under control of "+capitalize(o[0]) + ")";
		    else if (objectp(o[0]))
			aname += " (under control of "+capitalize(o[0]->query_name()) +")";
		    }
                LOG->log("DEATHS", cap_name + ": " + query_level()
                    + " killed by " + aname + " in " + current_room + ".\n");
                if (!attacker_ob->query_NPC()) /* player or controlled monster */
                {
                    if (environment( this_object() )->query_property("city"))
                        LOG->log("CRIME", cap_name + " killed in "
                        + environment(this_object())->query_short_desc() +
                        " by " + aname + "\n");
             /* "std/player_death"->add_death(this_object(), attacker_ob); */
                }
            }
            else
                LOG->log("DEATHS", cap_name + ": "+ query_level()
                    + " - killer unknown in "+current_room+".\n");
        }
	if (!this_object()->second_life(0)) 
	{
/* monsters have souls too!  All other objects were xferred to corpse. */
	array conts; int s;
	    conts = contents(this_object());
            s = sizeof(conts);
            for (x=0; x<s; x++)
            if (conts[x]) 
		destruct(conts[x]);
            move_object(this_object(), "RO/void");
            destruct(this_object());
            return;
        }
        repair_limb();
        hit_point = 0;
        heal_self(max_hp());
        if (!is_npc && name != "guest") 
	    this_object()->save_me();
    }
}

uncon() 
{
    object un;

	if (this_object() ->creature_immortal()) return;
	un = clone_object("players/dredd/uncon/grey");
/* make an unconcsious body and move it the current location */
	move_object(this_object(), un);	
#ifdef TRACK_ACTION
	track_action("HEALTH", "unconsciousness", "");
#endif
	return 1;
}

set_gender(g) {
/*    if (gender == "male" || gender == "female") */
	gender = g;
}

query_gender() { return gender; }

make_corpse()
{
    object corpse, env;
    string tmpname; /* Jenna Nov 99 */

	tmpname = this_object()->short();
	if (this_object()->is_player() || !tmpname || sizeof(tmpname) > 12)
	    tmpname = capitalize(name);		

	corpse = clone_object("obj/corpse");
	corpse->set_corpse_name(tmpname);
#if 1
	if (this_object()->query_alias())
		  corpse->add_id("corpse of "+this_object()->query_alias());
	corpse->set_size(query_siz());
	catch corpse->set_body(body_locations + [], body_hps + []);;
	catch MISC_LIB->transfer_all(this_object(), corpse);;
#endif
	corpse->set_was_player(!is_npc);	/* for butcher, bounty */
    env = environment(this_object());
    if (env) move_object(corpse, environment(this_object()));
	return corpse;
}

static death_adjust() 
{
    string filen;
    int i;

	add_bleeding(-query_bleeding());
	stat_death_adjust();	/* players & npc lose skills on death */
	add_pow(-1);
	return 1;
}

make_ghost()
{
    ghost = 1;
    msgin = "drifts around";
    msgout = "blows";
    set_light(1);
    if (!is_npc) this_object()->soul("on");
    return 1;
}

valid_hit()
{
    if (query_wiz() && !query_npc()) return 0; /* You can't hit a wizard */
    if (ghost) return 0;		/* or someone who is dead... */

    /* or someone who is link-dead... */
    if (this_object() ->is_player() 
        && !query_ip_number(this_object())
	&& this_object()->query_idle() > 15) // Jenna Nov 00
		return 0;  			
    return 1;
}

query_name()
{
string fn = this_object()->query_family_name();

// Commented out to avoid "ghost of some mist" - Jenna Oct 00
//    if (ghost)
//	return NAME_OF_GHOST;
    if (this_object()->query_store("test_fn"))
    {
	if (stringp(fn))
	    return cap_name + " " + fn;
    }
    return cap_name;
}

query_real_name() { return name; }

my_name() { return query_npc() ? this_object()->short() : query_name(); }

(int|string)
query_family_name()
{
    return this_object()->query_store("familyname");
}

set_family_name( (int|string) nme )
{
    return this_object()->add_store("familyname", nme);
}


query_npc() { 
	if (receive_host) return 0;
	return is_npc; 
}

/*
 * This routine is called when we are attacked by a player.
 */

set_attack(ob) 
{
    if (ob != this_object())
	attacker_ob = ob;
}

/* New attacked_by() with auto-ally stuff broken out 
 * by Jenna, June 2000
 */
attacked_by(ob, recur) {
    object tmpo = attacker_ob;

    if (ob && !ob->query_ghost() && ob != this_object())
	set_attack(ob);
    else
	return 0;
    if (attacker_ob != tmpo && !recur) 
    {
        add_hunt(tmpo);
	if (allies)
	    make_allies_attack(ob);
	else if (this_object()->query_NPC() 
		 && find_call_out("find_allies") == -1)
	    call_out("find_allies", 2);
    }
#ifdef TRACK_ACTION
    track_action("FIGHT", "attacked-by",
        "attacker-obj="+file_name(attacker_ob) +
        "; attacker-name="+attacker_ob->query_real_name() +
        "; attacker-class="+attacker_ob->query_npc());
#endif
    this_object()->start_attack_cycle();
    return attacker_ob;
}

find_allies()
{
    int i;
    array ret = [];
    array c;
    string r = query_race();

    if (!environment(this_object()))
	return;
    c = contents(environment(this_object()));
    if (!attacker_ob)
	return 0;
    for (i = 0; i < sizeof(c); i++)
        if (c[i] != this_object() 
	    && c[i] != attacker_ob
	    && c[i]->query_race() == r
	    && c[i]->query_NPC())
	            ret += [ c[i] ];
    if (sizeof(ret))
    {
        add_team(ret);
	call_out("make_allies_attack",1, attacker_ob);
        return 1;
    }
}

make_allies_attack(ob)
{
    int ally;
    int delete_me = -1;

    if (!allies)
	return;
    for (ally = 0; ally < sizeof(allies); ally++) 
    {
        if (objectp(allies[ally]))
              allies[ally]->attacked_by(ob,1); /* 1 prevents loop */
        else 
	      call_out("clean_allies", 1);
    }
}

clean_allies()
{
    int ally;
    int delete_me = -1;

    while(find_call_out("clean_allies") != -1)
	remove_call_out("clean_allies");
    if (!allies)
	return;
    for (ally = 0; ally < sizeof(allies); ally ++)
	if (!objectp(allies[ally]))
	    delete_me = ally;
    if (delete_me != -1)
    {
	allies = allies[..delete_me-1] + allies[delete_me+1..];
	call_out("clean_allies", 1);
    }
}

/* return the wielded weapon */
query_wielded() { return name_of_weapon; }

stop_wielding() {
int i;
array default_weapon,inv;

    if (!name_of_weapon) return;
#ifdef TRACK_ACTION
    track_action("APPAREL", "wield-stop", "name="+name_of_weapon->query_name());
#endif
    name_of_weapon->un_wield();
    if (name_of_weapon->is_protection()
	&& name_of_weapon->query_invoke() == "wield")
		name_of_weapon->uninvoke();
    name_of_weapon = 0;
    inv = contents (this_object());
    for (i = sizeof (inv) ;i;) {
	i --;
	if (inv[i] -> id ("race_soul")) {
	  weapon_class = inv[i] -> query_weapon_class();
	  break ;
	}
    }
    if (!weapon_class) weapon_class = WEAPON_CLASS_OF_HANDS;
}

query_wc () { return weapon_class; }
set_wc (int arg) { weapon_class = arg;  }

stop_wearing(ob) 
{
    int x;
    array a,b;

    if (!sizeof(armour)) return;
    call_out("stat_check", 1); // Jenna Sep 00
    if (ob == "all") {
			armour = [];
#ifdef TRACK_ACTION
    		track_action("APPAREL", "wear-stop", "ALL");
#endif
			return; /* quick hack - Dredd */
    }
    armour = ARRAY->remove(armour, ob);
    if (ob->is_protection() 
	&& ob->query_invoke() == "worn")
		ob->uninvoke();
#if 0
    checked_say(cap_name + " removes " + ob->query_name() + ".\n");
   	track_action("APPAREL", "wear-stop", "name="+ob->query_name());
    tell_object(this_object(),"Ok.\n");
#endif
}

// When removing items, check if this will cause stat-death.
// Jenna 000826, after introduction of magic rings & amulets
stat_check() { this_object()->stat_zero_check(-1); }

query_prev_room() { return prev_room; }

query_level() { return level; /*(str+con+san+iq+dex+pow+cha)/7;*/ }
query_wiz() {
    if (wiz_level < GOD)
        return wiz_level;
    else
	return wiz_level + random(wiz_level * 100);
}
query_action() { return action; }
query_reaction() { return reaction;}

max_hp()
{
    int ret;
    
    ret = 20+query_siz()+query_str()*2+query_con()*6;
    if (ret != max_hp && !query_npc())
    {
	if (!max_hp)
	    max_hp = ret;
    	hit_point = hit_point*ret/max_hp;
    	if (hit_point > ret) hit_point = ret;
    	max_hp = ret;
    	recalc_hps();
    }
#ifdef TRACK_ACTION
    	track_action("HEALTH", "max-hp", "max-hp="+max_hp);
#endif
    return ret;
}

max_sp() { return 20+query_pow()*8+query_int(); }

query_value() { return 0; }/* This object is not worth anything in the shop ! */

get() { return 0; } /* It is never possible to pick up a player ! */

set_action(str, silent) { action = action_value(str);
		  if (action > 3) action = 0;
		  if (!silent)
		      tell_object(this_object(), "Action set to "+action_type(action)+".\n");
		return 1;
}

set_reaction(str, silent) { reaction = action_value(str);
		  if (reaction > 4) reaction = 0;
		  if ((query_dex() < 15 || query_skill("defence") < 90)
			&& (reaction == 4)) reaction = 0;
		  if (!silent)
		      tell_object(this_object(), "Reaction set to "+action_type(reaction)+".\n");
		return 1;
}

action_value(str) {
	if (str == "attack") return 0;
	if (str == "parry") return  1;
	if (str == "dodge") return  2;
	if (str == "riposte") return  3;
}

query_action_name() { return action_type(action); }
query_reaction_name() { return action_type(reaction); }

action_type(act)
{
	if (act == 0) return "attack";
	if (act == 1) return "parry";
	if (act == 2) return "dodge";
	if (act == 3) return "riposte";
	return "attack";
}

set_feeble(int length, msgs) 
{
    if (query_feeble())
	end_feeble();
    if (length < 1) return;
    this_object()->add_store("feeble", 1);
    call_out("end_feeble", length, msgs);
    return 1;
}

query_feeble() 
{
    if (find_call_out("end_feeble") == -1) 
    {
	if (query_store("feeble"))
	    this_object()->remove_store("feeble");
	return 0;
    }
    if (!query_store("feeble"))
	this_object()->add_store("feeble", 1);
    return 1;
 }

end_feeble(msgs) 
{
    if (query_feeble()) {
	remove_call_out("end_feeble");
	}
    this_object()->remove_store("feeble");
    if (msgs)
	broadcast_recovery();
}

query_attack() { return attacker_ob; }

/* Wield a weapon. */
wield(w) 
{
    if (disable && pointerp(disable) && index(disable, "handle") > -1) {
	tell_object(this_object(), "You're too disabled to wield a weapon!\n");
	return 1;
	}
    if (name_of_weapon) stop_wielding();
    name_of_weapon = w;
    weapon_class = w ->weapon_class();
    if (w->is_protection()
	&& w->query_invoke() == "wield")
		w->invoke();
    checked_say(cap_name + " wields " + w ->query_name() + ".\n");
#ifdef TRACK_ACTION
    	track_action("APPAREL", "wield", w->query_name());
#endif
    tell_object(this_object(),"Ok.\n");
}

/* Wear some armour. */
wear(object a) 
{
    object old;

    if (!a) return 0;
#if 0
    if (sizeof(armour)) 
    {
			old = test_type(a);
			if (old) return old;
    }
#endif
    armour += [ a ];
    if (a->is_protection()
	&& a->query_invoke() == "worn")
		a->invoke();
#if 0
    checked_say(cap_name + " wears " + a ->query_name() + ".\n");
    track_action("APPAREL", "wear", a->query_name());
    tell_object(this_object(), "Ok.\n");
#endif
    call_out("stat_check", 1); // Jenna Sep 00
    return 0;
}

/* test to see if we have armour of this type already */
test_type(a)
{
    int i;

	for (i = sizeof(armour) ; i; ) 
    {
		i --;
		if (armour[i] && (a->query_type() == armour[i] ->query_type())) 
			return (armour[i]);	
	}
	return 0;
}

/* 
	return the armour class of a specific body location.

	a temporary 1-1 hack will have to do for now :(
*/
query_armour(locn) 
{
int amt, i;

	if (!locn || index(body_locations,locn) <0) return 0;
	if (!sizeof(armour)) return base_armour;
	for (i = sizeof(armour); i; ) {
		i --;
		if (armour[i]) amt += armour[i] ->query_armour (locn);	
		}
	return (amt + base_armour);
}

query_all_armour() { return armour; }

add_base_armour ((int) arg) { base_armour += arg; }
query_base_armour () { return base_armour; }

add_protection(object obj) {
    if (index(prots, obj) != -1)
	return;
    prots += [ obj ];
    }

remove_protection(obj) {
    prots = ARRAY->remove(prots, obj);
    }

query_protections() { return prots; }

absorb_damage(dtype, amt, locn) {
    int i;
    int temp;

    for (i = 0; i < sizeof(prots); i++) {
	if (!objectp(prots[i]))
		temp = i;
	else
        	amt = prots[i]->absorb_damage(dtype, amt, locn);
	}
    /* Occasionally protections are dested without being removed
     * from the player's prot list. The only known case is when a 
     * player is resouled, and their protection souls are moved
     * to room/storage before being dested. Since the souls have
     * no living environment, they don't know who to remove themselves
     * from. It's simple enough to remove them here.
     */
    if (temp)
	prots = prots[..temp-1] + prots[temp+1..];
    return amt;
    }

add_weight(w) {
/*
    if (w + local_weight > level + 10 && (!query_wiz()))
	return 0;
*/
    if (!query_wiz() && w + local_weight > query_max_can_carry())
	return 0;
    local_weight += w;
    return 1;
}

query_weight_carried() { return local_weight; }

query_max_can_carry() {
    int i = (query_str() + query_siz() + query_con()) / 2;
    i += this_object()->query_extra_carry(); /* Rocs, etc */
    if (i < 1) i = 1;
    return i;
    }
 
repair_limb(locn)
{
    int i;

    if (!body_locations) return -1;
    if (!body_hps) setup_hps();
    if (!body_hps || !sizeof(body_hps)) return -1;
    i = 0;
    if (locn)
	if ((i = index(body_locations, locn)) < 0)
	{ i = 0; locn = 0; }
    do
    {
	if (!intp(body_hps[i+1]))
	    body_hps[i+1] = -body_hps[i];
	i += 2;
	if (i >= sizeof(body_hps)) locn = "finished";
    } while (!locn);
    do_disabilities();
}

heal_self(h, locn) {
    int i, sb;
    
    if (!h || h < 0)
	return;
    hit_point += h;
    if (query_bleeding() > 0) {
    	sb = h / bleeding;
	    add_bleeding (-sb);
    }
    if (hit_point > max_hp())
	hit_point = max_hp();
#ifdef TRACK_ACTION
    track_action("HEALTH", "heal",
	"locn="+locn+"; amount="+h+"; result-hp="+hit_point);
#endif

#if 0
    sp += h / 2;
    if (sp > max_sp())
	sp = max_sp();
#endif
    if (!body_locations) return;
    if (!body_hps) setup_hps();
    if (locn)
    {
	if ((i = index(body_locations, locn)) < 0)
	{ i = 0; locn = 0; }
    }
    else
	i = 0;
    do
    {
	if (intp(body_hps[i+1]))
	{
	    if ((body_hps[i+1] = body_hps[i+1] + h) > body_hps[i])
		body_hps[i+1] = body_hps[i];
	}
	i+=2;
	if (i >= sizeof(body_hps)) locn = "finished";
    } while (!locn);
}

add_sp(x) {
	if (x < 0 && query_wiz())  /* Jenna 2000 */
		return;
	sp += x;
	if (sp > max_sp()) sp = max_sp();
#ifdef TRACK_ACTION
	track_action("HEALTH", "add-sp", "amount="+x+"; result-sp="+sp);
#endif
}

can_put_and_get(str) { return str != 0; }

attack_object(ob) {

    if (!ob) {
			stop_hunter();
			return 0;
    }
    if (ob == this_object() || ob->query_ghost()) return 0;

#ifdef TRACK_ACTION
	track_action("FIGHT", "attack",
	    "who="+file_name(ob) +
	    "; who-name="+ob->query_real_name() +
	    "; who-class="+ob->query_npc());
#endif
    ob->attacked_by(this_object());
    attacked_by(ob);
/*    this_object()->start_attack_cycle();  No need! attacked_by does this */
	  FIGHT_SERV->attack(this_object());
	  return 1;
}

query_ghost() { return ghost; }

/*
 * If no one is here (except ourself), then turn off the heart beat.
 * Nah! - I want monsters to bash up other monsters.
 * Nah, fuck that - turns out to be too expensive (you moron!)
 */

test_if_any_here() {
    object ob;
    array what;
    int i;
    ob = environment();
    if (!ob) return;
    what = contents(ob);
    for (i = 0; i < sizeof(what); i++) {
	    ob = what[i];
	    if (ob) {
		if (ob != this_object() && living(ob) && !ob ->query_npc())
		    return 1;
      	    }
    }
    return 0;
}

stop_hunter() {
/*    hunter = 0; */
    tell_object(this_object(), "You are no longer hunted.\n");
}

force_us(cmd) {
    int other_lev;
    other_lev = query_su_level(previous_object());
    if ((query_wiz() > other_lev) && (other_lev < GOD)) {
	tell_object(this_object(), this_player() ->query_name() +
		    " tried to force you to " + cmd + ".\n");
	return;
    }
    tell_object(this_object(), this_player() ->query_name() +
		" forced you to '" + cmd + "'.\n");
    command(cmd);
}

/* This is used by the shop etc. */
/* Should now only be used by MOVE->move_money(), not by other code. */

add_money(int m) 
{
	object clr = caller();
/*	if (!this_object()->is_player()) return; */
	/* I want to fix present2 so it uses MOVE->move_money() instead.
	   *sighs* don't have time to do a proper job right now. Bel. */
	if ((file_name(clr) != MOVE) && (file_name(clr) != "RO/lib/present2"))
	{
		LOG->log("MONEY.leaks", "add_money() in living called by " +
			file_name(clr) + "\n");
	}
	money = money + m;
#ifdef TRACK_ACTION
	track_action("MONEY", "add-money", "amount=" + m + "; result=" + money);
#endif
/*	catch add_worth(m);; */
	 }

query_money() { return money; }

run_away() 
{
    object here;
    array dd;
    int i, j;

    here = environment();
    dd = here->query_dest_dir();
    if ((!pointerp(dd)) || (sizeof(dd) < 2)) 
    {
	    dd = DIRECTION->query_abbreviated();
	    j = random(sizeof(dd));
	    while (i < 14 && here == environment()) 
        {
		    notify_fail("");
		    i++; j++;
		    if (j >= sizeof(dd)) j = 0;
		    command(dd[j]);
	    }
    }
    else {
	/* should really run back to where we came from */
	if (prev_room) i = index(dd, prev_room) + 1;
	if (!i) i = random(sizeof(dd) / 2) * 2 + 1;
	command(dd[i]);
	prev_room = 0;
		/* forget where we ran from - so we don't run between only 2 locs */
	}
    if (here == environment()) {
	checked_say(cap_name + " tried, but failed to run away.\n");
	tell_object(this_object(),
	    "Your legs tried to run away, but failed.\n");
    } else {
	tell_object(this_object(), "Your legs run away with you!\n");
    }
#ifdef TRACK_ACTION
	track_action("GENERAL", "fleed", "");
#endif
	return 1;
}

query_hp(locn) 
{
    int x;

	if (!locn)  return hit_point;
	if (!body_hps) setup_hps();
	if (locn == "all") return body_hps;
	x = index(body_locations, locn);
	if (x < 0) return "invalid location";
	else return body_hps[x+1];	
}

query_max_hp(locn) 
{
    int x;

	if (!locn)  return max_hp();
	max_hp(); /* in case max hp has changed */
	if (!body_hps) setup_hps();
	x = index(body_locations, locn);
	if (x < 0) return "invalid location";
	else return body_hps[x];	
}

query_wimpy() { return wimpy; }
query_current_room() { return current_room; }

/* this determines whether we're in trouble based on a % run */
/* return 0 for a wimp out! else returns a status string */
query_wimp_out() 
{
    string pro;

    if (query_hp() < (max_hp() * wimpy / 100)) return 0;
    else {
    int x,i; string junk;
            /* go through body locations */
	    pro = "Hp:"+hit_point+"; Sp:"+sp;
            if (!body_hps) setup_hps();
            x = sizeof(body_hps);
            for(i = 0; i < x; i += 2)
                    if (index(body_locations[i+1], "*") > -1) {
                    if (intp(body_hps[i+1]) && body_hps[i]
                                     < body_hps[i+1] * wimpy /100)
                            return 0;
		    if (body_hps[i+1] > 700)
			recalc_hps();    /* Jenna 2000 */
                    pro=pro+"; "+capitalize(body_locations[i]) +
                            ":"+body_hps[i+1];
                    }
    }
    return pro;
}

checked_say(string str,(int|object|array) ob) 
{
    /* if (query_invis() >= INVIS_ACTION) return; */
	if (ob) say(str, ob);
	else say(str,this_object()); /* We need the second parameter otherwise the say happens around this_player() */
}

int
query_invis(int num) 
{
    /* if (!x || (x < level)) return is_invis; */
    if (!num) return is_invis;
    return -is_invis;
}

stop_fight()
{
    remove_hunt(attacker_ob);
    attacker_ob = 0;
}

/* 
	All of the following breathing code added by
	Rob Rendell - rendell@bruce.cs.monash.edu.au
	aka Nevyn on the Shattered World.

	note: This stuff needs to be in living.c because breathing
	is a function of the object in question (ie. you keep
	drowning if you're still under water). But I think this should
	be a generic state machine -- Dredd.

	Well, what ever is the case I added remove_can_breathe, and
	  did a few mods here and there. -- Llati
*/

array I_breathe;

/* call this with a new medium the living thing can breathe - anything means
    they can breathe anything (strangely enough) */

add_can_breathe(medium)
{
	if (stringp(I_breathe))
		I_breathe = explode(I_breathe, "&");
	if (I_breathe && (index (I_breathe,medium) >= 0) )
		return ; 
	if (stringp(medium))
		medium = [medium];
	if (medium[0] == "anything" || !I_breathe)
		I_breathe = medium;
	else
		I_breathe += medium;
}

/* Ho ho, its Llati again, sickening aint it? oh, i wrote the fn below */
remove_can_breathe (medium ) {
int i;
	if (!I_breathe) return;
	i = index (I_breathe,medium);
	if (i < 0) return 0;
	I_breathe = I_breathe[..i-1] + I_breathe [i+1..];
	return 1;
}

/* test_breath should be called from the init of every room, with length =
    the starting point of breath holding (10 is the default, if nothing is
    passed) */

test_breath(length)
{
	if (environment() && !I_can_breathe(environment()->query_medium()))
		if (find_call_out("breath_routine") <= 0 && find_call_out("fail_breath") <= 0)
		{
			if (length == 0)
				length = 10;
			call_out("breath_routine", 5, length);
		}
}

/* we must roll under con*length to keep holding our breath.  length decrements
    each call (minimum 1) */

breath_routine(length)
{
	if (!I_can_breathe(environment()->query_medium()))
		if (random(100) < query_con()*length)
		{
			if (length > 1)
				length -= 1;
			tell_object(this_object(), "You manage to hold your breath.\n");
			checked_say(cap_name+" seems to be having difficulty breathing.\n");
			call_out("breath_routine", 5, length);
		}
		else
		{
			tell_object(this_object(), "You cannot hold your breath any longer!\n");
			fail_breath();
		}
}

/* if the room has a function query_inhale_object defined returning an object,
    the function "inhale" in that object will be called, otherwise 1-25 points
	of damage is done on the living object.  If inhale returns 0, no more
	drowning occurs, otherwise the return value is the delay on the next call.
*/

fail_breath()
{
	int temp;
	array medium;

	medium = environment()->query_medium();
	if (stringp(medium))
		medium = [medium];
	if (!I_can_breathe(medium))
	{
		if (environment()->query_inhale_object())
		{
			if (temp = environment()->query_inhale_object()->inhale(this_object()))
				call_out("fail_breath", temp);
		}
		else
		{
			damage("suffocation", random(25)+1);
			call_out("fail_breath", 5);
		}
		if (!medium)
			medium = ["air"];
		tell_object(this_object(), "You inhale "+medium[0]+"!\n");
		checked_say(cap_name+" inhales "+medium[0]+"!\n");
	}
}

/* tests if the living thing can breathe in this medium.  If no_supply is 0, a
    test is made for air supplies in inventory, otherwise only the medium is
    considered. */

query_I_breathe() { return I_breathe; }

I_can_breathe(medium, no_supply)
{
	string s1, s2;
	int i;
	object ob;
	array what;

	if (!medium)
		medium = ["air"];
	if (stringp(medium))
		medium = [medium];
	if (stringp(I_breathe)) /* backdating thing */
		I_breathe = explode(I_breathe, "&");
	if (!I_breathe)
		I_breathe = ["air"];
	if (query_wiz() || ghost ||
		index(I_breathe, "anything") >= 0 ||
		index(medium, "magic") >= 0)
		return 1;
	for (i = 0; i < sizeof(medium); i++)
		if (index(I_breathe, medium[i]) >= 0)
			return 1;
	if (no_supply)
		return 0;
	what  =  contents(this_object());
	for (i = 0; i < sizeof(what); i++) {
	ob = what[i];
	if (ob)
	{
		if (ob->query_breath_supply() && I_can_breathe(ob->query_medium(), 1))
		{
			ob->use_breath_supply();
			call_out("test_breath", 5);
			return 1;
		}
	}
	}
	return 0;
}

/* New show_damage() - Jenna Nov 99 */
show_damage()
{
    string ret, ret2;
    int i, j, x;
    array bdam, aret;

    if (!body_hps)
	setup_hps();
    for (i = 0, bdam = [ ]; i < sizeof(body_hps); i+=2)
    {
	string temp;
	array dtype1, dtype2;

	if (stringp(body_hps[i+1]))
	    temp = body_hps[i+1];
	else
	{
	    dtype1 = query_body_damage(body_locations[i]);
	    if (pointerp(dtype1) && sizeof(dtype1))
	    {
                for (j = 0, dtype2 = [ ]; j < sizeof(dtype1); j += 2)
                dtype2 += [ "std/damage"->query_live_effect(dtype1[j],
                       (dtype1[j+1] * 3 / max_hp(body_locations[i])) + 1) ];
                temp = STRINGS->array2string(dtype2);
            }
        }
        if (temp)
        {
	    x = index(bdam, temp);
	    if (x == -1)
		bdam += [ temp, [ body_locations[i] ] ];
	    else
		bdam[x+1] += [ body_locations[i] ];
        }
    }
    if (sizeof(bdam))
    {
	aret = allocate(sizeof(bdam)/2);
        for (i = 0; i < sizeof(bdam); i+= 2)
        {
	    if (sizeof(bdam[i+1]) == 1)
		aret[i/2] = bdam[i+1][0] + " is ";
	    else
	        aret[i/2] = STRINGS->array2string(bdam[i+1]) + " are ";
	    aret[i/2] = GENDER->pos(this_object()) + " " +
		aret[i/2] + bdam[i];
	}
	ret = capitalize(STRINGS->array2string(aret));	
    }
    else
        ret = capitalize(GENDER->sub(this_object()));

    ret2 = (hit_point < max_hp()/5) ? " looks near death!\n" :
    	   (hit_point < max_hp()/3) ? " seems to be in some pain.\n":
	   (hit_point*6 < max_hp()*5) ? " looks somewhat pale.\n" : 0;
    if (!sizeof(bdam))
    {
	if (!ret2)
	    return capitalize(GENDER->sub(this_object())) + " is in good shape.\n";
	return capitalize(GENDER->sub(this_object())) + ret2;
    }
    else
    {
	if (!ret2)
	    return ret + ", but otherwise " + 
		GENDER->sub(this_object()) + " is in good shape.\n";
	return ret + ", and " + GENDER->sub(this_object()) + ret2;
    }
}

query_disable()
{
	if (!disable) disable = [ ];
	return disable;
}

/* This routine is called from objects that moves the player. *\
 * Special: direction "X" means teleport.                     *
 *                                                            *
 * The bits for moving silently between rooms and for         *
 * allowing players to see rooms in the dark added by:        *
 * Rattcherryh, 18th April 1993.                              *
 *                                                            *
 * Rewritten by Jenna, 18th July 2001, to remove some redun-  *
 * dancy, and to allow some players to see certain people     *
 * moving and others not (previously it was all-or-nothing).  *
 * This should make things easier for future enhancement of   *
 * light/silent movement/perception abilities.
\*                                                            */

move_player(dir, dest)
{
    int i;
    string mmsgin, mmsgout, catch_message;
    string cap_name2 = capitalize(my_name());
    object ob, fromenv;

    // Part 1: check if this is a valid move

    if (!dest)
    {
        LOG->log("BAD_DEST", dir + " from " + current_room +
                " has no destination.\n");
    }
    if (!ghost 
	&& dir != "X"
	&& !is_npc     /* hmm */
	&& !query_wiz()
        && !this_object()->add_weight(0))
    {
        tell_object(this_object(),"You try to move but collapse under "+
	    "the weight of all you're carrying!\n");
        broadcast_move(cap_name2 + " tries to leave but collapses under " +
	    "the weight of " + GENDER->pos(this_object()) + " load!\n");
        return 0;
    }
    if (!ghost 
	&& dir != "X"
	&& disable 
	&& arrayp(disable) 
        && (index(disable, "motor") > -1))
    {
        tell_object(this_object(), "Your injuries don't allow " +
            "you to move!\n");
	broadcast_move(cap_name2 + " struggles to leave " + dir + 
	    ", but can't due to " + GENDER->pos(this_object()) + 
	    " injuries!\n");
        return 0;
    }
    catch_message = catch dest ->stupid_fucking_load_up_func();;
    if (catch_message)
    {
        tell_object(this_object(), "There seems to be a disturbance " +
            "in reality in that direction.\nYou decide not to " +
            "leave that way.\n");
        if (dir != "X")
        {
	    broadcast_move("A mysterious force prevents "  + cap_name +
                " from leaving " + dir + ".\n");
            if (stringp(dest) || intp(dest))
                LOG->log("BAD_DEST", dir + " from " +
                        current_room + " to " + dest + "\n");
            else
                LOG->log("BAD_DEST", dir + " from " +
                        current_room + " to " + file_name(dest) + "\n");
        }
        return;
    }
    fromenv = environment(this_object());
    if (dir == "X" 
	&& !query_wiz() 
	&& (fromenv->realm() == "NT" 
	    || fromenv->query_property("no_teleport") 
	    || dest->realm() == "NT" 
	    || dest->query_property("no_teleport") ))
    {
        tell_object(this_object(), "Your attempt to leave failed.\n");
        return 0;
    }
    /* test movement hooks */
    if ( test_binary_hook( "move", this_object(), dir, dest )
        && !(query_wiz() && !query_store("wiz_move_hooks")) )
    {
        return 0;       /* refuse to move */
    }

    // Part 2: move the player

    if (fromenv)
        prev_room = file_name(fromenv);

    move_object(this_object(), dest);
    ob = environment(this_object());

#ifdef MAPPER_OBJECT
    MAPPER_OBJECT->add_valid_move(dir + "\t" + here + "\t" + file_name(ob));
#endif

    // Part 3: Setup strings for message broadcasting

    if (!msgin)
 	msgin = "arrives";
    if (!msgout)
	msgout = "leaves";
    if (dir == "X")
    {
        mmsgout = query_store("toutmsg");
        mmsgin = query_store("tinmsg");
        if (!mmsgout)
                mmsgout = "disappears in a puff of smoke";
        if (!mmsgin)
                mmsgin = "arrives in a puff of smoke";
    }

    // Part 4: Broadcast messages

    if (ob != fromenv)
    {
        if (ghost)
        {
            broadcast_move(NAME_OF_GHOST + " " + msgout + " " + dir + ".\n",
		fromenv);
            broadcast_move(NAME_OF_GHOST + " " + msgin + ".\n");
        }
        else if (dir == "X")
        {
            broadcast_move(cap_name2 + " " + mmsgout + ".\n", fromenv);
	    broadcast_move(cap_name2 + " " + mmsgin + ".\n");
        }
	else
	{
	    broadcast_move(cap_name2 + " " + msgout + " " + dir + ".\n",
		fromenv);
	    broadcast_move(cap_name2 + " " + msgin + ".\n");
	}
    }

    // Part 5: force a 'glance', if this player can see.

    if (query_wiz())
	tell_object(this_object(), (stringp(dest) ? dest: file_name(dest)) + 
	    "\n");

    this_object() -> did_a_move(); /* So an inherited one can be used */

    if (can_see())
	command("glance");
    else
	tell_object(this_object(), "A dark area.\n");
    test_advisory_hook( "postmove", prev_room, dest);

#if 0
    /* Show minimap */
    if (!is_npc 
	&& !query_store("minimap")
	&& !query_brief()
	&& environment(this_object())->query_property("is_world")) 
	{
            object support = query_soul("generic/support");
            if (support) support->minimap();
        }
    }
#endif

    return 1;
}


// Return 1 if this player can see, else 0.
// Defaults to environment if no arg supplied.
// Intended for future enhancement.
can_see(e)
{
    if (!objectp(e))
	e = environment();
    return set_light(0) > 0;
}

// Compares my invisibility level with the perception ability of
// everyone present. At the moment this is pretty crude, but it's
// written for future enhancement, when we have a decent light
// system.
//
// One of the first things to do will probably be to break out
// the 'how good is my perception' stuff: i.e. let players
// tell us what their perception is, rather than have us
// calculate it for them.
//
// But for the moment: normal perception is the room's light
// less 1. So in an average room of light == 1, your perception
// is 0; in a dark room of 0 light, your perception is -1.
// If it is dark, your "infravision" store is taken into
// account, so in a dark room an "infravision" score of 1 will
// allow you to see normally.
//
// If you're a wiz, you have excellent perception, based on your
// wizlevel.
//
// Compared to that is the player's invisibility. If perception is
// equal to or greater than his invis level, he will be seen.
//
// Thus, any invis level will currently hide your movement in a room
// of light == 1 (except from wizzes), but in brighter rooms, you need
// a correspondingly higher invis level. In the dark, players can
// only see you if their "infravision" is enough to counter the
// room's darkness level, and if you're not also invisible.
//
// In the future I expect that various people will be given better
// or worse perception than others.
//
// Jenna July 2001.
broadcast_move(str, where)
{
    int i, l, invis, percep, cap;
    array c;

    if (!where)
	where = environment();

    invis = query_invis();
    l = where->query_light();

    c = contents(where);
    for (i = 0; i < sizeof(c); i++)
	if (c[i] != this_object()
	    && c[i]->query_living())
	{
	    percep = l - 1; /* room's light level */
	    if (l < 1)
	    {
		cap = c[i]->query_store("infravision");
		/* infra can't make you see better than normal */
		if (cap + percep > 0)
		    percep = 0;
		else
		    percep += cap;
	    }
            percep += c[i]->query_wiz(); /* big wiz bonus */
	    if (percep >= invis)
		tell_object(c[i], str);
        }
}


int
add_move_hook( (string|object) obj, string func )
{
    return add_hook( "move", obj, func );
}


int
remove_move_hook(obj, func)
{
    return remove_hook( "move", obj, func );
}

query_move_hooks() 
{
    return query_hooks( "move" );
}

/*
	Written by Rob Rendell - rendell@bruce.cs.monash.edu.au
	aka. Nevyn on the Shattered World.
	(please retain this header).   - Dredd
*/

show_appearance()
{
    string phys, temp1, temp2;
    int flag;
    int last_rand;

    phys = SCORE_SERV->show_build(this_object()); 

    flag = 0;
    if (query_dex() > 25)
	temp1 = capitalize(subjective(this_object()))+" moves with an inhuman grace";
    else if (query_dex() > 16)
	temp1 = capitalize(subjective(this_object()))+" moves with a cat-like grace";
    else if (query_dex() < 6)
	temp1 = capitalize(subjective(this_object()))+" seems to blunder into everything";
    if (query_pow() > 25)
	temp2 = possessive(this_object())+" eyes almost glow with intensity";
    else if (query_pow() > 16)
	temp2 = possessive(this_object())+" eyes are very intense";
    if (query_san() < 5)
	if (temp2) temp2 += ", but have a manic gleam to them";
	else temp2 = possessive(this_object())+" eyes have a manic gleam to them";
    if (temp1 && temp2)
	temp1 += ", and "+temp2;
    else if (temp2)
	temp1 = capitalize(temp2);
    if (temp1)
	phys += "  "+temp1;
    if (phys[strlen(phys) - 1] != '.') phys += ".";
#if 0
    write(cap_name+" is "+phys+"  ");
    show_damage();
#endif
    return cap_name+" is "+phys+"  " + show_damage();
}

array hunting;
add_hunt(ob)
{
	if (!ob) return;
	if (!hunting) hunting = [ ob ];
	else 
		if (index(hunting, ob) == -1) hunting = [ ob ] + hunting;
	return;
}

query_hunt()
{
	if (!pointerp(hunting)) return;
	return hunting;
}

remove_hunt(ob) { 
	if (ob == "all") hunting = 0;
	else hunting = ARRAY->remove(hunting, ob);
}

/*
	 Teams stuff - originally by Rob Rendell.
*/

add_team(team_list)
{
    int ally;

    if (!team_list) return;
    if (objectp(team_list)) team_list = [team_list];
    add_allies(team_list);
    for (ally = 0; ally < sizeof(allies); ally++)
	if (allies[ally])
	    allies[ally]->add_allies(allies + [ this_object() ]);
}

add_allies(list)
{
    int ally;

    if (!allies) allies = [];
    for (ally = 0; ally < sizeof(list); ally++)
	if (index(allies, list[ally]) < 0 && list[ally] != this_object())
	    allies += [ list[ally] ];
}

stop_being_ally()
{
    int ally, s;
    s = sizeof(allies);
    for (ally = 0; ally < s; ally++)
	if (allies[ally])
	    allies[ally]->remove_ally(this_object());
}

remove_ally(who) { allies = ARRAY->remove(allies, who); }
query_allies() { return allies; }

static int casting;

string combat_spell;
add_spell(time, callb)
{
	if (is_casting()) return 0;
	if (time) casting = 1;
	if (time) call_out("finished_casting", time);
	combat_spell = callb;
#ifdef TRACK_ACTION
    track_action("SPELL", "cast-start", "spell="+callb);
#endif
	return 1;
}

query_spell() { return combat_spell; }
finished_casting(callb) { casting = 0; }
is_casting() { return casting; }

/*
	Notify - if the appropriate string is stored in
	the generic store then the call back is called.

	* DEATH - if you wish to be notified of an opponents death.
*/

notify(str, param)
{
string retv;
int y;
	retv = query_store(str);
	if (!retv) return;
	y = index(retv, ".");
	if (y == -1) return;
	retv[..y-1]->(retv[y+1..])(param);
}

/* Brief stuff */
static int in_temp_brief = 0;
static int lastmovechecktime;

query_in_temp_brief() { return in_temp_brief; }
query_brief() { return brief; }

toggle_brief() {
    brief = !brief;
    if (brief) {
        in_temp_brief = 0;
        write("Brief mode.\n");
        }
    else
        write("Verbose mode.\n");
    return 1;
}

did_a_move() {
    if (time() - lastmovechecktime <= 1) {
      /* We are moving fast */
          if (!in_temp_brief && !brief) {
            in_temp_brief = 1;
            brief = 1;
            }
        }
    else
	check_temp_brief();
    lastmovechecktime = time();
}

check_temp_brief() 
{
    if (in_temp_brief && (time() - lastmovechecktime > 1)) {
        /* Get out of brief */
        in_temp_brief = 0;
        brief = 0;
        }
}

query_msgin() { return msgin; }
query_msgout() { return msgout; }

setmin(m) { msgin = m; }
setmout(m) { msgout = m; }

set_body(bl) {
	if (!pointerp(bl)) bl = [ bl ];
	body_locations = bl;
	body_damage = allocate(sizeof(body_locations));
	setup_hps();
	do_disabilities ();
	return 1;
}

query_body() { return body_locations; }

/*
 * set_race is in monster.c, but not player.c.
 * I do NOT like people changing race in game - G.
 */

internal_set_race(r) {
        if (query_su_level(caller()) < GOD)
		return;
	race = r;
#ifdef TRACK_ACTION
    track_action("RACE", "set-race", "race="+r);
#endif
	}

/* added by dredd for vamp (let's try em again) */
/* another exception added by Jenna for new entry processes */

set_race(r) {
         if (r != "vampire"
             && file_name(caller())[..12] != "std/adventure")
		return;
	 LOG->log("RACES", query_name() + " changed from " + race +
		" to " + r + ".\n");
         race = r;
#ifdef TRACK_ACTION
         track_action("RACE", "set-race", "race="+r);
#endif
}

query_race() { return race; }

/* soul won't be added if you already have one, or if someone owns it. */

int add_soul(object o) 
{
    // if (o->query_myself() && (o->query_myself() != this_object())) return 0;
    string n = o->query_soul_type();
    /* no, we dont rename souls here any more */
    aliased_object(n, o);	
    // call_out( "ping_new_soul", 0.5, o );	
    /* after a time, init the soul */
    o->soul_init(this_object());
    return 1;
}

int query_soul(string n) {
    return "RO/Root/Loaded"::GetAliasedObject(n);
    }