/*    /lib/combat.c
 *    from the Dead Souls LPC Library
 *    combat events and data
 *    created by Descartes of Borg 950124
 *    Version: @(#) combat.c 1.40@(#)
 *    Last modified: 96/11/17
 */

#include <lib.h>
#include <rounds.h>
#include <config.h>
#include <daemons.h>
#include <position.h>
#include <damage_types.h>
#include <function.h>

inherit LIB_RACE;
inherit LIB_CLASSES;
inherit LIB_COMBATMSG;

private int Wimpy, Dead;
private string WimpyCommand;
private static int cParalyzed, tNextRound;
private static string TargetLimb, Party;
private static object CurrentEnemy;
private static function fParalyzed, fNextRound;
private static object *Hostiles, *Enemies, *SpecialTargets, *NonTargets;
private static object *PriorEnemies;

string GetName();
mixed GetProperty(string key);
string array AddChannel(mixed val);
string array RemoveChannel(mixed val);
int eventForce(mixed args);
int eventExecuteAttack(mixed target);
int eventWeaponRound(mixed target, mixed val);
void eventWeaponAttack(object target, object weapon, int num);
int eventMeleeRound(mixed target, function f);
void eventMeleeAttack(object target, string limb);
int eventMagicRound(mixed target, function f);
int eventWimpy(int i);

static void create() {
    race::create();
    classes::create();
    Hostiles = ({});
    Enemies = ({});
    NonTargets = ({});
    CurrentEnemy = SpecialTargets = 0;
    Party = 0;
    fParalyzed = 0;
    fNextRound = 0;
    cParalyzed = 0;
    tNextRound = ROUND_UNDEFINED;
    Wimpy = 0.20;
    WimpyCommand = "go out";
}

/*  *****************  /lib/combat.c data functions  *****************  */
varargs int GetMaxHealthPoints(string limb){
    return race::GetMaxHealthPoints(limb);
}

int GetDead(){
    return Dead;
}

int SetDead(int i){
    if(!i) Dead = 0;
    else Dead = 1;
    return Dead;
}

object array GetEnemies() {
    return Enemies;
}

int AddEnemy(object ob) {
    if( !ob || (member_array(ob, Enemies) != -1) ) {
        return 0;
    }
    if( !living(ob) ) {
        return 0;
    }
    Enemies += ({ ob });
    return 1;
}

int RemoveEnemy(object ob) {
    if( !ob || (member_array(ob, Enemies) == -1) ) {
        return 0;
    }
    Enemies -= ({ ob });
    return 1;
}

object SetCurrentEnemy(object ob) {
    if( !ob ) {
        return (CurrentEnemy = 0);
    }
    if( !living(ob) ) {
        return CurrentEnemy;
    }
    if( member_array(ob, Enemies) == -1 ) {
        AddEnemy(ob);
    }
    return (CurrentEnemy = ob);
}

static object ResetCurrentEnemy() {
    object array obs;

    obs = filter(GetEnemies(), (: $1 && environment() == environment($1) :));
    if( !sizeof(obs) ) {
        return 0;
    }
    return SetCurrentEnemy(obs[random(sizeof(obs))]);
}

object GetCurrentEnemy() {
    return CurrentEnemy;
}

private static void SortEnemies() {
    if( !sizeof(Enemies = filter(Enemies, (: ($1 && living($1)) :))) ) {
        Hostiles = ({});
        CurrentEnemy = 0;
        return;
    }
    Hostiles = (Hostiles & Enemies);
}

mixed *AddNonTargets(mixed val){
    if(!arrayp(val)) val = ({ val });
    foreach(object member in val){
        if(member_array(member,NonTargets) == -1){
            if(base_name(this_object()) != base_name(member)) NonTargets += ({ member });
        }
    }
    return NonTargets;
}

mixed *RemoveNonTargets(mixed val){
    if(!val) val = ({});
    if(!arrayp(val)) val = ({ val });
    if(!sizeof(val)){
        NonTargets = ({});
        return NonTargets;
    }
    foreach(object member in val){
        if(member_array(member,NonTargets) != -1){
            NonTargets -= ({ member });
        }
    }
    return NonTargets;
}

object *GetNonTargets(){
    return NonTargets;
}


int AddHostile(object ob) {
    if( !ob || (member_array(ob, Hostiles) != -1) ) {
        return 0;
    }
    if( !living(ob) ) {
        return 0;
    }
    Hostiles += ({ ob });
    return 1;
}

int RemoveHostile(object ob) {
    if( !ob || (member_array(ob, Hostiles) == -1) ) {
        return 0;
    }
    Hostiles -= ({ ob });
    return 1;
}

object array GetHostiles() {
    return Hostiles;
}

object array GetSpecialTarget() {
    return SpecialTargets;
}

object array SetSpecialTarget(object *cibles) {
    if(cibles) SpecialTargets = cibles;
    return SpecialTargets;
}

varargs int SetParalyzed(int count, function f) {
    if(count < 1) {
        count = cParalyzed = 0;
        fParalyzed = 0;
    }
    else {
        fParalyzed = f;
        cParalyzed = count;
    }
    return count;
}

int GetParalyzed() { return cParalyzed; }

int eventQuell(){
    object *truced;
    truced = get_livings(environment(this_object()));
    if(sizeof(Enemies)) PriorEnemies = Enemies;
    Enemies = ({});
    AddNonTargets(truced);
    return 1;
}

int eventUnQuell(){
    object *truced;
    truced = GetNonTargets();
    if(sizeof(PriorEnemies)) Enemies += PriorEnemies;
    if(sizeof(truced)) RemoveNonTargets(truced);
    return 1;
}

string SetParty(string str) {
    if( file_name(previous_object()) != PARTY_D ) return Party;
    if( str ) AddChannel(str);
    else RemoveChannel(Party);
    return (Party = str);
}

string GetParty() {
    return Party;
}

varargs int SetAttack(mixed target, function callback, int type) {
    int i;
    string *voibs = ({ "kill", "target", "smite", "waste", "hit", "attack" });

    if( objectp(target) ) target = ({ target });
    if( target ) {
        if( member_array(this_object(), target) != -1 ) return 0;
        if(sizeof(NonTargets) && member_array(query_verb(),voibs) != -1){
            foreach(object individual in target){
                if(member_array(individual,NonTargets) != -1){
                    NonTargets -= ({ individual });
                }
            }
        }
        else if(sizeof(NonTargets)){
            foreach(object individual in target){
                if(member_array(individual,NonTargets) != -1) target -= ({ individual });
            }
            if(!sizeof(target)) return 0;
        }

        if( !GetCurrentEnemy() ) call_out((: eventExecuteAttack :), 0, target);
        i = sizeof(target);
        while(i--) if( AddEnemy(target[i]) ) AddHostile(target[i]);
        if(!sizeof(SpecialTargets) ) SpecialTargets = target;
        else {
            int spec_targ_here;
            foreach(object t in SpecialTargets){
                if(t && present(t->GetKeyName(),environment(this_player()))) spec_targ_here = 1;
            }
            if(!spec_targ_here) SpecialTargets = target;
        }
    }
    fNextRound = callback;
    tNextRound = (type || ROUND_UNDEFINED);
    return 1;
}

int GetLevel() {
    return classes::GetLevel();
}

int GetInCombat() {
    return sizeof(filter(GetEnemies(),
        (: $1 && (environment($1) == environment()) :)));
}

int GetBaseStatLevel(string stat) {
    return race::GetBaseStatLevel(stat);
}

float SetWimpy(float wimpy) {
    return (Wimpy = wimpy);
}

float GetWimpy() {
    return Wimpy;
}

string SetWimpyCommand(string cmd) {
    return (WimpyCommand = cmd);
}

string GetWimpyCommand() {
    return WimpyCommand;
}

int GetMaxCarry() {
    return race::GetMaxCarry();
}

int GetMagicChance(int val) {
    val = GetStatLevel("intelligence")/2 + (3*val)/2;
    return val + GetLuck();
}

int GetMagicResistance() {
    int val = GetStatLevel("wisdom")/2 + (3*GetSkillLevel("magic defense"))/2;

    return val + GetLuck();
}

int GetCombatChance(int val) {
    val = val + random((val * GetMobility())/50);
    val = (val/(3- visibility()) + GetLuck());
    if( GetBlind() ) {
        return val/10;
    }
    else {
        return val;
    }
}

int GetDefenseChance(int val) {
    val = (val * GetMobility())/50;
    val = (val/(3- visibility()) + (GetLuck()/2));
    if( GetBlind() ) {
        return val/10;
    }
    else {
        return val;
    }
}

int GetCombatBonus(int level) {
    int diff = level - GetLevel();

    if( diff >= 6 && diff < 16 ) {
        return 4;
    }
    else if( diff >= 1 && diff < 6 ) {
        return 3;
    }
    else if( diff >= -9 && diff < 1 ) {
        return 2;
    }
    else if( diff >= -20 && diff < -9 ) {
        return 1;
    }
    else {
        return 0;
    }
}

static int GetDamage(int power, string skill) {
    int x = GetSkillLevel(skill);

    if( power < 1 ) {
        return 0;
    }
    else if( power > 100 ) {
        power = 100;
    }
    if( x < 1 ) {
        x = 1;
    }
    else if( x > 100 ) {
        x = 100;
    }
    x = (x * (power/2 + random(power/2))/10);
    x += (GetLuck()/2) + GetStatLevel("strength")/8;
    if( x < 1 ) { // negative luck or cursed strength
        return 1;
    }
    return x;
}

int CanWeapon(object target, string type, int hands, int num) {
    string limb = target->GetRandomLimb(TargetLimb);
    int chance = (7*GetSkillLevel(type+" attack") + 
      3*GetStatLevel("coordination"))/10;
    int div = 2;
    int x, y;

    if(hands > 1) {  
        if(GetSkillLevel("multi-hand")) {
            chance = (chance/2) + 
            (GetSkillLevel("multi-hand")/75)*(chance/2);
        }
        else { /* If you are really strong you can use multihand a bit */
            chance *= GetStatLevel("strength")/300;
            div += (hands-1);
        }
    }
    if(num > 1) {
        if(GetSkillLevel("multi-weapon")) {
            chance = (chance/2) + 
            (GetSkillLevel("multi-weapon")/75)*(chance/2);
        }    
        else { /* If you are really coordinated you can use multiweap a bit */
            chance *= GetStatLevel("coordination")/300;
            div += (num-1);
        }
    }
    chance = GetCombatChance(chance/div);
    x = random(chance);
    y = random(10);
    if( x <= y ) {
        if( x > y/2 ) { 
            TargetLimb = target->GetRandomLimb(0);
        }
        else { 
            TargetLimb = 0;
        }
    }
    else {
        TargetLimb = limb;
    }
    return chance;
}


int CanMelee(object target) {
    if(!this_object()->GetMelee()){

        string limb = target->GetRandomLimb(TargetLimb);
        int chance = ( 6*GetSkillLevel("melee attack") +
          2*GetStatLevel("coordination") )/20;
        int y = random(10);
        int x;

        chance = GetCombatChance(chance/3);
        x = random(chance);
        if( x <= y ) {
            if( x > y/2 ) {
                TargetLimb = target->GetRandomLimb(0);
            }
            else {
                TargetLimb = 0;
            }
        }
        else {
            TargetLimb = limb;
        }
        return chance;
    }
    else {
        string limb = target->GetRandomLimb(TargetLimb);
        int chance = ( 7*GetSkillLevel("melee attack") +
          3*GetStatLevel("coordination") )/10;
        int y = random(10);
        int x;

        chance = GetCombatChance(chance/2);
        x = random(chance);
        if( x <= y ) {
            if( x > y/2 ) {
                TargetLimb = target->GetRandomLimb(0);
            }
            else {
                TargetLimb = 0;
            }
        }
        else {
            TargetLimb = limb;
        }
        return chance;
    }
}

static int Destruct() {
    if( GetParty() ) PARTY_D->eventLeaveParty(this_object());
    return 1;
}

/*  *****************   /lib/combat.c events  ***************** */

varargs int eventDie(mixed agent) {
    object ob;
    int x;

    if(Dead) return 1;
    Dead = 1;

    x = race::eventDie(agent);
    if( x != 1 ) {
        return x;
    }
    foreach(ob in GetEnemies()) {
        if( ob ) {
            ob->eventEnemyDied(this_object());
        }
    }
    environment()->eventLivingDied(this_object(), agent);
    Enemies = ({});
    flush_messages();
    return 1;
}

int eventExecuteAttack(mixed target) {
    object array weapons;
    function f = fNextRound;
    int type = tNextRound;
    int position = GetPosition();

    if(Dead) return 1;
    if(target->GetDead()) return 1;

    fNextRound = 0;
    tNextRound = ROUND_UNDEFINED;
    if( position == POSITION_LYING || position == POSITION_SITTING &&
      RACES_D->GetLimblessCombatRace(this_object()->GetRace()) != 1) {
        if(this_object()->CanFly()) {
            this_object()->eventFly();
        }
        else if(RACES_D->GetLimblessCombatRace(GetRace()) != 1){
            this_object()->eventPrint("You can't fight unless you are up!");
            return 0;
        }
    }
    if(this_object()->GetPacifist()) {
        tell_object(this_object(),"As a pacifist, you choose not to fight.");
        return 0;
    }
    if( arrayp(target) ) {
        if( !f || (functionp(f) & FP_OWNER_DESTED) ) {
            return 0; /* built in only handles 1 targ */
        }
        target = filter(target, function(object ob) {
              if( !ob ) {
                  return 0;
              }
              if( !ob->eventPreAttack(this_object()) ) {
                  return 0;
              }
              return 1;
            });
          if( !sizeof(target) ) {
              return 0;
          }
      }
        else if( !target->eventPreAttack(this_object()) ) {
            return 0;
        }
        this_object()->AddStaminaPoints(-1);
        switch(type) {
        case ROUND_UNDEFINED:
            if( functionp(f) && !(functionp(f) & FP_OWNER_DESTED) ) {
                return evaluate(f, target);
            }
            if( sizeof(weapons = GetWielded()) ) {
                return eventWeaponRound(target, weapons);
            }
            else {
                return eventMeleeRound(target, 0);
            }

        case ROUND_MAGIC:
            return eventMagicRound(target, f);

        case ROUND_MELEE:
            return eventMeleeRound(target, functionp(f) ? f : 0);

        case ROUND_WEAPON:
            return eventWeaponRound(target, functionp(f) ? f : GetWielded());

        case ROUND_OTHER:
            if( functionp(f) && !(functionp(f) & FP_OWNER_DESTED) ) {
                return evaluate(f);
            }
            else {
                return 0;
            }

        default: return 0;
        }
        return 0;
    }

    int eventWeaponRound(mixed target, mixed val) {
        object array weapons = 0;
        function f = 0;

        if(Dead) return 1;
        if(target->GetDead()) return 1;

        if( arrayp(val) ) {
            weapons = val;
        }
        else if( functionp(val) && !(functionp(val) & FP_OWNER_DESTED) ) {
            f = val;
        }
        else {
            return 0;
        }
        if( f ) {
            evaluate(f, target);
        }
        else {
            int count = sizeof(weapons);

            foreach(object weapon in weapons) {
                if( !target ) {
                    break;
                }
                eventWeaponAttack(target, weapon, count);
            }
        }
        return target->GetDying();
    }

    void eventWeaponAttack(object target, object weapon, int num){
        string weapon_type = weapon->GetWeaponType();
        int hands = weapon->GetHands();
        int level = target->GetLevel();
        int bonus = GetCombatBonus(level);
        int power, pro, con;

        if(Dead) return;
        if(target->GetDead()) return;

        if( target->GetDying() ) {
            return;
        }
        pro = CanWeapon(target, weapon_type, hands, num);
        power = random(pro);
        con = target->GetDefenseChance(target->GetSkillLevel(weapon_type +
            " defense"));
        if( !TargetLimb ) { // If the thing stood still, I still missed
            eventTrainSkill(weapon_type + " attack", pro, 0, 0, bonus);
            if( hands > 1 ) {
                eventTrainSkill("multi-hand", pro, 0, 0, bonus);
            }
            if( num > 1 ) {
                eventTrainSkill("multi-weapon", pro, 0, 0, bonus);
            }
            SendWeaponMessages(target, -2, weapon, TargetLimb);
        }
        else if( !target->eventReceiveAttack(power, weapon_type, this_object()) ) {
            // Target avoided the attack
            eventTrainSkill(weapon_type + " attack", pro, con, 0, bonus);
            if( hands > 1 ) {
                eventTrainSkill("multi-hand", pro, con, 0, bonus);
            }
            if( num > 1 ) {
                eventTrainSkill("multi-weapon", pro, con, 0, bonus);
            }
            SendWeaponMessages(target, -1, weapon, TargetLimb);
        }
        else { // I hit, but how hard did I hit?
            int damage_type, damage, weapon_damage, actual_damage, encumbrance;
            encumbrance = this_object()->GetEncumbrance();
            if(encumbrance > 20){
                tell_object(this_object(),"You struggle to fight while carrying stuff.");
            }
            eventTrainSkill(weapon_type + " attack", pro*2, con, 1, bonus);
            damage_type = weapon->GetDamageType();
            damage = (weapon->eventStrike(target) * pro)/(GetLevel()*2);
            damage = GetDamage(damage, weapon_type + " attack");
            damage -= encumbrance;
            if(damage < 0) damage = 0;
            actual_damage = target->eventReceiveDamage(this_object(), damage_type,
              damage, 0, TargetLimb);
            if( actual_damage < 0 ) {
                actual_damage = 0;
            }
            weapon_damage = damage - actual_damage;
            if( weapon_damage > 0 ) {
                weapon->eventReceiveDamage(this_object(), BLUNT, weapon_damage,
                  0, TargetLimb);
            }
            if( !target->GetDying() ) {
                SendWeaponMessages(target, actual_damage, weapon, TargetLimb);
            }
            else {
                this_object()->eventPrint(possessive_noun(target) + " destruction is now on your "
                  "head.");
                target->eventPrint(GetName() + " is your murderer.");
                environment()->eventPrint(possessive_noun(target) +
                  " destruction is now on " +
                  possessive_noun(this_object())
                  + " head.", ({ this_object(), target }));
            }
        }
    }

    int eventMeleeRound(mixed target, function f) {
        string array limbs = GetLimbs() - ({ GetTorso() });
        int count = sizeof(limbs);
        int attacks;

        if(Dead) return 1;
        if(target->GetDead()) return 1;

        if( count < 2 ) {
            if(RACES_D->GetLimblessCombatRace(this_object()->GetRace())){
                limbs = GetLimbs();
                count = sizeof(limbs);
            }
            else return 0;
        }
        if( !f || (functionp(f) & FP_OWNER_DESTED) ) {
            attacks = 1 + random(GetSkillLevel("melee attack"))/30;
            while( attacks-- ) {
                if( target->GetDying() ) {
                    break;
                }
                eventMeleeAttack(target, limbs[random(count)]);
            }
        }
        else {
            evaluate(f, target, limbs[random(count)]);
        }
        return target->GetDying();
    }

    void eventMeleeAttack(object target, string limb) {
        int pro, con;
        int chance;

        if( target->GetDead() || Dead || target->GetDying() ) {
            return;
        }

        if(limb == "head" && this_object()->GetCanBite()) {
            eventBite(target);
            return;
        }

        pro = CanMelee(target);
        con = target->GetDefenseChance(target->GetSkillLevel("melee defense"));
        chance = random(pro);
        if( !TargetLimb ) { // I *really* missed
            SendMeleeMessages(target, -2);
            eventTrainSkill("melee attack", pro, 0, 0,
              GetCombatBonus(target->GetLevel()));
        }
        else if( !target->eventReceiveAttack(chance, "melee", this_object()) ) {
            // Enemy dodged my attack
            SendMeleeMessages(target, -1);
            eventTrainSkill("melee attack", pro, con, 0,
              GetCombatBonus(target->GetLevel()));
        }
        else {
            int x, encumbrance;
            encumbrance = this_object()->GetEncumbrance();
            if(encumbrance > 20){
                tell_object(this_object(),"You struggle to fight while carrying stuff.");
            }
            // I hit, how hard?
            eventTrainSkill("melee attack", pro, con, 1,
              GetCombatBonus(target->GetLevel()));
            if(this_object()->GetMelee()) x = GetDamage(3*chance/4, "melee attack");
            else x = GetDamage(3*chance/20, "melee attack");
            x -= encumbrance;
            if(x < 0) x = 0;
            x = target->eventReceiveDamage(this_object(), BLUNT, x, 0,
              TargetLimb);
            if( !target->GetDying() ) {
                SendMeleeMessages(target, (x > 0) ? x : 0, TargetLimb);
            }
            else {
                this_object()->eventPrint(possessive_noun(target) + " destruction is now "
                  "on your head.");
                target->eventPrint(GetName() + " is your murderer.");
                environment()->eventPrint(possessive_noun(target) +
                  " destruction is now on " +
                  possessive_noun(this_object()) +
                  " head.", ({ this_object(), target }));
            }
        }
    }

    int eventMagicRound(mixed target, function f) {
        if(target->GetDead()) return 1;
        evaluate(f, target);
        return target->GetDying();
    }

    mixed eventBite(object target) {
        int pro = CanMelee(target);
        int con = target->GetDefenseChance(target->GetSkillLevel("melee defense"));
        int x = random(pro);

        if(target->GetDead()) return 1;

        if( environment() != environment(target) ) {
            this_object()->eventPrint(target->GetName() + " has gone away.");
            return 1;
        }
        if( TargetLimb ) {
            if( target->eventReceiveAttack(x, "melee", this_object()) ) { 
                x = GetDamage(pro*2, "melee attack");
                x = target->eventReceiveDamage(this_object(), BITE, x, 0,
                  TargetLimb);
                if( x < 1 ) {
                    target->eventPrint(possessive_noun(this_object()) + " bite "
                      "is nothing more than a pinch.");
                    this_object()->eventPrint("Your bite is nothing more than a pinch.");
                    environment()->eventPrint(possessive_noun(this_object()) +
                      " bite is nothing more than a "
                      "pinch.",
                      ({ target, this_object() }));
                }
                else {
                    target->eventPrint(GetName() + " bites you in the " +
                      TargetLimb + "!");
                    this_object()->eventPrint("You bite " + target->GetName() + " in the " +
                      TargetLimb + "!");
                    environment()->eventPrint(GetName() + " bites " +
                      target->GetName() + " in the " +
                      TargetLimb + "!",
                      ({ target, this_object() }));
                }
                eventTrainSkill("melee attack", pro, con, 1,
                  GetCombatBonus(target->GetLevel()));
            }
            else {
                target->eventPrint("You avoid " + possessive_noun(this_object()) +
                  " bite.");
                this_object()->eventPrint(target->GetName() + " avoids your bite.");
                environment()->eventPrint(target->GetName() + " avoids " +
                  possessive_noun(this_object()) +
                  " bite.",
                  ({ this_object(), target }));
                eventTrainSkill("melee attack", pro, con, 0,
                  GetCombatBonus(target->GetLevel()));
            }
        }
        else {
            this_object()->eventPrint("You flounder about like a buffoon.");
            environment()->eventPrint(GetName() + " flounders about like a "
              "buffoon.", this_object());
        }
        return 1;
    }

    int eventPreAttack(object agent) {
        if( agent == this_object() ) {
            return 0;
        }
        if( environment()->GetProperty("no attack") ) {
            return 0;
        }
        if( GetDying() ) {
            return 0;
        }
        if( playerp(this_object()) && playerp(agent) && !PLAYER_KILL) { 
            if( !environment()->CanAttack( agent, this_object() ) ) {
                return 0;
            }
        }
        if( AddEnemy(agent) ) {
            AddHostile(agent);
        }
        return 1;
    }

    varargs int eventReceiveAttack(int speed, string def, object agent) {
        int x, pro, level, bonus;

        if(Dead) return 0;

        if( !agent ) {
            agent = previous_object();
        }
        if( !living(agent) ) {
            level = 1;
            bonus = 1;
        }
        else {
            level = agent->GetLevel();
            bonus = GetCombatBonus(level);
        }
        if( AddEnemy(agent) ) {
            AddHostile(agent);
        }
        if( def == "magic" ) {
            pro = GetMagicResistance();
            if( (x = random(pro)) > speed ) {
                eventTrainSkill("magic defense", pro, speed, 1, bonus);
                return 0;
            }
            else {
                eventTrainSkill("magic defense", pro, speed, 0, bonus);
                return 1;
            }
        }
        else {
            pro = GetDefenseChance(GetSkillLevel(def + " defense"));
            x = random(pro = pro/2);
            if( x > speed ) {
                eventTrainSkill(def + " defense", pro, speed, 1, bonus);
                return 0;
            }
            else {
                eventTrainSkill(def + " defense", pro, speed, 0, bonus);
                return 1;
            }
        }
    }

    void eventKillEnemy(object ob) {
        int level;

        if( !ob ) return;
        level = ob->GetLevel();
        if(ob->GetCustomXP()) this_object()->AddExperiencePoints(ob->GetCustomXP());
        else this_object()->AddExperiencePoints(level * 80);
        if( member_array(ob, GetHostiles()) == -1 ) {
            int x;

            eventTrainSkill("murder", GetLevel(), level, 1,GetCombatBonus(level)); 
            x = (int)ob->GetMorality();
            if( x > 0 ) x = -x;
            else if( GetMorality() > 200 ) x = 100;
            else x = 0;
            eventMoralAct(x);
        }
    }

    void eventDestroyEnemy(object ob) {
        int level;

        if( !ob ) return;
        level = ob->GetLevel();
        if(ob->GetCustomXP()) this_object()->AddExperiencePoints(ob->GetCustomXP());
        else this_object()->AddExperiencePoints(level * 80);
        eventTrainSkill("faith", GetLevel(), level, 1, GetCombatBonus(level));
    }

    void eventEnemyDied(object ob) {
        if( !ob ) return;
        Enemies -= ({ ob });
        Hostiles -= ({ ob });
        if(!sizeof(SpecialTargets) || (!sizeof(Enemies) || !sizeof(Hostiles))) 
            NonTargets = ({});
    }

    varargs int eventReceiveDamage(mixed agent, int type, int x, int internal,
      mixed limbs) {
        int hp,encumbrance;

        encumbrance = this_object()->GetEncumbrance();

        if(Dead) return 0;
        if(encumbrance > 200){
            if(GetInCombat()) tell_object(this_object(),"You try to dodge while weighed down.");
        }
        x = race::eventReceiveDamage(agent, type, x, internal, limbs);
        if( !Wimpy ) return x;
        if( (hp = GetHealthPoints()) < 1 ) return x;
        if( Wimpy < percent(hp, GetMaxHealthPoints()) )
            return x;
        call_out((: eventWimpy :), 0);
        return x;
    }

    mixed eventTurn(object who) {
        int defense;

        if( !GetUndead() ) {
            return 0;
        }
        if( GetProperty("no turn") ) {
            if( !who ) {
                return 0;
            }
            else {
                int x = GetProperty("no turn");

                environment(who)->eventPrint("The power of the undead "
                  "turns on " + who->GetName() +
                  ".", who);
                who->eventPrint("The power of the undead turns on you.");
                if( x > random(100) + 1 ) {
                    who->eventDie(this_object());
                }
                else {
                    who->eventReceiveDamage(this_object(), MAGIC, random(50), 1);
                }
                return 0;
            }
        }
        if( !who ) {
            race::eventTurn(who);
            return 1;
        }
        defense = GetMagicResistance();
        if( who->GetSkillLevel("faith") < defense ) {
            who->eventPrint("You writhe in pain.");
            environment(who)->eventPrint(who->GetName() + " writhes in pain.",
              who);
            who->eventReceiveDamage(this_object(), MAGIC, random(defense), 1);
            eventTrainSkill("magic defense", defense, who->GetSkillLevel("faith"),
              1, GetCombatBonus(who->GetLevel()));
            return 0;
        }
        race::eventTurn(who);
        return 1;
    }

    int eventWimpy(int i) {
        object env = environment();
        string dir, cmd;

        if( !env || !GetInCombat() ) {
            if(!i) return 0;
        }
        cmd = WimpyCommand || "go out";
        if( (sscanf(cmd, "go %s", dir) && !((string)env->GetExit(dir))) ||
          (sscanf(cmd, "enter %s", dir) && !((string)env->GetEnter(dir))) ) {
            string *tmp;

            tmp = filter((string *)environment()->GetExits(),
              (: !((string)environment()->GetDoor($1)) :));
            if( !sizeof(tmp) ) {
                tmp = filter((string *)environment()->GetEnters(),
                  (: !((string)environment()->GetDoor($1)) :));
                if( !sizeof(tmp) ) {
                    this_object()->eventPrint("You need to escape, but you have nowhere to go!");
                    return 0;
                }
                cmd = "enter " + tmp[random(sizeof(tmp))];
            }
            else cmd = "go " + tmp[random(sizeof(tmp))];
        }
        return eventForce(cmd);
    }

    static void heart_beat() {
        race::heart_beat();
        if( GetSleeping() || GetDying() ) {
            return;
        }
        if( cParalyzed > 0 ) {
            cParalyzed--;
            if( cParalyzed < 1 ) {
                function f;

                f = fParalyzed;
                fParalyzed = 0;
                if( functionp(f) && !(functionp(f) & FP_OWNER_DESTED) ) {
                    evaluate(f);
                }
                else {
                    this_object()->eventPrint("You can move again.");
                }
            }
            return;
        }
        if( sizeof(Enemies) ) {
            SortEnemies();
            foreach(object dude in Enemies){
                if(member_array(dude,NonTargets) != -1){
                    RemoveEnemy(dude);
                    RemoveHostile(dude);
                }
            }
        }
        if( sizeof(Enemies) ) {
            object ob;

            SortEnemies();

            if( SpecialTargets ) {
                foreach(object target in SpecialTargets) {

                    if( objectp(SetCurrentEnemy(target)) ) {
                        break;
                    }
                }
                eventExecuteAttack(SpecialTargets);
                SpecialTargets = 0;
            }
            else if( ob = ResetCurrentEnemy() ) {
                eventExecuteAttack(ob);
            }
        }
        else if( tNextRound != ROUND_UNDEFINED && functionp(fNextRound) ) {
            function f;

            f = fNextRound;
            tNextRound = ROUND_UNDEFINED;
            evaluate(f);
        }
    }
