dw_fluffos_v2/
dw_fluffos_v2/fluffos-2.9-ds2.05/
dw_fluffos_v2/fluffos-2.9-ds2.05/ChangeLog.old/
dw_fluffos_v2/fluffos-2.9-ds2.05/Win32/
dw_fluffos_v2/fluffos-2.9-ds2.05/compat/
dw_fluffos_v2/fluffos-2.9-ds2.05/compat/simuls/
dw_fluffos_v2/fluffos-2.9-ds2.05/include/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/clone/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/command/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/data/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/etc/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/include/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/inherit/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/inherit/master/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/log/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/single/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/single/tests/compiler/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/single/tests/efuns/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/single/tests/operators/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/u/
dw_fluffos_v2/fluffos-2.9-ds2.05/tmp/
dw_fluffos_v2/fluffos-2.9-ds2.05/windows/
dw_fluffos_v2/lib/
dw_fluffos_v2/lib/binaries/cmds/
dw_fluffos_v2/lib/binaries/cmds/creator/
dw_fluffos_v2/lib/binaries/cmds/living/
dw_fluffos_v2/lib/binaries/cmds/player/
dw_fluffos_v2/lib/binaries/d/admin/obj/
dw_fluffos_v2/lib/binaries/d/liaison/
dw_fluffos_v2/lib/binaries/global/virtual/
dw_fluffos_v2/lib/binaries/global/virtual/setup_compiler/
dw_fluffos_v2/lib/binaries/obj/handlers/autodoc/
dw_fluffos_v2/lib/binaries/obj/handlers/terrain_things/
dw_fluffos_v2/lib/binaries/obj/misc/
dw_fluffos_v2/lib/binaries/obj/misc/buckets/
dw_fluffos_v2/lib/binaries/obj/monster/
dw_fluffos_v2/lib/binaries/obj/reactions/
dw_fluffos_v2/lib/binaries/obj/reagents/
dw_fluffos_v2/lib/binaries/secure/cmds/creator/
dw_fluffos_v2/lib/binaries/secure/master/
dw_fluffos_v2/lib/binaries/std/
dw_fluffos_v2/lib/binaries/std/dom/
dw_fluffos_v2/lib/binaries/std/effects/object/
dw_fluffos_v2/lib/binaries/std/guilds/
dw_fluffos_v2/lib/binaries/std/languages/
dw_fluffos_v2/lib/binaries/std/races/
dw_fluffos_v2/lib/binaries/std/room/
dw_fluffos_v2/lib/binaries/std/room/basic/
dw_fluffos_v2/lib/binaries/std/shops/
dw_fluffos_v2/lib/binaries/std/shops/inherit/
dw_fluffos_v2/lib/binaries/www/
dw_fluffos_v2/lib/cmds/guild-race/
dw_fluffos_v2/lib/cmds/guild-race/crafts/
dw_fluffos_v2/lib/cmds/guild-race/other/
dw_fluffos_v2/lib/cmds/playtester/
dw_fluffos_v2/lib/cmds/playtester/senior/
dw_fluffos_v2/lib/d/admin/
dw_fluffos_v2/lib/d/admin/log/
dw_fluffos_v2/lib/d/admin/mapper/31-10-01/mapmaker/event/
dw_fluffos_v2/lib/d/admin/meetings/
dw_fluffos_v2/lib/d/admin/obj/
dw_fluffos_v2/lib/d/admin/room/we_care/
dw_fluffos_v2/lib/d/admin/save/
dw_fluffos_v2/lib/d/dist/
dw_fluffos_v2/lib/d/dist/mtf/
dw_fluffos_v2/lib/d/dist/pumpkin/
dw_fluffos_v2/lib/d/dist/pumpkin/chars/
dw_fluffos_v2/lib/d/dist/pumpkin/desert/
dw_fluffos_v2/lib/d/dist/pumpkin/gumboot/
dw_fluffos_v2/lib/d/dist/pumpkin/hospital/
dw_fluffos_v2/lib/d/dist/pumpkin/inherit/
dw_fluffos_v2/lib/d/dist/pumpkin/map/
dw_fluffos_v2/lib/d/dist/pumpkin/plain/
dw_fluffos_v2/lib/d/dist/pumpkin/pumpkin/
dw_fluffos_v2/lib/d/dist/pumpkin/save/
dw_fluffos_v2/lib/d/dist/pumpkin/squash/
dw_fluffos_v2/lib/d/dist/pumpkin/terrain/
dw_fluffos_v2/lib/d/dist/pumpkin/woods/
dw_fluffos_v2/lib/d/dist/start/
dw_fluffos_v2/lib/d/learning/TinyTown/buildings/
dw_fluffos_v2/lib/d/learning/TinyTown/map/
dw_fluffos_v2/lib/d/learning/TinyTown/roads/
dw_fluffos_v2/lib/d/learning/add_command/
dw_fluffos_v2/lib/d/learning/arms_and_weps/
dw_fluffos_v2/lib/d/learning/chars/
dw_fluffos_v2/lib/d/learning/cutnpaste/
dw_fluffos_v2/lib/d/learning/examples/npcs/
dw_fluffos_v2/lib/d/learning/examples/player_houses/npcs/
dw_fluffos_v2/lib/d/learning/examples/terrain_map/basic/
dw_fluffos_v2/lib/d/learning/functions/
dw_fluffos_v2/lib/d/learning/handlers/
dw_fluffos_v2/lib/d/learning/help_topics/npcs/
dw_fluffos_v2/lib/d/learning/help_topics/objects/
dw_fluffos_v2/lib/d/learning/help_topics/rcs_demo/
dw_fluffos_v2/lib/d/learning/help_topics/rooms/
dw_fluffos_v2/lib/d/learning/help_topics/rooms/crowd/
dw_fluffos_v2/lib/d/learning/help_topics/rooms/situations/
dw_fluffos_v2/lib/d/learning/items/
dw_fluffos_v2/lib/d/learning/save/
dw_fluffos_v2/lib/d/liaison/
dw_fluffos_v2/lib/d/liaison/NEWBIE/doc/
dw_fluffos_v2/lib/d/liaison/NEWBIE/save/oldlog/
dw_fluffos_v2/lib/db/
dw_fluffos_v2/lib/doc/
dw_fluffos_v2/lib/doc/creator/
dw_fluffos_v2/lib/doc/creator/autodoc/include/reaction/
dw_fluffos_v2/lib/doc/creator/autodoc/include/ritual_system/
dw_fluffos_v2/lib/doc/creator/autodoc/include/talker/
dw_fluffos_v2/lib/doc/creator/autodoc/include/terrain_map/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/baggage/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/clock/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/clothing/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/cont_save/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/corpse/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/money/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/monster/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/scabbard/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/service_provider/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/state_changer/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/wand/
dw_fluffos_v2/lib/doc/creator/autodoc/std/book_dir/
dw_fluffos_v2/lib/doc/creator/autodoc/std/key/
dw_fluffos_v2/lib/doc/creator/autodoc/std/learning/
dw_fluffos_v2/lib/doc/creator/autodoc/std/map/
dw_fluffos_v2/lib/doc/creator/autodoc/std/race/
dw_fluffos_v2/lib/doc/creator/autodoc/std/weapon_logic/
dw_fluffos_v2/lib/doc/creator/files/
dw_fluffos_v2/lib/doc/creator/policy/
dw_fluffos_v2/lib/doc/creator/room/
dw_fluffos_v2/lib/doc/effects/
dw_fluffos_v2/lib/doc/ideas/
dw_fluffos_v2/lib/doc/known_command/
dw_fluffos_v2/lib/doc/lpc/basic_manual/
dw_fluffos_v2/lib/doc/lpc/intermediate/
dw_fluffos_v2/lib/doc/new/add_command/
dw_fluffos_v2/lib/doc/new/handlers/
dw_fluffos_v2/lib/doc/new/living/
dw_fluffos_v2/lib/doc/new/living/race/
dw_fluffos_v2/lib/doc/new/living/spells/
dw_fluffos_v2/lib/doc/new/player/
dw_fluffos_v2/lib/doc/new/room/guild/
dw_fluffos_v2/lib/doc/new/room/outside/
dw_fluffos_v2/lib/doc/new/room/storeroom/
dw_fluffos_v2/lib/doc/object/
dw_fluffos_v2/lib/doc/playtesters/
dw_fluffos_v2/lib/doc/policy/
dw_fluffos_v2/lib/doc/weapons/
dw_fluffos_v2/lib/global/handlers/
dw_fluffos_v2/lib/global/virtual/setup_compiler/
dw_fluffos_v2/lib/include/
dw_fluffos_v2/lib/include/cmds/
dw_fluffos_v2/lib/include/effects/
dw_fluffos_v2/lib/include/npc/
dw_fluffos_v2/lib/include/shops/
dw_fluffos_v2/lib/net/daemon/chars/
dw_fluffos_v2/lib/net/inherit/
dw_fluffos_v2/lib/net/intermud3/
dw_fluffos_v2/lib/net/intermud3/services/
dw_fluffos_v2/lib/net/obj/
dw_fluffos_v2/lib/net/save/
dw_fluffos_v2/lib/net/smnmp/
dw_fluffos_v2/lib/net/snmp/
dw_fluffos_v2/lib/obj/amulets/
dw_fluffos_v2/lib/obj/b_day/
dw_fluffos_v2/lib/obj/examples/
dw_fluffos_v2/lib/obj/food/alcohol/
dw_fluffos_v2/lib/obj/food/chocolates/
dw_fluffos_v2/lib/obj/food/fruits/
dw_fluffos_v2/lib/obj/food/meat/
dw_fluffos_v2/lib/obj/food/nuts/
dw_fluffos_v2/lib/obj/food/seafood/
dw_fluffos_v2/lib/obj/food/vegetables/
dw_fluffos_v2/lib/obj/fungi/
dw_fluffos_v2/lib/obj/furnitures/artwork/
dw_fluffos_v2/lib/obj/furnitures/bathroom/
dw_fluffos_v2/lib/obj/furnitures/beds/
dw_fluffos_v2/lib/obj/furnitures/cabinets/
dw_fluffos_v2/lib/obj/furnitures/chairs/
dw_fluffos_v2/lib/obj/furnitures/chests/
dw_fluffos_v2/lib/obj/furnitures/clocks/
dw_fluffos_v2/lib/obj/furnitures/crockery/
dw_fluffos_v2/lib/obj/furnitures/cupboards/
dw_fluffos_v2/lib/obj/furnitures/cushions/
dw_fluffos_v2/lib/obj/furnitures/fake_plants/
dw_fluffos_v2/lib/obj/furnitures/lamps/
dw_fluffos_v2/lib/obj/furnitures/mirrors/
dw_fluffos_v2/lib/obj/furnitures/outdoor/
dw_fluffos_v2/lib/obj/furnitures/safes/
dw_fluffos_v2/lib/obj/furnitures/shelves/
dw_fluffos_v2/lib/obj/furnitures/sideboards/
dw_fluffos_v2/lib/obj/furnitures/sofas/
dw_fluffos_v2/lib/obj/furnitures/stoves/
dw_fluffos_v2/lib/obj/furnitures/tables/
dw_fluffos_v2/lib/obj/furnitures/wardrobes/
dw_fluffos_v2/lib/obj/handlers/
dw_fluffos_v2/lib/obj/handlers/autodoc/
dw_fluffos_v2/lib/obj/jewellery/anklets/
dw_fluffos_v2/lib/obj/jewellery/bracelets/
dw_fluffos_v2/lib/obj/jewellery/earrings/
dw_fluffos_v2/lib/obj/jewellery/misc/
dw_fluffos_v2/lib/obj/jewellery/necklaces/
dw_fluffos_v2/lib/obj/jewellery/rings/
dw_fluffos_v2/lib/obj/media/
dw_fluffos_v2/lib/obj/misc/buckets/
dw_fluffos_v2/lib/obj/misc/jars/
dw_fluffos_v2/lib/obj/misc/papers/
dw_fluffos_v2/lib/obj/misc/player_shop/
dw_fluffos_v2/lib/obj/misc/shops/
dw_fluffos_v2/lib/obj/misc/traps/
dw_fluffos_v2/lib/obj/monster/
dw_fluffos_v2/lib/obj/monster/godmother/
dw_fluffos_v2/lib/obj/monster/transport/
dw_fluffos_v2/lib/obj/plants/inherit/
dw_fluffos_v2/lib/obj/potions/
dw_fluffos_v2/lib/open/boards/
dw_fluffos_v2/lib/save/autodoc/
dw_fluffos_v2/lib/save/bank_accounts/
dw_fluffos_v2/lib/save/boards/frog/
dw_fluffos_v2/lib/save/books/bed_catalog/
dw_fluffos_v2/lib/save/creators/
dw_fluffos_v2/lib/save/mail/
dw_fluffos_v2/lib/save/mail/p/
dw_fluffos_v2/lib/save/soul/data/
dw_fluffos_v2/lib/save/tasks/
dw_fluffos_v2/lib/save/vaults/
dw_fluffos_v2/lib/secure/cmds/lord/
dw_fluffos_v2/lib/secure/config/
dw_fluffos_v2/lib/secure/items/
dw_fluffos_v2/lib/secure/player/
dw_fluffos_v2/lib/soul/
dw_fluffos_v2/lib/soul/i/
dw_fluffos_v2/lib/soul/j/
dw_fluffos_v2/lib/soul/k/
dw_fluffos_v2/lib/soul/o/
dw_fluffos_v2/lib/soul/q/
dw_fluffos_v2/lib/soul/to_approve/
dw_fluffos_v2/lib/soul/u/
dw_fluffos_v2/lib/soul/v/
dw_fluffos_v2/lib/soul/wish_list/
dw_fluffos_v2/lib/soul/y/
dw_fluffos_v2/lib/soul/z/
dw_fluffos_v2/lib/std/creator/
dw_fluffos_v2/lib/std/effects/
dw_fluffos_v2/lib/std/effects/attached/
dw_fluffos_v2/lib/std/effects/external/
dw_fluffos_v2/lib/std/effects/fighting/
dw_fluffos_v2/lib/std/effects/other/
dw_fluffos_v2/lib/std/environ/
dw_fluffos_v2/lib/std/guilds/
dw_fluffos_v2/lib/std/hospital/
dw_fluffos_v2/lib/std/house/
dw_fluffos_v2/lib/std/house/onebedhouse/
dw_fluffos_v2/lib/std/house/onebedhut/
dw_fluffos_v2/lib/std/house/tworoomflat/
dw_fluffos_v2/lib/std/languages/
dw_fluffos_v2/lib/std/liquids/
dw_fluffos_v2/lib/std/nationality/
dw_fluffos_v2/lib/std/nationality/accents/
dw_fluffos_v2/lib/std/nationality/accents/national/
dw_fluffos_v2/lib/std/nationality/accents/regional/
dw_fluffos_v2/lib/std/npc/goals/
dw_fluffos_v2/lib/std/npc/goals/basic/
dw_fluffos_v2/lib/std/npc/goals/misc/
dw_fluffos_v2/lib/std/npc/inherit/
dw_fluffos_v2/lib/std/npc/plans/
dw_fluffos_v2/lib/std/npc/plans/basic/
dw_fluffos_v2/lib/std/outsides/
dw_fluffos_v2/lib/std/races/shadows/
dw_fluffos_v2/lib/std/room/basic/topography/
dw_fluffos_v2/lib/std/room/controller/
dw_fluffos_v2/lib/std/room/controller/topography/
dw_fluffos_v2/lib/std/room/furniture/games/
dw_fluffos_v2/lib/std/room/furniture/inherit/
dw_fluffos_v2/lib/std/room/inherit/carriage/
dw_fluffos_v2/lib/std/room/inherit/topography/
dw_fluffos_v2/lib/std/room/punishments/
dw_fluffos_v2/lib/std/room/topography/area/
dw_fluffos_v2/lib/std/room/topography/iroom/
dw_fluffos_v2/lib/std/room/topography/milestone/
dw_fluffos_v2/lib/std/shadows/
dw_fluffos_v2/lib/std/shadows/attached/
dw_fluffos_v2/lib/std/shadows/curses/
dw_fluffos_v2/lib/std/shadows/disease/
dw_fluffos_v2/lib/std/shadows/fighting/
dw_fluffos_v2/lib/std/shadows/room/
dw_fluffos_v2/lib/std/shops/controllers/
dw_fluffos_v2/lib/std/shops/objs/
dw_fluffos_v2/lib/std/shops/player_shop/
dw_fluffos_v2/lib/std/shops/player_shop/office_code/
dw_fluffos_v2/lib/std/socket/
dw_fluffos_v2/lib/www/
dw_fluffos_v2/lib/www/external/autodoc/
dw_fluffos_v2/lib/www/external/java/telnet/Documentation/
dw_fluffos_v2/lib/www/external/java/telnet/Documentation/images/
dw_fluffos_v2/lib/www/external/java/telnet/examples/
dw_fluffos_v2/lib/www/external/java/telnet/tools/
dw_fluffos_v2/lib/www/pics/
dw_fluffos_v2/lib/www/secure/creator/
dw_fluffos_v2/lib/www/secure/editors/
dw_fluffos_v2/lib/www/secure/survey_results/
dw_fluffos_v2/win32/
/**
 * This file contains all the methods and fun things actually run combat.
 * @author Ceres
 *
 *
 * TODO: Add offensive & defensive criticals -- broken limbs etc.
 *
 */
inherit "/std/living/corpse";
inherit "/std/weapon_logic";

#include <attack_messages.h>
#include <tasks.h>
#include <player.h>
#include <living.h>
#include <weapon.h>
#ifdef COMBAT_SPEED
#undef COMBAT_SPEED
#endif
#include <combat.h>

#ifdef ABS
#undef ABS
#endif
#define ABS(n) (n) < 0 ? -(n) : (n)

// 2 == verbose, 1 == brief, 0 == none.
#define DEBUG 1
#undef USE_SURRENDER

class tactics _tactics;

#ifdef USE_SURRENDER
class surrender_information _surrender;
#endif

nosave class combat_information _combat;
nosave string tmpzone; // needed for function pointer.
nosave int _callout;

object *query_attacker_list();
void stop_all_fight();
int query_distance(object);
int set_distance(object, int);
void adjust_action_defecit(int amount);
private void init_tactics();
#ifdef USE_SURRENDER
object *query_surrenderers();
#endif

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

  init_tactics();
  _combat = new(class combat_information,
                in_combat : 0,
                protectors : ({ }),
                defenders : ({ }),
                attacker_list : ([ ]),
                hunting_list : ([ ]),
                specials : ({ })
                );

#ifdef USE_SURRENDER
  _surrender = new(class surrender_information,
                   to : ({ }),
                   from : ({ }),
                   refusers : ({ }));
#endif
}

private void init_tactics() {
  if(!classp(_tactics) || sizeof(_tactics) != 8) {
    _tactics = new(class tactics,
                   attitude : "neutral",
                   response : "neutral",
                   parry : "both",
                   attack : "both",
                   parry_unarmed : 0,
                   mercy : "ask",
                   focus_zone : 0,
                   ideal_distance : 0);
  }
}

#ifdef DEBUG
class combat_information query_combat() { return _combat; }
#ifdef USE_SURRENDER
class surrender_information query_surrender() { return _surrender; }
#endif
#endif

/** @ignore yes */
void combat_debug(string fmt, mixed args ...) {
  if(userp(this_object()))
    this_object()->event_inform(this_object(),
                                sprintf("%s " + fmt,
                                        this_object()->query_name(), args ...),
                                      "combat-debug", this_object());
  event(environment(this_object()), "inform",
        sprintf("%s " + fmt, this_object()->query_name(), args ...),
        "combat-debug", this_object());
}

/**
 * Decide if we will attack this round or not.  This function can be
 * overloaded if necessary. It also checks for the property "cannot attack"
 * on this object. If the property is set the object cannot attack.
 *
 * @return 1 if we want to, 0 if we don't.
 */
int query_can_attack() {
  if(this_object()->query_property(PASSED_OUT) ||
     this_object()->query_property("dead") ||
     this_object()->query_auto_loading() ||
     this_object()->query_hp() < 0 ||
     this_object()->query_casting_spell() ||
     (userp(this_object()) && !interactive(this_object())) ||
     this_object()->query_property("cannot attack"))
    return 0;

  return 1;
}

/**
 * Decide if we are able to defend ourselves this time or not.
 * This function can be overloaded if necessary. It also checks for the
 * property "cannot defend"
 * on this object. If the property is set the object cannot defend.
 *
 * @return 1 if we want to, 0 if we don't.
 */
int query_can_defend() {
  if(this_object()->query_property(PASSED_OUT) ||
     this_object()->query_property("dead") ||
     this_object()->query_auto_loading() ||
     this_object()->query_hp() < 0 ||
     this_object()->query_casting_spell() ||
     (userp(this_object()) && !interactive(this_object())) ||
     this_object()->query_property("cannot defend"))
    return 0;

  return 1;
}

/**
 * Choose an opponent and attack them.
 *
 * This method must fill in the opponent variable. If no opponent is chosen
 * the attack will be aborted.
 *
 * PRE: Attacker information has been filled in.
 * POST: opponent must be chosen or the attack will be aborted.
 *
 * @param class attack
 * @return class attack
 */
protected class attack choose_opponent(class attack att) {
  mixed *opponents, opponent;
  int i;
  
#ifdef USE_DISTANCE
  mixed tmp;
#endif

  // TODO: More advanced opponent selection.
  opponents = map(keys(_combat->attacker_list),
                  (: $1 && objectp($1) ? $1 : find_player($1) :));
  opponents = filter(opponents, (: $1 && environment($1) == environment($2) &&
                                 $1->query_attackable() :), att->attacker);
  
#ifdef USE_SURRENDER
  // Remove surrendered people from the opponent list so we don't kill
  // anyone during the surrender.
  foreach(opponent in query_surrenderers()) {
    if(userp(opponent))
      opponent = opponent->query_name();
    opponents -= ({ opponent });
  }
#endif

  // we pick the closest opponent.
  if(!sizeof(opponents))
    return att;

  // Attack whoever we're concentrating on.
  if(interactive(this_object()) &&
     objectp(att->attacker_concentrating) && 
     member_array(att->attacker_concentrating, opponents) != -1)
    opponent = att->attacker_concentrating;
  else {
#ifdef USE_DISTANCE
    opponent = opponents[0];
    foreach(tmp in opponents)
      if(_combat->attacker_list[tmp] < _combat->attacker_list[opponent])
        opponent = tmp;
#endif

    // Three different NPC algorithms. good_fighter should use better
    // ones.
    switch(random(3)) {
    case 2:
      opponent = opponents[0];
      for(i=0; i<sizeof(opponents); i++)
        if((opponent->query_hp() / 10 + random(100)) >
           (opponents[i]->query_hp() / 10 + random(100)))
          opponent = opponents[i];
      break;
    case 1:
      opponent = opponents[0];
      for(i=0; i<sizeof(opponents); i++)
        if((opponent->query_hp() / 10 + random(100)) <
           (opponents[i]->query_hp() / 10 + random(100)))
          opponent = opponents[i];
      break;
    default:
      opponent = opponents[random(sizeof(opponents))];
    }
  }

#ifdef USE_DISTANCE
  // Set the opponent and distance.
  att->distance = _combat->attacker_list[opponent];
#endif

  if(stringp(opponent))
    att->opponent = find_player(opponent);
  else
    att->opponent = opponent;

  // Just in case they disappeared somehow.
  if(environment(att->opponent) != environment(this_object()))
    return att;

  // Automatically concentrate on someone if they're the only opponent.
  if(sizeof(opponents) == 1)
    _combat->concentrating = att->opponent;

  return att;
}

/** @ignore yes */
void clear_protection(object attacker, object protector) {
  if(attacker->remove_protector(protector))
    protector->event_combat(protector,
                            "You stop protecting "+ attacker->the_short() +
                            " as "+ attacker->query_pronoun() +
                            " moves to attack "+
                            "you!\n", ({ }), 0);
  if(attacker->remove_defender(protector))
    protector->event_combat(protector, "You stop defending "+
                            attacker->the_short() + " as "+
                            attacker->query_pronoun() +" moves to attack "+
                            "you!\n", ({ }), 0);
}

/**
 * This method is used to choose who will defend this attack. Typically this
 * will be the person who was attacked, however it could be someone else in
 * some circumstances.
 *
 * PRE: attacker, attack and defender information has been set.
 * POST: defender and person_hit must be set.
 *
 * @param class attack
 * @return class attack
 */
class attack choose_defender(class attack att) {
  object *protectors, *defenders;

  // If the combat system (or a special) has already chosen a protector
  // we won't override it.
  if(!att->person_hit) {
    // People who will protect (ie. jump infront of an attack)
    protectors = filter((att->opponent)->query_protectors(),
                        (: $1 && environment($1) == environment($2) &&
                         $1->query_protect() && !pk_check($1, $2) :),
                        att->attacker);
    
    if(sizeof(protectors)) {
      protectors->attack_by(att->attacker);
      
      att->person_hit = protectors[random(sizeof(protectors))];
#if DEBUG == 1
      combat_debug("picked %s to protect", (att->person_hit)->query_name());
#endif
    }
  }
  
  // If the combat system (or a special) has already chosen a defender
  // we won't override it.
  if(!att->defender) {
    defenders = filter((att->opponent)->query_defenders(),
                       (: $1 && environment($1) == environment($2) &&
                        !pk_check($1, $2) :), att->attacker);

    // Make sure local defenders join in the fight.
    if(sizeof(defenders)) {
      defenders->attack_by(att->attacker);
      defenders = filter(defenders, (: $1->query_defend() :));
    }

    // See if we have someone to defend.
    if(sizeof(defenders)) {
      att->defender = defenders[random(sizeof(defenders))];
#if DEBUG == 1
      combat_debug("picked %s to defend", (att->defender)->query_name());
#endif
    }
  }
  
  // Do the defaults if we haven't been protected or defended.
  if(!att->person_hit)
    att->person_hit = att->opponent;
  if(!att->defender)
    att->defender = att->opponent;
  
  return att;
}

/**
 * Is this object attackable or not?
 *
 * @return 1 or 0 for true or false.
 */
int query_attackable() {
  if(this_object()->query_property(PASSED_OUT) ||
     this_object()->dont_attack_me() ||
     this_object()->attack_inhibit() ||
     this_object()->query_property("dead") ||
     this_object()->query_auto_loading() ||
     this_object()->query_hp() < 0 ||
     (userp(this_object()) && !interactive(this_object())))
    return 0;
  return 1;
}

/**
 * Is this object prepared to try to defend someone else at this time?
 *
 * @return 1 or 0 for true or false.
 */
int query_defend() {
  if(!query_attackable() || this_object()->query_casting_spell() ||
     this_object()->query_specific_gp("fighting") < 1)
    return 0;
  return ((_tactics->response == "parry" || _tactics->response == "both") &&
          _combat->action_defecit < (COMBAT_ACTION_TIME * 4));
}

/**
 * Is this object prepared to try to protect someone else at this time?
 * @return 1 or 0 for true or false.
 */
int query_protect() {
  if(!query_attackable() || this_object()->query_casting_spell() ||
     this_object()->query_specific_gp("fighting") < 1)
    return 0;
  return (_combat->action_defecit < (COMBAT_ACTION_TIME * 4));
}


/**
 * Determine which attack they'll use.
 *
 * This picks a weapon, gets the attack data and the weapon skill and
 * determines how much the attack will cost.
 *
 * PRE: attacker info, opponent and defender info have been set.
 * POST: attack weapon, attack skill, attack data, and attack_cost must
 * be set.
 *
 * @param class attack
 * @return class attack
 */
protected class attack choose_attack(class attack att) {
  object weapon, *weapons;
  string hand, *tmp;
  int perc, which_attack, i;
  class tactics tactics;
  mixed *attacks;
  string zone;
  float sz;

  tactics = att->attacker_tactics;

  if(!this_object()->query_can_attack())
    return att;

  // Depending on their tactics they will go further into defecit in order
  // to attack.
  if(!OFFENSIVE_DEFECITS[tactics->attitude])
    tactics->attitude = "neutral";

  if(att->attacker_defecit > OFFENSIVE_DEFECITS[tactics->attitude])
    return att;

  weapons = (att->attacker)->query_holding();
  tmp = (att->attacker)->query_limbs();

  // Do they have a preferred attacking hand?
  hand = (att->attacker_tactics)->attack;
  if(hand && hand != "both") {
    for(i=0; i<sizeof(tmp); i++)
      if(tmp[i] == hand + " hand")
        break;
    if(!weapons[i]) // they're going to attack unarmed.
      weapons = ({ });
    else if(weapons[i]->query_weapon()) // they'll attack with the weapon
      weapons = ({ weapons[i] });
  }

  // Make sure we have a unique list of weapons if we're going to have to
  // pick one.
  if(sizeof(weapons) > 1)
    weapons = uniq_array(filter(weapons, (: $1 && $1->query_weapon() :)));
  
  // Pick the attacking weapon from what's left by using the most appropriate
  // for distance.
  switch(sizeof(weapons)) {
  case 0:
    
    weapon = att->attacker;
    break;
  case 1:
    // Tend to attack with the weapon in the left hand.
    weapon = weapons[0];
    break;
  default:
#ifdef USE_DISTANCE
    // Pick the weapon that best fits the combat distance we're at right now.
    weapon = weapons[0];
    foreach(wtmp in weapons)
      if(ABS(REACH + weapon->query_length() - att->distance) >
         ABS(REACH + wtmp->query_length() - att->distance))
        weapon = wtmp;
#else
    weapon = weapons[random(sizeof(weapons))];
#endif
  }

  att->attack_weapon = weapon;

  // Calculate their attack percentage.

  // For the moment I've just given a simple strength bonus so that stronger
  // players hit slightly harder.
  perc = 75 + (att->attacker)->query_str() + (att->attacker)->query_dex();

  // The heavier the weapon the harder it is to hit hard with it.
  if(weapon != att->attacker)
    perc -= weapon->query_weight() / 2;

  if(perc < 25)
    perc = 25;
  
  attacks = weapon->weapon_attacks(perc, att->defender);
  
  if(!sizeof(attacks)) {
#if DEBUG == 2
    combat_debug("No attacks with %O, terminating [%d, %O].", weapon,
                 perc, att->defender);
#endif
    return att;
  }

  // Choose an attack at random from the list of attacks. This should probably
  // be a weighted average (since each attack has a %age chance)
  which_attack = random(sizeof(attacks) / AT_ARRAY_SIZE);
  attacks = attacks[(which_attack * AT_ARRAY_SIZE)..];

  // No free hands? Can't attack with your hands then.
  if(attacks[AT_NAME] == "hands" && !(att->attacker)->query_free_limbs())
    return att;
  
  att->attack_data = attacks;
  att->attack_skill = ATTACK_SKILL + attacks[AT_SKILL];

  // Choose an attack zone. If they've chosen one then they get a -ve modifier
  // If they haven't set a preference then choose one at random.
  if((att->attacker_tactics)->focus_zone == "upper body") {
    tmp = ((att->opponent)->query_race_ob())->query_target_zones();
    zone = tmp[random(sizeof(tmp) / 2)];
  } else  if((att->attacker_tactics)->focus_zone == "lower body") {
    zone = tmp[random(sizeof(tmp) / 2) + sizeof(tmp)/2];
  } else if((att->attacker_tactics)->focus_zone &&
     (att->attacker_tactics)->focus_zone != "none") {
    // generic zone.
    zone = (att->attacker_tactics)->focus_zone;
    // map to the races zone
    zone = ((att->opponent)->query_race_ob())->map_target_zone(zone);
    att->target_zone = zone;    
  } else {
    if(att->attack_data[AT_SKILL] == "unarmed") {
      // for humanoids punches will typically be upper body while
      // kicks will typically be lower body (depending on target size).
      if(att->attack_data[AT_NAME] == "hands")
        sz = ((att->attacker)->query_height() * 2) /
          (att->opponent)->query_height();
      else if(att->attack_data[AT_NAME] == "feet")
        sz = ((att->attacker)->query_height() / 2) /
          (att->opponent)->query_height();
    } else
      sz = to_float((att->attacker)->query_height()) /
        to_float((att->opponent)->query_height());

    // The relative sizes of combatants lead to more attacks higher or lower
    // on the body.
    tmp = ((att->opponent)->query_race_ob())->query_target_zones();
    if(sizeof(tmp)) {
      if(sz > 1.5)
        zone = tmp[random(random(sizeof(tmp)))];
      if(sz < 0.75) {
        i = random(sizeof(tmp) + 10);
        if(i > sizeof(tmp)-1)
          i = sizeof(tmp) - 1;
        zone = tmp[i];
      } else {
        zone = tmp[random(sizeof(tmp))];
      }
    }
  }
  att->target_zone = zone;

  return att;
}

/**
 * Will the defender defend or not? If so, with what skill and weapon and at
 * what action cost?
 *
 * PRE: all attack data and opponent and defender data have been set.
 * POST: defense type, skill and weapon must be set. If defense_action
 * is "none" the defender will have a big negative modifier.
 *
 * @param class attack
 * @return class attack
 */
class attack choose_defense(class attack att) {
  object defender;
  string response, *hands, hand;
  object *weapons;
  class tactics tactics;
  int i, which;
#ifdef USE_DISTANCE
  int move_cost;
#endif  

  defender = att->defender;
  att->defense_action = "none";
  att->defense_skill = DODGE;
  att->defense_weapon = 0;

  tactics = att->defender_tactics;

  // Defenders (when the defender is not the opponent) have already
  // decided that they'll take on this action regardless of their
  // defecit thus only check defecits if opponent == defender.
  if(defender == att->opponent) {
    if(!defender->query_can_defend())
      return att;
    
    if(!DEFENSIVE_DEFECITS[tactics->attitude])
      tactics->attitude = "neutral";

    if(att->defender_defecit > DEFENSIVE_DEFECITS[tactics->attitude])
      return att;
  }

#ifdef USE_DISTANCE
  else if(att->distance != defender->query_distance(att->attacker) &&
            ABS(defender->query_distance(att->attacker)) >
            MOVE_DISTANCE[tactics->attitude]) {
    // If we have a defender or protector and they aren't in the right
    // position they need to move there and that will cost them some extra
    // actions. This _could_ lead to them spending all their time running
    // back and forth if they try to protect/defend too many people.

    defender->set_distance(att->attacker, att->distance);
    move_cost = MOVE_COST;
    move_cost -= defender->query_skill_bonus(MOVE_SKILL) / 50;
    if(move_cost < 0)
      move_cost = 0;

    (att->opponent)->event_combat(att->opponent, defender->one_short() +
                                  " moves to " +
                                  (att->person_hit == defender ?
                                   "protect" : "defend" ) + " you.\n",
                                  ({ }), 0);
    defender->event_combat(defender, "You move to " +
                           (att->person_hit == defender ?
                            "protect" : "defend") + " " +
                           (att->opponent)->one_short() + ".\n",
                           ({ }), 0);
    event(environment(att->opponent), "combat", defender->one_short() +
          " moves to " + (att->person_hit == defender ? "protect" : "defend") +
          " " + (att->opponent)->one_short() + ".\n",
          ({ att->opponent, defender, att->person_hit }), 1);
  }
#endif

  response = tactics->response;

  // Defenders have two choices, parry or don't, dodging isn't any use.
  if(defender != att->opponent) {
    if(response == "dodge")
      return att;
    response = "parry";
  } else if(!response || response == "neutral") // opponents get a free choice.
    response = ({"parry", "dodge"})[random(2)];

  if(response == "parry") {
    weapons = (object *)defender->query_holding();

    // Figure out which weapon index is the hand they prefer to parry with.
    hand = (att->defender_tactics)->parry;
    which = -1;
    hands = (att->defender)->query_limbs();
    for(i=0; i<sizeof(hands); i++)
      if(hands[i][0..sizeof(hand)-1] == hand)
        which = i;
    
    // If we found a preferred hand then try to parry with it, or parry
    // unarmed if they want to.
    if(which != -1) {
      if(weapons[which]) {
        att->defense_action = "parry";
        att->defense_skill = WEAPON_PARRY;
        att->defense_weapon = weapons[which];
      } else if((att->defender_tactics)->parry_unarmed) {
        att->defense_action = "parry";
        att->defense_skill = UNARMED_PARRY;
        att->defense_weapon = att->defender;
        att->defense_limb = (att->defender)->query_limbs()[which];
      }
    } else {
      // If there's no preferred hand and they have weapons they parry with
      // whichever they have a weapon in. Otherwise, they parry unarmed if
      // they want to do that, otherwise it's no defense.
      weapons -= ({ 0 });
      if(!sizeof(weapons)) {
        if((att->defender_tactics)->parry_unarmed) {
          att->defense_action = "parry";
          att->defense_skill = UNARMED_PARRY;
          att->defense_weapon = att->defender;
        }
      } else {
        att->defense_action = "parry";
        att->defense_skill = WEAPON_PARRY;
        att->defense_weapon = weapons[random(sizeof(weapons))];
      }
    }

  } else {
    att->defense_action = "dodge";
    att->defense_skill = DODGE;
    att->defense_weapon = 0;
  }

  return att;
}

/**
 * This method calculates the modifier to the attack.
 *
 * It will take into account all the basic stuff that makes the attack
 * harder or easier.
 *
 * PRE: All attack and response data has been filled in.
 * POST: The offensive modifier must be set.
 *
 * @param class attack
 * @return class attack
 */
protected class attack calc_attack_modifier(class attack att) {
  object attacker, attack_weapon, *holding;
  int mod, wep, hld, lght, mntd, hlth, brdn, dist, tact, targ, oth;
  int tmp, tmp2, limbs, dex, num, actions;
  string zone, *attackers;
  
  attacker = att->attacker;
  dex = attacker->query_dex();
  attack_weapon = att->attack_weapon;
  mod = wep = hld = lght = mntd = hlth = brdn = dist = tact = 0;

  if(attack_weapon != attacker) {
    /*
     * Find out how heavy the weapon is, how many hands they're using to hold
     * it, how many other weapons they're holding, which hand they're
     * holding their many hands they're using to hold the weapon and decrease
     * a based on that.
     */
    holding = attacker->query_holding();

    // tmp -- how many hands they're using to hold the attacking weapon
    // tmp2 -- weight factor for other held items.
    limbs = tmp2 = 0;
    for(num = 0; num < sizeof(holding); num++) {
      if(holding[num] == attack_weapon)
        limbs++;
      else if(holding[num]) {
        if((holding[num])->id("shield"))
          tmp2 += (holding[num])->query_weight() / 5;
        else
          tmp2 += (holding[num])->query_weight();
      }
    }

    // If they're holding a second weapon but aren't parrying with it
    // then their negative modifier for it is lower.
    switch((att->attacker_tactics)->response) {
    case "both":
      tmp2 = (tmp2 * 3) / 2;
      break;
    case "dodge":
      // To hold multiple weapons your dex must outweigh the weight of the
      // secondary weapons.
      if(tmp2 < dex)
        tmp2 /= 2;
    }
    
    // weapon weight.
    wep = attack_weapon->query_weight() + (tmp2 / 2);
    
    // 2/3 for one hand. 2 / 4 for two hands. 2 / 5 for three etc.
    wep /= (limbs + 1);
    wep -= (att->attacker)->query_str();
    
    // If their weapon weight factor adds up to more than their strength
    // they take a negative modifier.
    if(wep > 0 ) {
      wep = - to_int(pow(wep, 1.4));
    } else
      wep = 0;


    // If you're attacking with your off-hand (ie. not your prime hand)
    // you take a negative modifier if your dex is low.
    // (index 0 is your prime hand)
    if(dex < 14 && sizeof(holding) && holding[0] != attack_weapon)
      hld = dex - 14;
    
  } else {
    wep =  (dex * 2) - attacker->query_burden();
  }

  // light levels
  switch(attacker->check_dark(environment(attacker)->query_light())) {
  case -2:
  case 2:
    lght = -50;
    break;
  case -1:
  case 1:
    lght = -25;
    break;
  }

  // mountedness
  mntd = 0;

  // health (up to -50)
  hlth -= (50 - (attacker->query_hp() * 50) / attacker->query_max_hp());
  
  // fatigue (up to -25)
  tmp = attacker->query_specific_gp("fighting");
  if(tmp < -50)
    hlth += -25;
  else if(tmp < 0)
    hlth += (tmp / 2);

  // burden (up to -33)
  brdn -= (attacker->query_burden() / 3);

#ifdef USE_DISTANCE
  // distance from opponent & length of weapon.
  if(att->attack_weapon == att->attacker)
    dist = -3 * ABS(REACH - att->distance);
  else
    dist = -3 * ABS(REACH + (att->attack_weapon)->query_length() -
                    att->distance);
#endif

  // Bonus for offensive tactics.
  switch((att->attacker_tactics)->attitude) {
  case "insane":
    tact = 25;
    break;
  case "offensive":
    tact = 15;
    break;
  case "defensive":
    tact = -25;
    break;
  case "wimp":
    tact = -50;
    break;
  }

  switch(att->attacker_tactics->focus_zone) {
  case 0:
  case "none":
    targ = 0;
    break;
  case "upper body":
  case "lower body":
    targ -= 25;
    break;
  default:
    tmp = 0;
    foreach(zone in ((att->opponent)->query_race_ob())->query_target_zones())
      if(zone == att->target_zone)
        tmp++;
    num = sizeof(((att->opponent)->query_race_ob())->query_target_zones());
    // between -0 & -25 depending on what %age of the body the target zone
    // accounts for.
    targ -= ((num - tmp) * 25 / num);
  }

  if(targ < -25) {
    debug_printf("Targ too small (%s) %d. num: %d, tmp: %d\n",
                 (att->attacker_tactics)->focus_zone, targ, num,tmp);
    targ = -25;
  }

  // If you're further down the attacker list than the second person
  // you take a negative modifier since only the first 2-4 people
  // can really get a hit in on someone.
  attackers = filter((att->opponent)->query_attacker_list(),
                     (: $1 && environment($1) == environment(this_object()):));
  num = member_array(this_object(), attackers);
  if(num > 1)
    num += -25 * num;

  if((att->attacker_tactics)->attack == "both")
    oth += 5;
  
  mod = wep + hld + lght + mntd + hlth + brdn + dist + tact + targ + oth + num;
  att->attack_modifier += mod;

  // Calculate how much time an attack takes.
  actions += ATTACK_COST;
  
  // heavier weapons are slower to attack with.
  if(attack_weapon != att->attacker)
    actions += (sqrt(attack_weapon->query_weight()) * 3) / (limbs + 1);

  // Holding two weapons gives a slight advantage.
  if(sizeof((att->attacker)->query_weapons()) > 1)
    actions -= ATTACK_COST / 4;
  
  // more skilled opponents are more fluid in their movements.
  actions -= ((att->attacker)->query_skill_bonus(att->attack_skill) +
              (att->attacker)->query_skill_bonus(TACTICAL_SKILL)) / 50;
  
  if(actions < ATTACK_COST / 5)
    actions = ATTACK_COST / 5;
  else if(actions > ATTACK_COST * 2)
    actions = ATTACK_COST * 2;
  att->attack_cost = actions;

#if DEBUG == 1
  combat_debug("Attack %s (%s): wep: %d hld: %d lght: %d mntd: %d "
               "hlth: %d brdn: %d dist: %d tact: %d targ: %d oth: %d "
               "num: %d = %d [%d] (actions: %d %d)",
               att->attack_data[AT_NAME],
               (att->attack_weapon)->query_name(),
               wep, hld, lght, mntd, hlth, brdn, dist, tact, targ, oth, num,
               mod, att->attack_modifier, actions, att->attacker_defecit);
#endif
  return att;
}

/**
 * This method calculates the modifier to the defense.
 *
 * It will take into account all the basic stuff that makes the defense
 * harder or easier.
 *
 * PRE: All attack and response data has been filled in.
 * POST: The defensive modifier must be set.
 *
 * @param class attack
 * @return class attack
 */
class attack calc_defense_modifier(class attack att) {
  object defender, attacker, attack_weapon, defense_weapon, *holding;
  int mod, wep, wght, dist, brdn, hnd, lght, hlth, tact, prot, oth;
  int tmp, num, dex, limbs, actions;
  
  mod = wep = wght = dist = brdn = hnd = lght = hlth = tact = prot = oth = 0;
  defender = att->defender;
  defense_weapon = att->defense_weapon;
  dex = defender->query_dex();
  attacker = att->attacker;
  attack_weapon = att->attack_weapon;

  // Parry & Dodge are somewhat different. Obviously parrying depends on
  // using an object to block while dodge requires moving ones whole body.
  // They should each have strengths and weaknesses.
  switch(att->defense_action) {
  case "parry":
    if(defense_weapon != defender && defense_weapon) {

      holding = defender->query_holding();

      // weapon weight
      if(defense_weapon->id("shield"))
        wep = defense_weapon->query_weight() / 5;
      else
        wep = defense_weapon->query_weight() * 2;

      /*
       * Find out how many hands they're using to hold the weapon/shield
       */
      limbs = 0;
      for(num = 0; num < sizeof(holding); num++)
        if(holding[num] && holding[num] == defense_weapon)
          limbs++;

      // 2/2 for one hand. 2 / 3 for two hands. 2 / 4 for three etc.
      wep /= (limbs + 2);

      if(wep > defender->query_str())
        wep = - to_int(pow(wep - defender->query_str(), 1.3));

      // relative weight of attackers weapon to ours?
      if(attack_weapon != attacker)
        wght = 2 * (defense_weapon->query_weight() -
                    attack_weapon->query_weight());
      else
        wght = defense_weapon->query_weight() / 2;

      // Having a heavier defensive weapon doesn't help much, if at all.
      // However, having a light defensive weapon is a disadvantage.
      if(wght > 5)
        wght = 5;
      
#ifdef USE_DISTANCE
      dist = - ABS(REACH + defense_weapon->query_length() - att->distance);
#endif
    } else {
#ifdef USE_DISTANCE
      dist = - ABS(REACH - att->distance);
#endif
    }

    brdn = -(defender->query_burden() / 3);

    if(sizeof(holding)) {
      // If you're parrying with your off-hand (ie. not your prime hand)
      // you take a negative modifier.
      if(dex < 14 && holding[0] != defense_weapon)
        hnd = dex - 14;
    
      // If you're parrying with the same weapon you attack with
      // (ie. you're parrying & only holding one weapon that's a -ve.
      holding = uniq_array(filter(holding, (: $1 :)));
      if(dex  < 16 && sizeof(holding) == 1)
        hnd += dex - 16;
    }
    break;

  case "dodge":
    // All actions get the 1/3rd burden.
    brdn = -(defender->query_burden() / 3);

    // Dodgers get a dex mitigated burden modifier
    if(dex < (defender->query_burden() / 2))
      brdn -= dex - (defender->query_burden() / 2);
    
    if(att->attack_weapon != att->attacker)
      wght = ((att->attack_weapon)->query_weight() / 10);
    break;
  }

  // light levels / visibility of opponent
  if(!(att->attacker)->query_visible(defender))
    lght = -100;
  else {
    switch(defender->check_dark(environment(defender)->query_light())) {
    case -2:
    case 2:
      lght = -50;
      break;
    case -1:
    case 1:
      lght = -25;
      break;
    }
  }
  
  // mountedness

  // health (up to -25)
  hlth = - (25 - (defender->query_hp() * 25) / defender->query_max_hp());

  // fatigue (up to -25)
  tmp = defender->query_specific_gp("fighting");
  if(tmp < -50)
    hlth += -25;
  else if(tmp < 0)
    hlth += (tmp / 2);

  // Bonus for defensive tactics.
  switch((att->defender_tactics)->attitude) {
  case "insane":
    tact = -50;
    break;
  case "offensive":
    tact = -25;
    break;
  case "defensive":
    tact = 15;
    break;
  case "wimp":
    tact = 25;
    break;
  }

  // Protecting is a -15 modifier. Defending is an extra -15. This is because
  // it's easier to just jump in the way than to actually parry for someone.
  prot = 0;
  if(defender != att->opponent) {
    if(defender != att->person_hit) {
      // defending someone else with a shield is very tricky since shields
      // are setup only to protect you.
      if(defense_weapon->id("shield")) 
        prot -= 15;
      prot -= 15;
    }
    prot -= 15;
  }

  // Small bonus for response of both.
  if((att->defender_tactics)->response == "both")
    oth += 5;
  
  if(defender->query_casting_spell())
    oth -= 25;

  mod = wep + wght + dist + brdn + hnd + lght + hlth + tact + prot + oth;

  att->defense_modifier += mod;

  // Calculate how much time a parry/dodge takes.
  if(att->defense_action != "none") {
    actions = DEFENSE_COST;
    actions -= ((att->defender)->query_skill_bonus(att->defense_skill) +
      (att->attacker)->query_skill_bonus(TACTICAL_SKILL)) / 50;

    // heavier weapons are slower to parry with.
    if(att->defense_weapon && att->defense_weapon != att->defender) {
      if((att->defense_weapon)->id("shield"))
        actions += sqrt(att->defense_weapon->query_weight() / 4);
      else {
        actions += sqrt((att->defense_weapon->query_weight() * 2) /
                        (limbs + 1));

        if(sizeof((att->defender)->query_weapons()) > 1)
          actions -= DEFENSE_COST / 4;
      }
      
    } else if(att->defense_action == "dodge")
      actions += sqrt(defender->query_burden());

    if(actions < DEFENSE_COST / 5)
      actions = DEFENSE_COST / 5;
    else if(actions > DEFENSE_COST * 2)
      actions = DEFENSE_COST * 2;
    
    att->defense_cost = actions;
  }

#if DEBUG == 1
  combat_debug("Defense %s (%s): wep: %d wght: %d dist: %d "
               "brdn: %d hnd: %d lght: %d hlth: %d tact: %d prot: %d "
               "oth: %d = %d [%d] (actions: %d %d)",
               att->defense_action,
               att->defense_weapon ? (att->defense_weapon)->query_name() : "",
               wep, wght, dist, brdn, hnd, lght, hlth, tact, prot, oth,
               mod, att->defense_modifier, actions, att->defender_defecit);
#endif

  return att;
}

/****************************************************************************
 * Now we're into the damage phase.
 ****************************************************************************/

/**
 * After a successful attack (or possibly a parried one) calculate the
 * amount of damage done to the opponent.
 *
 * This method must fill in the amount of damage to be done and may adjust
 * the time taken too.
 *
 * PRE: the outcome of an attack must have been determined.
 * POST: damage must be filled in.
 */
protected class attack calc_damage(class attack att) {
  int damage, weapon_damage;
  object attacker;

  // Damage is a weighted average of the weapon damage and the
  // players skill.

  attacker = att->attacker;
  damage = att->attack_data[AT_DAMAGE];
  weapon_damage = damage;

  // This rather hampers unarmed, perhaps it should be removed.
  if(att->attack_weapon != attacker)
    damage = sqrt(damage * attacker->query_skill_bonus(att->attack_skill));

  if(damage > 3 * weapon_damage)
    damage = 3 * weapon_damage;

  // As the combat gets slower so the damage goes up to compensate otherwise
  // people heal faster than they take damage.
  damage *= COMBAT_SPEED;

  att->damage = damage;

  switch(att->result) {
  case OFFAWARD:
  case OFFWIN:
    // Critical attacks do 2x damage, exceptional attacks 1.5x
    switch(att->degree) {
    case TASKER_CRITICAL:
      att->damage *= 2;
      break;
    case TASKER_EXCEPTIONAL:
      att->damage = (att->damage * 3) / 2;
    case TASKER_MARGINAL:
      att->damage /= 2;
      break;
    }
    break;

  case DEFAWARD:
  case DEFWIN:
    // A Critical or Exceptional parry can cause the opponent to use up
    // extra combat actions since the defense pushed the attackers weapon
    // out of position or brought the defender into a better position.
    switch(att->degree) {
    case TASKER_CRITICAL:
      att->defense_cost = 0;
      break;
    case TASKER_EXCEPTIONAL:
      att->defense_cost /= 2;
      break;
    case TASKER_MARGINAL:
      att->defense_cost *= 2;
      break;
    }
    // All damage is always stopped.
    att->damage = 0;
  }

  combat_debug("Damage: %d %d",
               att->damage,
               attacker->query_skill_bonus(att->attack_skill));
  return att;
}

/**
 * Calculate how much of the damage is saved by the armour.
 *
 * PRE: all information has been filled in except the amount of damage
 * stopped by armour, and which piece of armour did the stopping.
 * POST: The damage stopped and which armour did it is filled in.
 *
 * @param class attack
 * @return class attack
 */
class attack calc_armour_protection(class attack att) {
  object person_hit;
  string armour_zone, damage_type;

  if(!att->damage || (att->result != OFFWIN && att->result != OFFAWARD))
    return att;

  person_hit = att->person_hit;

  // Specials/Criticals may change this calculation.
  armour_zone = (person_hit->query_race_ob())->
    map_armour_zone(att->target_zone);

  damage_type = att->attack_data[AT_TYPE];
  if(damage_type == "unarmed")
    damage_type = "blunt";

  att->armour_stopped = (int)person_hit->query_ac(damage_type, att->damage,
                                                  armour_zone);
  if(att->armour_stopped < 0) {
    debug_printf("armour stopped %d for %s %d %s",
                 att->armour_stopped, damage_type, att->damage, armour_zone);
    att->armour_stopped = 0;
  }
  
  return att;
}

/**
 * Do damage to the attacking & defending weapons.
 *
 * PRE: all information has been filled in
 * POST: the weapons involved have taken appropriate damage.
 *
 * @param class attack
 * @return class attack
 */
protected class attack damage_weapon(class attack att) {
  int off_damage, def_damage;

  switch(att->result) {
  case OFFAWARD:
  case OFFWIN:
    // The weapon hit the opponents armour.
    if(att->armour_stopped && att->degree)
      off_damage = att->armour_stopped / att->degree;
    // No def_damage needed since the query_ac() call damages the armour
    // directly (which is bad, but there we are).
    break;
  case DEFWIN:
  case DEFAWARD:
    // The defender successfully defended. If they parried then their
    // parrying weapon may take some damage and the attacking
    // weapon too.
    if(att->defense_action == "parry" && att->damage && att->degree) {
      off_damage = att->damage * (att->degree - 1);
      def_damage = att->damage / att->degree;
    }
    break;
  }

  if(off_damage) {
    if(att->attack_weapon == att->attacker)
      if((att->attack_weapon)->query_hp() > (off_damage / 10))
        (att->attack_weapon)->adjust_hp(- (off_damage / 10), att->defender,
                                        att->defense_weapon,
                                        "hitting someone.");
    else if(function_exists("hit_weapon", att->attack_weapon))
      (att->attack_weapon)->hit_weapon(off_damage, att->attack_data[AT_SKILL]);
    else
      (att->attack_weapon)->do_damage(att->attack_data[AT_SKILL], off_damage);
  }

  if(def_damage) {
    if(att->defense_weapon == att->defender)
      (att->defense_weapon)->adjust_hp(- def_damage, att->attacker,
                                       att->attack_weapon,
                                       "parrying an attack with a limb.");
    else if(function_exists("hit_weapon", att->defense_weapon))
      (att->defense_weapon)->hit_weapon(def_damage,
                                        att->attack_data[AT_SKILL]);
    else
      (att->defense_weapon)->do_damage(att->attack_data[AT_SKILL], def_damage);
  }

  return att;
}

/**
 * Prepare the result messages.
 *
 * This method prepares the attack and defense messages setting up the 5
 * element arrays of att_mess & def_mess. The elements are as follows:
 * 0 - the attacker, 1 - the opponent, 2 - everyone else, 3 - the defender
 * 4 - the person hit
 *
 * PRE: The attack has been done and the amount of damage done and stopped
 * has been calculated.
 * POST: The 5 element att_mess & def_mess arrays are populated.
 *
 * @param class attack
 */
protected class attack prepare_messages(class attack att) {
  object attacker, opponent, defender, person_hit;
  string *verb, def_short, margin;
  string *def_mess, *att_mess;
  int damage, armour_stopped;

  attacker = att->attacker;
  opponent = att->opponent;
  defender = att->defender;
  person_hit = att->person_hit;
  damage = att->damage;
  armour_stopped = att->armour_stopped;

  // Set attack part of the message.
  switch(att->result) {
  case OFFAWARD:
    tell_object(att->attacker,
                "%^YELLOW%^"+ replace(({ "You feel "
                "that your skill with $weapon$ has increased.", "You "
                "feel more able to use $weapon$.", "You seem to be a "
                "step closer to mastering $weapon$." })[random(3)],
                "$weapon$", (att->attack_weapon == att->attacker ?
                "unarmed combat" : (string)(att->attack_weapon)->a_short())) +
                "%^RESET%^\n" );

  case OFFWIN:
    if(person_hit == opponent) {
      att_mess = ATTACK_MESSAGE->query_message(damage - armour_stopped,
                                               att->attack_data[AT_SKILL],
                                               opponent, attacker,
                                               att->attack_data[AT_NAME],
                                               att->attack_weapon,
                                               att->target_zone);
      
      if(sizeof(att_mess) != 3) {
        att_mess = ({"You hit "+opponent->the_short()+"",
                       attacker->the_short()+" hits you",
                       attacker->the_short()+" hits "+opponent->the_short()});
      }
      
      att_mess += ({ att_mess[M_SPECTATORS], att_mess[M_SPECTATORS] });
    } else {
      att_mess = ATTACK_MESSAGE->query_message(damage - armour_stopped,
                                               att->attack_data[AT_SKILL],
                                               opponent, attacker,
                                               att->attack_data[AT_NAME],
                                               att->attack_weapon,
                                               att->target_zone);
      
      if(sizeof(att_mess) != 3) {
        att_mess = ({"You almost hit "+opponent->the_short()+"",
                       attacker->the_short()+" almost hits you",
                       attacker->the_short()+" almost hits "+
                       opponent->the_short()});
      }
      
      att_mess += ({ att_mess[M_SPECTATORS], att_mess[M_SPECTATORS] });

      att_mess[M_ATTACKER] += " but at the last second " +
        person_hit->the_short() + " leaps in and protects " +
        opponent->query_objective();
      att_mess[M_OPPONENT] += " but at the last second " +
        person_hit->the_short() + " leaps in and protects you";
      att_mess[M_SPECTATORS] += " but at the last second " +
        person_hit->the_short() + " leaps in and protects " +
        opponent->query_objective();
      if(defender == person_hit)
        att_mess[M_DEFENDER] += " but at the last second " +
          person_hit->the_short() + " leap in and protect " +
          opponent->query_objective();
      else
        att_mess[M_DEFENDER] += " but at the last second " +
          person_hit->the_short() + " leaps in and protects " +
          opponent->query_objective();

      att_mess[M_PERSON_HIT] += " but at the last second you leap in and "
        "protect " + opponent->query_objective();
    }
    break;
  case DEFAWARD:
  case DEFWIN:
    att_mess = ATTACK_MESSAGE->query_message(0,
                                             att->attack_data[AT_SKILL],
                                             opponent, attacker,
                                             att->attack_data[AT_NAME],
                                             att->attack_weapon,
                                             att->target_zone);
    if(sizeof(att_mess) != 3) {
      att_mess = ({"You try to hit "+opponent->the_short()+"",
                     attacker->the_short()+" tries to hit you",
                     attacker->the_short()+" tries to hit "+
                     opponent->the_short()});
    }
    att_mess += ({ att_mess[M_SPECTATORS], att_mess[M_SPECTATORS] });
    break;
  }

  def_mess = allocate(5);
  def_mess[M_ATTACKER] = def_mess[M_OPPONENT] = def_mess[M_SPECTATORS] =
    def_mess[M_DEFENDER] = def_mess[M_PERSON_HIT] = "";

  // Now set the response part ie. what happened after the attack.
  switch(att->result) {
  case OFFAWARD:
  case OFFWIN:
    // The attack was successful, so we add armour messages if appropriate.
    if(armour_stopped && armour_stopped > damage / 3) {
      if(objectp(att->stopped_by)) {
        def_mess[M_ATTACKER] = " but " + (att->stopped_by)->poss_short();
        def_mess[M_OPPONENT] = def_mess[M_ATTACKER];
        def_mess[M_SPECTATORS] = def_mess[M_ATTACKER];
        def_mess[M_DEFENDER] = def_mess[M_ATTACKER];
        def_mess[M_PERSON_HIT] = def_mess[M_ATTACKER];
      } else if(att->stopped_by) {
        def_mess[M_ATTACKER] = " but " + person_hit->query_possessive() +
          " " + att->stopped_by;

        if(person_hit == opponent)
          def_mess[M_OPPONENT] = " but your " + att->stopped_by;
        else
          def_mess[M_OPPONENT] = def_mess[M_ATTACKER];

        def_mess[M_SPECTATORS] = def_mess[M_ATTACKER];

        if(person_hit == defender)
          def_mess[M_DEFENDER] = " but your " + att->stopped_by;
        else
          def_mess[M_DEFENDER] = def_mess[M_ATTACKER];

        def_mess[M_PERSON_HIT] = " but your " + att->stopped_by;
      }

      // armour protected them, at least partly
      if(!damage || armour_stopped >= damage) {
        def_mess[M_ATTACKER] += " absorbs all of";
        def_mess[M_OPPONENT] += " absorbs all of";
        def_mess[M_SPECTATORS] += " absorbs all of";
        def_mess[M_DEFENDER] += " absorbs all of";
        def_mess[M_PERSON_HIT] += " absorbs all of";
      } else if(armour_stopped > (damage * 2 / 3)) {
        def_mess[M_ATTACKER] += " absorbs most of";
        def_mess[M_OPPONENT] += " absorbs most of";
        def_mess[M_SPECTATORS] += " absorbs most of";
        def_mess[M_DEFENDER] += " absorbs most of";
        def_mess[M_PERSON_HIT] += " absorbs most of";
      } else {
        def_mess[M_ATTACKER] += " absorbs some of";
        def_mess[M_OPPONENT] += " absorbs some of";
        def_mess[M_SPECTATORS] += " absorbs some of";
        def_mess[M_DEFENDER] += " absorbs some of";
        def_mess[M_PERSON_HIT] += " absorbs some of";
      }
      def_mess[M_ATTACKER] += " the blow";
      def_mess[M_OPPONENT] += " the blow";
      def_mess[M_SPECTATORS] += " the blow";
      def_mess[M_DEFENDER] += " the blow";
      def_mess[M_PERSON_HIT] += " the blow";
    }
    break;

  case DEFAWARD:
    if(att->defense_action == "parry") {
      if(att->defense_weapon && (att->defense_weapon)->query_weapon())
        verb = ({ "parry", "parries", "parrying" });
      else
        verb = ({ "block", "blocks", "blocking" });

      tell_object(att->defender,
                  "%^YELLOW%^"+ replace( ({ "You move more surely "
               "than you thought you could in $verbing$ $attacker$'s attack",
               "You just manage to $verb$ $attacker$'s attack, but you'll "
               "know better next time", "You feel better at $verbing$ as "
               "you $verb$ $attacker$'s attack" })[ random( 3 ) ],
               ({ "$verb$", verb[ 0 ], "$verbing$", verb[ 2 ], "$attacker$",
               (string)(att->attacker)->the_short() }) ) +".%^RESET%^\n" );
    } else if(att->defense_action == "dodge") {
      tell_object(att->defender,
                   "%^YELLOW%^"+ replace( ({ "You move more nimbly "
               "than you thought you could in dodging $attacker$", "You "
               "managed to predict $attacker$'s attack, letting you dodge it "
               "more easily", "You feel better at dodging as you avoid "
               "$attacker$'s attack" })[ random( 3 ) ], "$attacker$",
               (string)(att->attacker)->the_short() ) +".%^RESET%^\n" );
    }
  case DEFWIN:
    // The defense was successful so we print parry/dodge messages as
    // appropriate.
    def_mess = allocate(5);
    def_mess[M_ATTACKER] = "";
    def_mess[M_OPPONENT] = "";
    def_mess[M_SPECTATORS] = "";
    def_mess[M_DEFENDER] = "";
    def_mess[M_PERSON_HIT] = "";
    switch(att->degree) {
    case TASKER_MARGINAL:
      margin = "just ";
      break;
    case TASKER_EXCEPTIONAL:
      margin = "deftly ";
      break;
    case TASKER_MARGINAL:
      margin = "easily ";
      break;
    default:
      margin = "";
      break;
    }

    if(att->defense_action == "parry" && att->defense_weapon) {
      if(defender == opponent)
        def_mess[M_ATTACKER] = " but " + defender->query_pronoun() + " " +
          margin;
      else
        def_mess[M_ATTACKER] = " but " + defender->the_short() + " " + margin;

      if(defender == opponent)
        def_mess[M_OPPONENT] = " but you " + margin;
      else
        def_mess[M_OPPONENT] = def_mess[M_ATTACKER];

      def_mess[M_SPECTATORS] = def_mess[M_ATTACKER];
      def_mess[M_DEFENDER] = " but you " + margin;
      def_mess[M_PERSON_HIT] = def_mess[M_ATTACKER];

      if(att->defense_weapon != defender &&
         !(att->defense_weapon)->id("shield")) {
        def_mess[M_ATTACKER] += "parries";
        if(defender == opponent)
          def_mess[M_OPPONENT] += "parry";
        else
          def_mess[M_OPPONENT] += "parries";

        def_mess[M_SPECTATORS] += "parries";
        def_mess[M_DEFENDER] += "parry";
        if(att->person_hit == att->defender)
          def_mess[M_PERSON_HIT] += "parry";
        else
          def_mess[M_PERSON_HIT] += "parries";
      } else {
        def_mess[M_ATTACKER] += "blocks";
        if(defender == opponent)
          def_mess[M_OPPONENT] += "block";
        else
          def_mess[M_OPPONENT] += "blocks";

        def_mess[M_SPECTATORS] += "blocks";
        def_mess[M_DEFENDER] += "block";
        if(att->person_hit == att->defender)
          def_mess[M_PERSON_HIT] += "block";
        else
          def_mess[M_PERSON_HIT] += "blocks";
      }

      if(att->defense_weapon != defender) {
        def_short = (att->defense_weapon)->short();
      } else if(att->defense_limb) {
        def_short = att->defense_limb;
      } else
        def_short = (defender->query_race_ob())->map_target_zone("hands");


      def_mess[M_ATTACKER] += " the blow with " +
        defender->query_possessive() + " " + def_short;
      if(defender == opponent)
        def_mess[M_OPPONENT] += " the blow with your " + def_short;
      else
        def_mess[M_OPPONENT] += " the blow with " +
          defender->query_possessive() + " " + def_short;

      def_mess[M_SPECTATORS] += " the blow with " +
        defender->query_possessive() + " " + def_short;

      def_mess[M_DEFENDER] += " the blow with your " + def_short;

      if(defender == person_hit)
        def_mess[M_PERSON_HIT] += " the blow with your " + def_short;
            else
        def_mess[M_PERSON_HIT] += " the blow with " +
          defender->query_possessive() + " " + def_short;

    } else if(att->defense_action == "dodge") {
      def_mess[M_ATTACKER] = " but " + defender->query_pronoun() + " " +
        margin + "dodges out of the way";
      def_mess[M_OPPONENT] = " but you " + margin + "dodge out of the way";
      def_mess[M_SPECTATORS] = " but " + defender->query_pronoun() + " " +
        margin + "dodges out of the way";
    } else {
      def_mess[M_ATTACKER] = " but, although unable to defend, " +
        defender->query_pronoun() + " somehow avoids the attack";
      def_mess[M_OPPONENT] = " but, although unable to defend, you somehow "
        "avoid the attack";
      def_mess[M_SPECTATORS] = " but, although unable to defend, " +
        defender->query_pronoun() + " somehow avoids the attack";
    }
  }
  att->attack_messages = att_mess;
  att->defense_messages = def_mess;

  if(att->result == DEFAWARD || att->result == DEFWIN || !damage ||
     armour_stopped >= damage)
    att->verbose = 1;

  return att;
}

/**
 * Write out the attack/defense messages.
 *
 * PRE: all information has been filled in
 * POST: the appropriate messages have been written out.
 *
 * @param class attack
 * @return class attack
 */
protected class attack write_messages(class attack att) {
  int verbose;
  
  verbose = att->verbose;
  (att->attacker)->event_combat(att->attacker,
                                att->attack_messages[M_ATTACKER] +
                                att->defense_messages[M_ATTACKER] + ".\n",
                                ({ }), verbose);
  (att->opponent)->event_combat(att->opponent,
                                att->attack_messages[M_OPPONENT] +
                                att->defense_messages[M_OPPONENT] + ".\n",
                                ({ }), verbose);
  if(att->defender != att->opponent &&
     sizeof(att->attack_messages) > M_DEFENDER)
    (att->defender)->event_combat(att->defender,
                                  att->attack_messages[M_DEFENDER] +
                                  att->defense_messages[M_DEFENDER] + ".\n",
                                  ({ }), verbose);
  if(att->person_hit != att->opponent && att->person_hit != att->defender &&
     sizeof(att->attack_messages) > M_PERSON_HIT)
    (att->person_hit)->event_combat(att->person_hit,
                                    att->attack_messages[M_PERSON_HIT] +
                                    att->defense_messages[M_PERSON_HIT] +
                                    ".\n", ({ }), verbose);

  event(environment(att->attacker), "combat",
        att->attack_messages[M_SPECTATORS] +
        att->defense_messages[M_SPECTATORS] + ".\n",
        ({ att->attacker, att->opponent, att->defender, att->person_hit }),
        verbose);
  return att;
}

/**
 * Adjust the time left, combat actions and gp used by this attack.
 * This calls adjust_action_defecit in both the attacker and defender
 * as appropriate.
 *
 * @param class attack
 */
protected void adjust_actions(class attack att) {
  int tmp;

  // Reduce the attackers time left.
  if((att->attacker)->query_time_left() > 0)
    (att->attacker)->adjust_time_left(-DEFAULT_TIME);

  // Reduce their actions.
  (att->attacker)->adjust_action_defecit(att->attack_cost);
  (att->opponent)->adjust_action_defecit(att->defense_cost);

  // Extra cost for defenders
  if(att->defender != att->opponent)
    (att->defender)->adjust_action_defecit(DEFENSE_COST);

  // Extra cost for protectors
  if(att->person_hit != att->opponent)
    (att->person_hit)->adjust_action_defecit(DEFENSE_COST);

  // Reduce their GP
  tmp = ATTACK_GP[(att->attacker_tactics)->attitude];
  (att->attacker)->adjust_gp(-tmp);

  if(att->defender != att->opponent) {
    tmp = DEFENDER_GP[(att->defender_tactics)->attitude];
    (att->defender)->adjust_gp(-tmp);
  } else if(att->defense_action != "none") {
    tmp = DEFENSE_GP[(att->defender_tactics)->attitude];
    (att->opponent)->adjust_gp(-tmp);
  }

  if(att->person_hit != att->opponent) {
    tmp = DEFENDER_GP[(att->person_hit)->query_combat_attitude()];
    (att->person_hit)->adjust_gp(-tmp);
  }
}

/**
 * This function is called as part of the post attack processing and performs
 * the checks to see if someone can move closer or further away from their
 * opponent.
 *
 * @param class attack
 * @return class attack
 * @see after_attack()
 */
protected class attack perform_movement(class attack att) {
  int ideal, move_cost, closer;

  // Figure out if they will attempt to move-in or move-away from their
  // opponent.
  ideal = DISTANCES[(att->attacker_tactics)->ideal_distance];

  // If the difference between the actual and ideal distance is greater than
  // the acceptable difference defined for our attitude we'll attempt to
  // move in or out to compensate.
  if(ABS(ideal - att->distance) >
     MOVE_DISTANCE[(att->attacker_tactics)->attitude]) {

    if(att->distance > ideal)
      closer = 1;
    move_cost = MOVE_COST;
    move_cost -= (att->attacker)->query_skill_bonus(MOVE_SKILL) / 30;

    if(move_cost > 0)
      att->attack_cost += MOVE_COST;
    
    switch(TASKER->compare_skills(att->attacker, MOVE_SKILL,
                                  att->defender, MOVE_SKILL,
                                  att->attack_modifier -
                                  att->defense_modifier,
                                  TM_CONTINUOUS, TM_CONTINUOUS, 0)) {
    case OFFAWARD:
      tell_object(att->attacker,
                  "%^YELLOW%^You feel that your tactical skill has increased."
                  "%^RESET%^\n");
    case OFFWIN:
      if(closer) {
        (att->attacker)->event_combat(att->attacker, "You move in closer to " +
                                      (att->opponent)->one_short() + ".\n",
                                      ({ }), 0);
        (att->opponent)->event_combat(att->opponent,
                                      (att->attacker)->one_short() +
                                      " moves in closer to you.\n", ({ }), 0);

        event(environment(att->attacker), "combat",
              (att->attacker)->one_short() +
              " moves in closer to " + (att->opponent)->one_short() + ".\n",
              ({ att->attacker, att->opponent }), 1);

        set_distance(att->opponent, query_distance(att->opponent) -
                     DISTANCE_STEP);
        (att->opponent)->set_distance(att->attacker,
                                      query_distance(att->opponent));
      } else {
        (att->attacker)->event_combat(att->attacker,
                                      "You move further away from " +
                                      (att->opponent)->one_short() + ".\n",
                                      ({ }), 0);
        (att->opponent)->event_combat(att->opponent,
                                     (att->attacker)->one_short() +
                                     " moves further away from you.\n",
                                     ({ }), 0);
        event(environment(att->attacker), "combat",
              (att->attacker)->one_short() + " moves further away from " +
              (att->opponent)->one_short() + ".\n",
              ({ att->attacker, att->opponent }), 1);
        set_distance(att->opponent, query_distance(att->opponent) +
                     DISTANCE_STEP);
        (att->opponent)->set_distance(att->attacker,
                                      query_distance(att->opponent));
      }
      break;
    case DEFAWARD:
      tell_object(att->opponent,
                  "%^YELOW%^You feel that your tactical skill has increased."
                  "%^RESET%^\n");
    case DEFWIN:
      if(closer) {
        (att->attacker)->event_combat(att->attacker,
                                      "You try to move in closer to " +
                                      (att->opponent)->one_short() +
                                      " but cannot find "
                                      "an opportunity.\n", ({ }), 1);
        (att->opponent)->event_combat(att->opponent,
                                      (att->attacker)->one_short() +
                                      " tries to move in closer to you "
                                      "but you don't give " +
                                      (att->attacker)->query_objective() +
                                      " an opporunity.\n", ({ }), 1);
        event(environment(att->attacker), (att->attacker)->one_short() +
              " tries to move in closer to " +
              (att->opponent)->one_short() + " but " +
              (att->opponent)->query_pronoun() + " doesn't give " +
              (att->attacker)->query_objective() + " an opportunity.\n",
              ({ att->attacker, att->opponent }), 1);
      } else {
        (att->attacker)->event_combat(att->attacker,
                                      "You try to move further away from  " +
                                      (att->opponent)->one_short() +
                                      " but cannot find an opportunity.\n",
                                      ({ }), 1);
        (att->opponent)->event_combat(att->opponent,
                                      (att->attacker)->one_short() +
                                      " tries to move further away from you "
                                      "but you don't give " +
                                      (att->attacker)->query_objective() +
                                      " an opporunity.\n", ({ }) , 1);
        event(environment(att->attacker), "combat",
              (att->attacker)->one_short() +
              " tries to move further away from " +
              (att->opponent)->one_short() + " but " +
              (att->opponent)->query_pronoun() + " doesn't give " +
              (att->attacker)->query_objective() + " an opportunity.\n",
              ({ att->attacker, att->opponent }), 1);
      }
    }
  }

  return att;
}

/**
 * This function is called after it's all over and done (just before the
 * costs are charged). It can be used to do any special outcomes that
 * we may decide on.
 * Note that if you override this function in a special be absolutely certain
 * you know what you're doing if you return R_ABORT or R_DONE. Use of either
 * of these two could cause really nasty side-effects.
 *
 * @param class attack
 * @return class attack
 */
protected class attack after_attack(class attack att) {
  string *locations;
  int total, count, lperc, hperc;

#ifdef USE_DISTANCE
  if((att->result == OFFWIN || att->result == OFFAWARD) &&
     (att->attacker_tactics)->ideal_distance &&
     environment(att->attacker) == environment(att->opponent))
    att = perform_movement(att);
#endif

  adjust_actions(att);
  if((att->damage - att->armour_stopped) <= 0)
    return att;

  // Do weapon special attacks.
  if((environment(att->person_hit) == environment(att->attacker)) &&
     (att->result == OFFWIN || att->result == OFFAWARD)) {
    (att->attack_weapon)->attack_function(att->attack_data[AT_NAME],
                                          att->damage - att->armour_stopped,
                                          att->person_hit, att->attacker);
  }

  // Determine special injuries if enough damage was done.

  locations = (att->defender->query_race_ob())->query_target_zones();

  total = sizeof(locations);
  tmpzone = att->target_zone;
  locations = filter(locations, (: $1 == tmpzone :));
  count = sizeof(locations);
  lperc = (count * 100) / total;

  if(att->degree == TASKER_CRITICAL) {
    hperc = ((att->damage - att->armour_stopped) * 100) /
      (att->opponent)->query_max_hp();

    if(lperc < hperc) {
      switch(att->target_zone) {
      case "head":
        // Unconscious
        break;
      case "chest":
      case "stomach":
        break;
      case "arm":
        // broken arm.
        break;
      case "leg":
        // broken leg.
        break;
      }
    }
  }
  return att;
}

/**
 * Go through the list of the attacker & opponents specials and
 * call any that have registered for this stage.
 */
protected mixed *call_special(int stage, class attack att) {
  class combat_special *tmp;
  mixed *result;
  int i;

  if(!this_object()->query_no_specials()) {
    tmp = att->attacker_specials;
    for(i=0; i<sizeof(tmp); i++) {
      if((tmp[i]->type & T_OFFENSIVE) && (tmp[i]->events & stage)) {
        if(functionp(tmp[i]->callback))
          result = evaluate(tmp[i]->callback, stage, att, tmp[i]->data);
        else if(arrayp(tmp[i]->callback)) {
          if(!tmp[i]->callback[0] || !tmp[i]->callback[1]) {
            (att->attacker)->remove_special(tmp[i]->id);
          } else 
            result = call_other(tmp[i]->callback[0], tmp[i]->callback[1],
                                stage, att, tmp[i]->data);
        }
        
        if(sizeof(result)) {
          if(!classp(result[1])) {
            debug_printf("Error, call_special received invalid return "
                         "data. %O when calling %O stage %d\n", result,
                         tmp[i]->callback, stage);
            result[0] = R_REMOVE_ME | R_ABORT;
          }
                         
          if(result[0] & R_REMOVE_ME) {
            (att->attacker)->remove_special(tmp[i]->id);
          } else {
            // Set the data to be the new data in both the current attack
            // data & in the original info.
            tmp[i]->data = result[2];
            (att->attacker)->set_special_data(tmp[i]->id, result[2]);
          }
          
          if(result[0] & R_ABORT)
            return result;
          
          if((result[0] & R_DONE) || (result[0] & R_CONTINUE))
            break;
        }
      }
    }
  }

  if(att->defender && !(att->defender)->query_no_specials()) {
    tmp = att->defender_specials;
    for(i=0; i<sizeof(tmp); i++) {
      if((tmp[i]->type & T_DEFENSIVE) && (tmp[i]->events & stage)) {
        if(functionp(tmp[i]->callback))
          result = evaluate(tmp[i]->callback, stage, att, tmp[i]->data);
        else if(arrayp(tmp[i]->callback)) {
          if(!tmp[i]->callback[0] || !tmp[i]->callback[1]) {
            (att->defender)->remove_special(tmp[i]->id);
          } else 
            result = call_other(tmp[i]->callback[0], tmp[i]->callback[1],
                                stage, att, tmp[i]->data);
        }
        
        if(sizeof(result)) {
          if(!classp(result[1])) {
            debug_printf("Error, call_special received invalid return "
                         "data. %O when calling %O stage %d", result,
                         tmp[i]->callback, stage);
            result[0] = R_REMOVE_ME | R_ABORT;
          }

          if(result[0] & R_REMOVE_ME) {
            (att->defender)->remove_special(tmp[i]->id);
          } else {
            // Set the data to be the new data.
            (att->defender)->set_special_data(tmp[i]->id, result[2]);
          }
          
          if(result[0] & R_ABORT)
            return result;

          // Done or continue we stop processing specials.
          // R_PASSTHRU means try the next special.
          if((result[0] & R_DONE) || (result[0] & R_CONTINUE))
            break;
        }
      }
    }
  }
  return result;
}

/**
 * This performs the actual attack itself.
 * All attack data, everything we know about the attacker and the defender
 * is stored in the attack class which is passed into and back by every
 * function at every step of the attack.
 */
void do_attack() {
  class tasker_result result;
  class attack att;
  mixed *sp_result;
  int modifier, tmp;

  // This is required since some people have the old tactics settings :(
  init_tactics();
  if(_callout)
    _callout = remove_call_out("announce_intent");

  att = new(class attack,
            attacker : this_object(),
            attacker_tactics : this_object()->query_tactics(),
            attacker_specials : this_object()->query_specials(),
            attacker_concentrating : this_object()->query_concentrating(),
            attacker_defecit : this_object()->query_action_defecit(),
            attacker_last_opponent : this_object()->query_last_opponent(),
            attacker_last_weapon : this_object()->query_last_weapon(),
            attacker_last_action : this_object()->query_last_action(),
            attacker_last_result : this_object()->query_last_result(),
            repeat : 0
            );

  // choose opponent
  sp_result = call_special(E_OPPONENT_SELECTION, att);
  if(!sp_result || sp_result[0] & R_CONTINUE || sp_result[0] & R_PASSTHRU)
    att = this_object()->choose_opponent(att);
  else if(!(sp_result[0] & R_ABORT) && classp(sp_result[1]))
    att = (class attack)sp_result[1];
  else
    return;

  if(!(att->opponent) || !(att->opponent)->attack_by(att->attacker)) {
    combat_debug("No opponent chosen, aborting attack");
    return;
  }


  event(environment(att->attacker), "fight_in_progress", att->opponent);
  clear_protection(att->attacker, att->opponent);
  _combat->last_opponent = att->opponent;
  
  // This loop causes us to allow a defender to defend and if the defender
  // fails & isn't the opponent the opponent also gets a chance to defend.
  do {
    // Now lets figure out who will do the defense, the person being attacked
    // or someone/thing that wants to defend them.
    sp_result = call_special(E_DEFENDER_SELECTION, att);
    if(!sp_result || sp_result[0] & R_CONTINUE || sp_result[0] & R_PASSTHRU)
      att = (att->opponent)->choose_defender(att);
    else if(!(sp_result[0] & R_ABORT) && classp(sp_result[1]))
      att = (class attack)sp_result[1];
    else
      return;

    att->defender_tactics = (att->defender)->query_tactics();
    att->defender_concentrating = (att->defender)->query_concentrating();
    att->defender_last_opponent = (att->defender)->query_last_opponent();
    att->defender_last_action = (att->defender)->query_last_action();
    att->defender_last_result = (att->defender)->query_last_result();
    att->defender_defecit = (att->defender)->query_action_defecit();
    att->defender_specials = (att->defender)->query_specials();

    clear_protection(att->attacker, att->defender);

    // get weapon and attack data
    sp_result = call_special(E_ATTACK_SELECTION, att);
    if(!sp_result || sp_result[0] & R_CONTINUE || sp_result[0] & R_PASSTHRU)
      att = this_object()->choose_attack(att);
    else if(!(sp_result[0] & R_ABORT) && classp(sp_result[1]))
      att = (class attack)sp_result[1];
    else
      return;

    if(!att->attack_weapon || !sizeof(att->attack_data) || !att->attack_skill){
      combat_debug("Missing attack weapon, attack data or attack skill, "
                   "aborting attack.");
      return;
    }

    // set the 'last' data.
    _combat->last_action = att->attack_data[AT_NAME];
    _combat->last_weapon = att->attack_weapon;

    // will opponent defend? If so determine defense skill & weapon.
    sp_result = call_special(E_DEFENSE_SELECTION, att);
    if(!sp_result || sp_result[0] & R_CONTINUE || sp_result[0] & R_PASSTHRU)
      att = (att->defender)->choose_defense(att);
    else if(!(sp_result[0] & R_ABORT) && classp(sp_result[1]))
      att = (class attack)sp_result[1];
    else
      return;

    // Set the 'last' data.
    (att->opponent)->set_last_opponent(att->attacker);
    if(att->opponent != att->defender)
      (att->defender)->set_last_opponent(att->attacker);
    (att->defender)->set_last_action(att->defense_action);
    (att->defender)->set_last_weapon(att->defense_weapon);
    if(att->person_hit != att->opponent && att->person_hit != att->defender)
      (att->person_hit)->set_last_opponent(att->attacker);
    
    // determine attack modifier
    sp_result = call_special(E_ATTACK_MODIFIER, att);
    if(!sp_result || sp_result[0] & R_CONTINUE || sp_result[0] & R_PASSTHRU) {
      att = this_object()->calc_attack_modifier(att);
    } else if(!(sp_result[0] & R_ABORT) && classp(sp_result[1])) {
      att = (class attack)sp_result[1];
    } else
      return;

    // determine defense modifier
    // If they aren't defending they just get a big fat minus.
    if(!att->defense_action || att->defense_action == "none")
      att->defense_modifier += -1000;
    else {
      sp_result = call_special(E_DEFENSE_MODIFIER, att);
      if(!sp_result || sp_result[0] & R_CONTINUE || sp_result[0] & R_PASSTHRU)
        att = (att->defender)->calc_defense_modifier(att);
      else if(!(sp_result[0] & R_ABORT) && classp(sp_result[1]))
        att = (class attack)sp_result[1];
      else
        return;
    }

    tmp = att->attack_modifier - att->defense_modifier + BALANCE_MOD;
    modifier = tmp;
    // This flattens the modifier out a bit preventing crazy modifiers
    // against extreme skills.
    if(modifier > 25)
      modifier = sqrt(modifier * 25);
    else if(modifier < -25)
      modifier = -sqrt(-modifier * 25);
    
    // compare_skills
    result = TASKER->compare_skills(att->attacker, att->attack_skill,
                                    att->defender, att->defense_skill,
                                    modifier,
                                    TM_CONTINUOUS, TM_CONTINUOUS, 1);
    att->result = result->result;
    att->degree = result->degree;

    // Set the last_result data.
    _combat->last_result = att->result;
    (att->defender)->set_last_result(att->result);

    // If they got hit while being defended then reduce the defenders actions
    // and let them defend themselves.
    if((att->result == OFFWIN || att->result == OFFAWARD) &&
       att->defender != att->opponent && !att->repeat) {
      // charge the defender
      (att->defender)->adjust_action_defecit(att->defense_cost);
      (att->defender)->adjust_gp(-(DEFENSE_GP[att->defender_tactics->attitude]));

      // mark them to defend themselves.
      att->defender = att->opponent;
      att->repeat = 1;
    } else
      att->repeat = 0;
    
  } while(att->repeat);

  // calculate damage
  sp_result = call_special(E_DAMAGE_CALCULATION, att);
  if(!sp_result || sp_result[0] & R_CONTINUE || sp_result[0] & R_PASSTHRU)
    att = this_object()->calc_damage(att);
  else if(!(sp_result[0] & R_ABORT) && classp(sp_result[1]))
    att = (class attack)sp_result[1];
  else
    return;

  // see how much damage was stopped by armour.
  sp_result = call_special(E_ARMOUR_CALCULATION, att);
  if(!sp_result || sp_result[0] & R_CONTINUE || sp_result[0] & R_PASSTHRU)
    att = (att->opponent)->calc_armour_protection(att);
  else if(!(sp_result[0] & R_ABORT) && classp(sp_result[1]))
    att = (class attack)sp_result[1];
  else
    return;

#if DEBUG == 1
  combat_debug("Outcome: mod: %d, res: %d, deg: %d dam: %d arm: %d",
               att->attack_modifier - att->defense_modifier + BALANCE_MOD,
               att->result, att->degree, att->damage, att->armour_stopped);
#endif

  // We'll only damage people if the attack succeeded.
  if(att->result == OFFWIN || att->result == OFFAWARD) {
    // and which armour stopped the attack.
    att->stopped_by = (att->person_hit)->query_stopped();

#if DEBUG == 2
    combat_debug("hit: %s, weapon: %s damage: %d, armour: %d, reducing hp by "
                 "%d to %d",
                 (att->person_hit)->query_name(),
                 (att->attack_weapon)->query_name(),
                 att->damage,
                 att->armour_stopped,
                 (att->damage - att->armour_stopped),
                 (att->person_hit)->query_hp());
#endif
  }

  // Prepare the offense/defense messages.
  att = this_object()->prepare_messages(att);
  if(sizeof(att->attack_messages) != 5 || sizeof(att->defense_messages) != 5) {
    debug_printf("No attack or defense messages.\n");
    return;
  }
    
  // Write out the offense/defense messages.
  sp_result = call_special(E_WRITE_MESSAGES, att);
  if(!sp_result || sp_result[0] & R_CONTINUE || sp_result[0] & R_PASSTHRU)
    att = this_object()->write_messages(att);
  else if(!(sp_result[0] & R_ABORT) && classp(sp_result[1]))
    att = (class attack)sp_result[1];
  else
    return;

  // Damage the person hit
  if(att->damage - att->armour_stopped > 0)
    (att->person_hit)->adjust_hp(-(att->damage - att->armour_stopped),
                                 att->attacker, att->attack_weapon,
                                 _combat->last_action);

  // damage the weapon.
  sp_result = call_special(E_WEAPON_DAMAGE, att);
  if(!sp_result || sp_result[0] & R_CONTINUE || sp_result[0] & R_PASSTHRU)
    att = this_object()->damage_weapon(att);
  else if(!(sp_result[0] & R_ABORT) && classp(sp_result[1]))
    att = (class attack)sp_result[1];
  else
    return;

  // Do post attack stuff.
  sp_result = call_special(E_AFTER_ATTACK, att);
  if(!sp_result || sp_result[0] & R_CONTINUE || sp_result[0] & R_PASSTHRU)
    att = this_object()->after_attack(att);
  else if(!(sp_result[0] & R_ABORT) && classp(sp_result[1]))
    att = (class attack)sp_result[1];
  else
    return;
}

/*
 * Figure out who should be in our attacker list and who should be in our
 * hunting list.
 */
void recalc_hunting_list() {
  mixed ob;
  object tmp;

  tmp = 0;
  foreach(ob in keys(_combat->attacker_list)) {
    if(stringp(ob) && find_player(ob))
      tmp = find_player(ob);
    else if(ob && objectp(ob))
      tmp = ob;

    if(!tmp) {
      // This removes the zeros left by dested objects.
      _combat->attacker_list = filter_mapping(_combat->attacker_list,(: $1 :));
    } else if(tmp->query_property("dead") ||
              !environment(tmp) ||
               base_name(environment(tmp)) == "/room/rubbish") {
      map_delete(_combat->attacker_list, ob);
    } else if(environment(tmp) != environment() ||
              !tmp->query_visible(this_object())) {
      // Add to hunting list, remove from attacker list.
      _combat->hunting_list[ob] = time();
      map_delete(_combat->attacker_list, ob);
    }
  }

  tmp = 0;
  foreach(ob in keys(_combat->hunting_list)) {
    if(stringp(ob) && find_player(ob))
      tmp = find_player(ob);
    else if(ob && objectp(ob))
      tmp = ob;

    if(!tmp) {
      // This removes the zeros left by dested objects.
      _combat->hunting_list = filter_mapping(_combat->hunting_list, (: $1 :));
      map_delete(_combat->hunting_list, ob);
    } else if(tmp->query_property("dead") ||
              base_name(environment(tmp)) == "/room/rubbish") {
      map_delete(_combat->hunting_list, ob);
    } else if(environment(tmp) == environment() &&
              tmp->query_visible(this_object())) {
      // If they are here and we can see them move them from the hunted
      // list to the attacker list.
      _combat->attacker_list[ob] = INITIAL_DISTANCE;
      map_delete(_combat->hunting_list, ob);
    } else if(_combat->hunting_list[ob] < time() - HUNTING_TIME) {
      // If we've been hunting them too long stop hunting them.
      this_object()->event_combat(this_object(), "You stop hunting " +
                                  tmp->one_short() + ".\n", ({ }), 1);
      map_delete(_combat->hunting_list, ob);
    }
  }
}

/**
 * Display the hitpoint monitor.
 */
void monitor_points() {
   int hp;
   int max;
   string colour;

#ifdef UNUSED
   this_object()->clear_gp_info();
   if ( (int)this_object()->adjust_gp( -1 ) < 0 )
     return;
#endif

   hp = (int)this_object()->query_hp();
   if(hp < 0)
     hp = 0;
   max = (int)this_object()->query_max_hp();
   switch ( ( hp * 100 ) / max) {
      case 50 .. 100 :
         colour = "%^GREEN%^";
         break;
      case 20 .. 49 :
         colour = "%^YELLOW%^";
         break;
      default :
         colour = "%^RED%^";
   }
   tell_object( this_object(), colour +"Hp: "+ hp +" (" + max + ") Gp: "+
         (int)this_object()->query_gp() +" (" + this_object()->query_max_gp() +
         ") Xp: " + this_object()->query_xp() + "%^RESET%^\n" );
}

/** @ignore yes */
void announce_intent(object opponent) {
  int difficulty = 50;
  object *things, ob;

  if(environment(opponent) != environment(this_object()))
    return;
  
  switch(this_object()->check_dark((int)environment(this_object())->query_light())) {
   case -2:
   case 2:
     difficulty *= 4;
     break;
   case -1:
   case 1:
     difficulty *= 2;
   }

   things = ({ });
   foreach(ob in filter(all_inventory(environment(this_object())),
                        (: $1 &&living($1) :))) {
     switch(TASKER->perform_task(ob, "other.perception", difficulty, TM_FREE)){
     case AWARD:
       tell_object(ob, "%^YELLOW%^You feel very perceptive."
                   "%^RESET%^\n");
     case SUCCEED:
       if(interactive(ob) && !ob->query_verbose("combat"))
         things += ({ ob });
       break;
     case FAIL:
       things += ({ ob });
     }
   }
   
   opponent->add_message((string)this_object()->one_short() +
                      " $V$0=moves,move$V$ aggressively towards you!\n",
                      ({ }));
   event(environment(opponent), "see", (string)this_object()->one_short() +
          " $V$0=moves,move$V$ aggressively towards "+
          (string)opponent->one_short() +"!\n", this_object(),
          ({ opponent, this_object() }) + things);
}

/**
 * This method is called when combat starts.  You can overload this function
 * to make your NPC do things at the start of combat.
 * You do not need to call ::start_combat() since this is just a stub function.
 *
 * @param opponent The first opponent to be attacked.
 */
void start_combat(object opponent) { return; }

/**
 * This method is called when combat ends.  You can overload this function
 * to make your NPC do things at the end of combat.
 * You do not need to call ::end_combat() since this is just a stub function.
 */
void end_combat() { return; }

/** @ignore yes */
void heart_beat() {
  int i;

  // are they in combat?
  if(!_combat->in_combat)
    return;

  recalc_hunting_list();

  if(!sizeof(keys(_combat->attacker_list)) &&
     !sizeof(keys(_combat->hunting_list))) {
    // Not in combat so remove any non-continuous specials.
    for(i=0; i<sizeof(_combat->specials); i++)
      if(!((_combat->specials[i])->type & T_CONTINUOUS))
        _combat->specials -= ({ (_combat->specials)[i] });

    // mark as not in combat
    _combat->in_combat = 0;
    adjust_action_defecit(-MAX_ACTION_DEFECIT);
    end_combat();
    return;
  }

  // Are they in a fit state to fight & do they have anyone to fight?
  if(!query_attackable() || !sizeof(keys(_combat->attacker_list)))
    return;

  // They get part of their combat actions here (assuming they aren't
  // trying to do something else as well) and part later.
  if((!userp(this_object()) ||
     this_object()->query_time_left() == ROUND_TIME) &&
     !this_object()->query_casting_spell()) {
    adjust_action_defecit(-(ACTIONS_PER_HB / (COMBAT_SPEED + 1)));
  }
  
  // Used to control combat speed, especially during testing.
  if(_combat->hbc++ % COMBAT_SPEED)
    return;
  _combat->hbc = 0;

  if(this_object()->query_monitor() &&
     this_object()->query_monitor() <= ++(_combat->mbc)) {
    monitor_points();
    _combat->mbc = 0;
  }

  do_attack();

  if(!this_object()->query_casting_spell()) {
    adjust_action_defecit(-(ACTIONS_PER_HB / (COMBAT_SPEED + 1)));

    // Give a bonus attack if they have no action defecit.
    if(_combat->action_defecit < (OFFENSIVE_DEFECITS[_tactics->attitude] / 2)) {
#if DEBUG == 1
      combat_debug("Adding bonus attack for %s", this_object()->query_name());
      debug_printf("Bonus attack for %s [%d, %d]",
                   this_object()->query_name(), _combat->action_defecit,
                   OFFENSIVE_DEFECITS[_tactics->attitude]);
#endif
      call_out("do_attack", 1);
    }
  }
}

/**
 * Register a combat special.
 *
 * The combat system maintains a list of combat specials. These specials can
 * be used to override or alter any facet of a combat attack (or defense).
 * <p>
 * A special must register what type of special it is:<br>
 * T_OFFENSIVE - an offensive special<br>
 * T_DEFENSIVE - a defensive special<br>
 * T_CONTINUOUS - can be used with one of the above to indicate that the
 *                special is continuous and not a one-time event.<br>
 * <p>
 * It also indicates which stages of combat it wants to be notified of:<br>
 * E_OPPONENT_SELECTION - selection of an opponent<br>
 * E_DEFENDER_SELECTION - selection of the defender<br>
 * E_ATTACK_SELECTION - selecting which attack the attacker will perform<br>
 * E_DEFENSE_SELECTION - selection of the method of defense<br>
 * E_ATTACK_MODIFIER - calculating the modifier for attack (how easy/hard it
 * is)<br>
 * E_DEFENSE_MODIFIER - doing the same for the defense<br>
 * E_DAMAGE_CALCULATION - calculating how much damage the attack can do<br>
 * E_ARMOUR_CALCULATION - calculating how much damage the armour will stop<br>
 * E_WEAPON_DAMAGE - performing damage to the weapons involved<br>
 * E_WRITE_MESSAGES - writing out the success/failure messages<br>
 * E_AFTER_ATTACK - any post-attack cleanup.<br>
 * <p>
 * The special provides the combat system with a callback function to be
 * called and any additional data the special wants tracked by the combat
 * system.
 * <p>
 * Then, when combat reaches one of the stages requested it calls the callback
 * function. This function is passed the stage we're at, all the data for this
 * attack and also the extra data the special requested.
 * <p>
 * The callback can then modify any aspect of the attack data (it could change
 * the attack skill, the defensive weapon, the defense modifier, anything at
 * all) and then return back into combat.
 * <p>
 * The return tells the combat system what to do next. The options are:<br>
 * R_CONTINUE - continue as though the special hadn't done anything.<br>
 * R_DONE - treat this step as completed.<br>
 * R_ABORT - abort the attack<br>
 * R_REMOVE_ME - do one of the above and then remove the special.
 * 
 * @param type Is this an offensive or defensive special.
 * @param events A bitmap of the events this special is interested in.
 * @param the function to be called. A mixed array containing an object & a
 * function string.
 * @return the id of the special.
 *
 * @example
 *
 * This example registers a special that causes the player to be unable to
 * defend themselves for three rounds.
 *
 * id = player->register_special(T_DEFENSIVE | T_CONTINUOUS,
 *                               E_DEFENSE_SELECTION | E_DAMAGE_CALCULATION,
 *                               ({ base_name(this_object()), "callback" }),
 *                               ({ player, 0}));
 *
 * mixed *callback(int stage, class attack att, mixed data) {
 *   // If it's not our player defending we won't do anything.
 *   if(att->defender != data[1])
 *     return ({ R_CONTINUE, att, data });
 *
 *    switch(stage) {
 *    case E_DEFENSE_SELECTION: // We won't let them defend themselves!
 *      att->defense_action = "none";
 *      return ({ R_DONE, att, data });
 *    case E_DAMAGE_CALCULATION:
 *      // If they managed to defend themself, turn it into a failure.
 *      if(att->result == DEFWIN || att->result == DEFAWARD)
 *        att->result = OFFWIN;
 *
 *      // By using R_CONTINUE they take 500 hits _plus_ the normal damage.
 *      att->damage = 500;
 *
 *      // The R_REMOVE_ME causes our special to disappear once we've hurt
 *      // them three times.
 *      if(data[1] > 3)
 *        return ({ R_CONTINUE & R_REMOVE_ME, att, data });
 *
 *      data[1]++;
 *      return ({ R_CONTINUE, att, data });
 *    }
 * }
 */
int register_special(int type, int events, mixed callback, mixed data) {
  _combat->specials += ({ new(class combat_special,
                              id : ++(_combat->special_id),
                              type : type,
                              events : events,
                              callback : callback,
                              data : data) });
  return _combat->special_id;
}

/**
 * Remove a combat special.
 *
 * @param id The id of the special
 * @return 1 for success, 0 for failure
 */
int remove_special(int id) {
  int i;

  for(i=0; i<sizeof(_combat->specials); i++) {
    if((_combat->specials[i])->id == id) {
      _combat->specials -= ({ (_combat->specials)[i] });
      return 1;
    }
  }

  debug_printf("Failed to remove special %d", id);
  return 0;
}

/**
 * List the specials currently registered for this npc/player.
 * @return an array of comba_special classes.
 */
class combat_special *query_specials() {
  return copy(_combat->specials);
}

/**
 * Directly set the special list. Use with care!
 * @param class combat_special *
 */
void set_specials(class combat_special *specials) {
  _combat->specials = specials;
}

/**
 * Update the user data for a specific special.
 * @param id The id of the special
 * @param data The data to be set
 * @return True or false for success or failure
 */
int set_special_data(int id, mixed data) {
  int i;

  for(i=0; i<sizeof(_combat->specials); i++) {
    if((_combat->specials[i])->id == id) {
      _combat->specials[i]->data = data;
      return 1;
    }
  }

  return 0;
}

void adjust_action_defecit(int amount) {
  _combat->action_defecit += amount;
  if(_combat->action_defecit > MAX_ACTION_DEFECIT)
    _combat->action_defecit = MAX_ACTION_DEFECIT;
  else if(_combat->action_defecit < MIN_ACTION_DEFECIT)
    _combat->action_defecit = MIN_ACTION_DEFECIT;
}

int query_action_defecit() { return _combat->action_defecit; }

/**
 * This method returns the current tactics set using the tactics class.
 * @see set_tactics()
 * @index tactics
 */
class tactics query_tactics() {
  init_tactics();
  return copy(_tactics);
}

/**
 * This sets the current attitude to use in combat.
 * @param attitude the new combat attitude
 * @see query_tactics()
 */
void set_tactics(class tactics new_tactics) { _tactics = new_tactics; }

string query_combat_attitude() { return _tactics->attitude; }
void set_combat_attitude(string attitude) { _tactics->attitude = attitude; }
string query_combat_response() { return _tactics->response; }
void set_combat_response(string response) { _tactics->response = response; }
string query_combat_attack() { return _tactics->attack; }
void set_combat_attack(string attack) { _tactics->attack = attack; }
string query_combat_parry() { return _tactics->parry; }
void set_combat_parry(string parry) { _tactics->parry = parry; }
int query_unarmed_parry() { return _tactics->parry_unarmed; }
void set_unarmed_parry(int parry) {  _tactics->parry_unarmed = parry; }
string query_combat_mercy() { return _tactics->mercy; }
void set_combat_mercy(string mercy) { _tactics->mercy = mercy; }
string query_combat_focus() { return _tactics->focus_zone; }
void set_combat_focus(string focus) { _tactics->focus_zone = focus; }
string query_combat_distance() { return _tactics->ideal_distance; }
void set_combat_distance(string distance) { _tactics->ideal_distance = distance; }

/**
 * This method returns the current array of protectors on the living
 * object.  This is the people who are protecting us, so if we are hit
 * make them attack the hitter.
 * @return the current protectors array
 * @see add_protector()
 * @see remove_protector()
 */
object *query_protectors() {
  _combat->protectors -= ({ 0 });
   return copy(_combat->protectors + ({ }));
}

/**
 * This method will add a protector to the current list of protectors
 * for this living object.
 * @param thing the protector to add
 * @see remove_protector()
 * @see query_protectors()
 */
int add_protector( object thing ) {
  if(thing == this_object() ||
     thing->query_property("dead") ||
     member_array(this_object(), thing->query_protectors() +
                  thing->query_defenders()) != -1 ||
     member_array(thing, query_attacker_list())  != -1)
    return 0;
  
  if(member_array(thing, _combat->protectors) == -1)
    _combat->protectors += ({ thing });
  return 1;
}

/**
 * This method will remove a protector to the current list of protectors
 * for this living object.
 * @param thing the protector to remove
 * @return 1 for success, 0 for failure.
 *
 * @see add_protector()
 * @see query_protectors()
 */
int remove_protector(object protector) {
  if(member_array(protector, _combat->protectors) == -1)
    return 0;
  _combat->protectors -= ({ protector });
  return 1;
}

/**
 * This method resets the protector array back to being nothing.
 */
void reset_protectors() {
  _combat->protectors = ({ });
}

/**
 * This method returns the current array of defenders on the living
 * object.  This is the people who are protecting us, so if we are hit
 * make them attack the hitter.
 * @return the current defenders array
 * @see add_protector()
 * @see remove_protector()
 */
object *query_defenders() {
  _combat->defenders -= ({ 0 });
   return copy(_combat->defenders + ({ }));
}

/**
 * This method will add a defender to the current list of defenders
 * for this living object.
 * @param thing the defender to add
 * @see remove_defender()
 * @see query_defenders()
 */
int add_defender( object thing ) {
  if(thing == this_object() ||
     thing->query_property("dead") ||
     member_array(this_object(), thing->query_defenders() +
                  thing->query_protectors()) != -1 ||
     member_array(thing, query_attacker_list())  != -1)
    return 0;

  if(member_array(thing, _combat->defenders) == -1)
    _combat->defenders += ({ thing });
  return 1;
}

/**
 * This method will remove a defender to the current list of defenders
 * for this living object.
 * @param thing the defender to remove
 * @return 1 for success, 0 for failure.
 *
 * @see add_defender()
 * @see query_defenders()
 */
int remove_defender(object defender) {
  if(member_array(defender, _combat->defenders) == -1)
    return 0;
  _combat->defenders -= ({ defender });
  return 1;
}

/**
 * This method resets the defender array back to being nothing.
 */
void reset_defenders() {
  _combat->defenders = ({ });
}

#ifdef USE_DISTANCE
/**
 * Set the distance from this object to the given opponent.
 *
 * @param opponent the object to set the distance to.
 * @param distance the new distance.
 */
int set_distance(object opponent, int distance) {
  if(userp(opponent)) {
    if(!_combat->attacker_list[opponent->query_name()])
      return 0;
    _combat->attacker_list[opponent->query_name()] = distance;
  } else {
    if(!_combat->attacker_list[opponent])
      return 0;
    _combat->attacker_list[opponent] = distance;
  }

  return 1;
}

/**
 * Query the distance from this object to the given opponent.
 *
 * @param opponent the object to check
 */
int query_distance(object opponent) {
  if(userp(opponent)) {
    if(!_combat->attacker_list[opponent->query_name()])
      return 0;
    return _combat->attacker_list[opponent->query_name()];
  } else {
    if(!_combat->attacker_list[opponent])
      return 0;
    return _combat->attacker_list[opponent];
  }
}
#endif

/**
 * This method returns the current list of people in the attacker list
 * on the object.
 * @return the current attacker array
 */
object *query_attacker_list() {
  return uniq_array(map(keys(_combat->attacker_list) +
                        keys(_combat->hunting_list),
                        (: $1 && objectp($1) ? $1 : find_player($1) :)) -
                    ({ 0 }));
}

/**
 * This method removes someone from an attacker/hunting list.
 * @param ob the object to be removed.
 */
void remove_attacker_list(object ob) {
  if(userp(ob)) {
    map_delete(_combat->attacker_list, ob->query_name());
    map_delete(_combat->hunting_list, ob->query_name());
  } else {
    map_delete(_combat->attacker_list, ob);
    map_delete(_combat->hunting_list, ob);
  }
}

/**
 * This method is called when the living object is attacked by some opponent.
 * @param opponent the opponent we are attacked by
 * @return 0 if we cannot attack them, 1 if we can
 * @see query_attacker_list()
 * @see attack_ob()
 */
int attack_by(object opponent) {
  int starting;

  if(!objectp(opponent) ||
     opponent == this_object() ||
     !this_object()->query_attackable() ||
     pk_check(this_object(), opponent))
    return 0;

  // We'll stop protecting them when they attack us.
  if(member_array(opponent, _combat->protectors) != -1)
    _combat->protectors -= ({ opponent });
  if(member_array(opponent, _combat->defenders) != -1)
    _combat->defenders -= ({ opponent });

  if(!sizeof(query_attacker_list()))
    starting = 1;
  
#ifdef USE_DISTANCE
  if(userp(opponent)) {
    if(!_combat->attacker_list[opponent->query_name()])
      _combat->attacker_list[opponent->query_name()] =
        opponent->query_distance(this_object());
  } else if(!_combat->attacker_list[opponent])
    _combat->attacker_list[opponent] = opponent->query_distance(this_object());
#else
  if(userp(opponent)) {
    if(!_combat->attacker_list[opponent->query_name()])
      _combat->attacker_list[opponent->query_name()] = 1;
  } else if(!_combat->attacker_list[opponent])
    _combat->attacker_list[opponent] = 1;
#endif

  if(starting) {
    _combat->action_defecit = (MAX_ACTION_DEFECIT - MIN_ACTION_DEFECIT) / 3;
    start_combat(opponent);
  }

  _combat->in_combat = 1;

  return 1;
}

/**
 * This method is called to make us attack someone else.
 * @param opponent the person to attack
 * @return 0 if we cannot attack them, 1 if we can
 * @see query_attacker_list()
 * @see attack_by()
 */
int attack_ob(object opponent) {
  int starting, new_opponent;

  if(!objectp(opponent) ||
     opponent == this_object() ||
     !opponent->query_attackable() ||
     !this_object()->query_attackable() ||
     pk_check(this_object(), opponent))
    return 0;

  // They stop protecting you when you attack them.
  if(member_array(opponent, _combat->protectors) != -1)
    _combat->protectors -= ({ opponent });
  if(member_array(opponent, _combat->defenders) != -1)
    _combat->defenders -= ({ opponent });

  if(!sizeof(query_attacker_list()))
    starting = 1;
  else if(member_array(opponent, query_attacker_list()) == -1)
    new_opponent = 1;
  
#ifdef USE_DISTANCE
  if(userp(opponent)) {
    if(!_combat->attacker_list[opponent->query_name()])
      _combat->attacker_list[opponent->query_name()] = INITIAL_DISTANCE;
  } else {
    if(!_combat->attacker_list[opponent])
      _combat->attacker_list[opponent] = INITIAL_DISTANCE;
  }
#else
  if(userp(opponent)) {
    if(!_combat->attacker_list[opponent->query_name()])
      _combat->attacker_list[opponent->query_name()] = 1;
  } else {
    if(!_combat->attacker_list[opponent])
      _combat->attacker_list[opponent] = 1;
  }
#endif
  if((new_opponent || starting) && 
     opponent->query_visible(this_object()))
    _callout = call_out("announce_intent", 0, opponent);

  this_object()->remove_hide_invis("hiding");

  if(starting) {
    _combat->action_defecit = (MAX_ACTION_DEFECIT - MIN_ACTION_DEFECIT) / 3;
    start_combat(opponent);
  }

  _combat->in_combat = 1;

  return 1;
}

void stop_fight(object opponent) {
  class combat_special tmp;
#if DEBUG == 2
  combat_debug("stop_fight called against %s", opponent->query_name());
#endif

  if(!objectp(opponent))
    return;

  if(userp(opponent)) {
    map_delete(_combat->attacker_list, opponent->query_name());
    map_delete(_combat->hunting_list, opponent->query_name());
  } else {
    map_delete(_combat->attacker_list, opponent);
    map_delete(_combat->hunting_list, opponent);
  }

#ifdef USE_SURRENDER
  _surrender->from -= ({ 0, opponent });
  _surrender->refusers -= ({ 0, opponent });
  _surrender->to -= ({ 0, opponent });
#endif

  if(!sizeof(query_attacker_list())) {
    // Not in combat so remove any non-continuous specials.
    foreach(tmp in _combat->specials)
      if(!(tmp->type & T_CONTINUOUS))
        remove_special(tmp->id);

    end_combat();
  }
}

void stop_all_fight() {
  class combat_special tmp;

#if DEBUG == 2
  combat_debug("stop_all_fight called");
#endif

  _combat->attacker_list = ([ ]);
  _combat->hunting_list = ([ ]);
  _combat->in_combat = 0;

#ifdef USE_SURRENDER  _surrender->from = ({ });
  _surrender->refusers = ({ });
  _surrender->to = ({ });
#endif

  // Not in combat so remove any non-continuous specials.
  foreach(tmp in _combat->specials)
    if(!(tmp->type & T_CONTINUOUS))
      remove_special(tmp->id);

  end_combat();
}

void stop_hunting(object opponent) {
  if(userp(opponent))
    map_delete(_combat->hunting_list, opponent->query_name());
  else
    map_delete(_combat->hunting_list, opponent);

  if(!sizeof(query_attacker_list()))
    end_combat();
}

#ifdef USE_SURRENDER
/**
 * This method will be called when we surrender.  This does mean that
 * the opponent must have accepted our surrender plea.
 * @return always returns 0
 * @param thing the thing which made us surrender
 */
object do_surrender(object thing) {
   mixed *attackers;
   mixed att;

   if(this_object()->query_property("dead"))
      return 0;

   death_helper(thing, 0);

   catch(DEATH->someone_surrendered(this_object()));
   attackers = query_attacker_list();
   foreach(att in attackers) {
     att->stop_fight(this_object());
   }
   stop_all_fight();

   return 0;
}

int dont_attack_me() {
  if(sizeof(_surrender->to))
    return 1;
  else
    return 0;
}

/**
 * This method tells us what the npc shouuld do in the case that it
 * might possibly surrender.
 * @param victim the person surrendering
 * @param attacker the person beating up the surrenderer
 */
void event_surrender(object victim, object *attackers) {
  object *offer;

  offer = ({ });
  if (this_object() == victim) {
    _surrender->refusers -= ({ 0 });
    _surrender->to -= ({ 0 });
    offer = filter(attackers, (: member_array($1, _surrender->refusers +
                                              _surrender->to) == -1 :));
    if(sizeof(offer)) {
      event(environment(), "combat",
            sprintf("%s kneels down and surrenders to %s.\n",
                    this_object()->one_short(), query_multiple_short(offer)),
            ({ this_object() }) + _surrender->to + _surrender->refusers, 0);

      this_object()->event_combat(this_object(),
                                sprintf("%s kneel down and surrender to %s.\n",
                                        this_object()->one_short(),
                                        query_multiple_short(offer)), ({ }), 0);
      offer->offered_surrender(victim);
      _surrender->to += offer;
    }
  }
}

/**
 * This method returns the current list of people surrendering to us.
 * @return the list of people surrendering
 * @see remove_surrenderer()
 */
object *query_surrenderers() {
  _surrender->from -= ({ 0 });
  return copy(_surrender->from);
}

/**
 * This method removes a person surrendering from our current list.
 * @param victim the person to remove
 * @see query_surrenderers()
 */
void remove_surrenderer(object victim) {
  _surrender->from -= ({ 0, victim });
  return;
}

/**
 * This method is called when someone offers to surrender to the living
 * object.  It will check the current tactics for surrender and then
 * handle the response appropriately.
 * @param victim the person surrendering
 */
void offered_surrender(object victim) {
  string mercy = this_object()->query_combat_mercy();

  switch(mercy) {
  case "ask":
    if (interactive(this_object())) {
      _surrender->from -= ({ 0, victim });
      _surrender->from += ({ victim });
      this_object()->event_combat(this_object(),
                                  victim->one_short() + " has surrendered to "
                                  "you.  Either \"accept " +
                                  victim->query_name() + "\" or \"reject " +
                                  victim->query_name() + ".\n", ({ }), 0);
    } else {
      if (this_object()->query_accept_surrender(victim))
        victim->accepted_surrender(this_object());
      else
        victim->refused_surrender(this_object());
    }
    break;
  case "always":
    this_object()->event_combat(this_object(),
                                "You accept the surrender of " +
                                victim->one_short() + ".\n", ({ }), 0);
    victim->accepted_surrender(this_object());
    break;
  default:
    this_object()->event_combat(this_object(),
                                sprintf("You refuse the surrender of %s.\n",
                                        victim->one_short()), ({ }), 0);
    victim->refused_surrender(this_object());
  }
}

/**
 * This method is called if surrender was refused for the particular
 * person.
 * @param attacker the person who is surrendering
 * @see accepted_surrender()
 */
void refused_surrender(object attacker) {
  _surrender->to -= ({ 0 });
  if (member_array(attacker, _surrender->to) == -1)
    return;
  _surrender->to -= ({ attacker });
  _surrender->refusers -= ({ 0 });
  _surrender->refusers += ({ attacker });

  this_object()->event_combat(this_object(), attacker->one_short() +
                              " refused your surrender\n", ({ }), 0);
}

/**
 * This method is called if surrender was accepted for the particular
 * person.
 * @param attacker the person who is surrendering
 * @see refused_surrender()
 */
void accepted_surrender(object attacker) {
  _surrender->to -= ({ 0 });
  if(member_array(attacker, _surrender->to) == -1) {
    return;
  }
  _surrender->to -= ({ attacker });

  this_object()->event_combat(this_object(), attacker->one_short() +
                              " accepts your surrender.\n", ({ }));
  this_object()->do_surrender(attacker);
}
#endif

/**
 * This method is called when the fight has stopped.  It propogates the
 * stopped fighting event onto all the objects in the room.
 * @param thing the thing which stopped fighting?
 */
void stopped_fighting(object thing) {
  event( environment(), "stopped_fighting", thing );
}

/**
 * This method is called when there is a fight in progress.  It will
 * propogate the event onto all the objects in the room.
 * @param thing the person fighting
 */
void fight_in_progress(object thing) {
  event(environment(), "fight_in_progress", thing );
}

/* Stuff from the old combat shadow */
int set_concentrating(object thing) {
  if(userp(thing)) {
    if(!_combat->attacker_list[thing->query_name()])
      return 0;
#ifdef USE_DISTANCE
    _combat->attacker_list[thing->query_name()] =
      thing->query_distance(this_object());
#else
    _combat->attacker_list[thing->query_name()] = 1;
#endif
  } else {
    if(!_combat->attacker_list[thing])
      return 0;
#ifdef USE_DISTANCE
    _combat->attacker_list[thing] = thing->query_distance(this_object());
#else
    _combat->attacker_list[thing] = 1;
#endif
  }
  _combat->concentrating = thing;

  return 1;
}

object query_concentrating() {
  object thing;

  thing = _combat->concentrating;
  if(!thing || !objectp(thing))
    return 0;

  if(userp(thing) && !_combat->attacker_list[thing->query_name()])
    return 0;
  else if(!_combat->attacker_list[thing])
    return 0;

  return thing;
}

/** @ignore yes
 * This is here for backward compatibility only.  Use query_last_opponent.
 */
object query_attacker() {
  return _combat->last_opponent;
}

/**
 * This method returns the last known opponent we attacked or defended
 * against.  It is kept as up to date as the system can manage and so will
 * include anyone who is currently attacking us or who we are currently
 * attacking.
 *
 * @return object the opponent.
 */
object query_last_opponent() {
  return _combat->last_opponent;
}

/** @ignore yes
 * This is _internal_ to the combat system and should not be used doing so
 * may have odd side-effects.
 */
void set_last_opponent(object last) { _combat->last_opponent = last; }

/**
 * This method returns the last weapon we used for an attack or defense.
 *
 * @return object the weapon.
 */
object query_last_weapon() {
  return _combat->last_weapon;
}

/** @ignore yes
 * This is _internal_ to the combat system and should not be used doing so
 * may have odd side-effects.
 */
void set_last_weapon(object last) { _combat->last_weapon = last; }

/**
 * This method returns the last (combat) action performed.
 *
 * @return string the action.
 */
string query_last_action() { return _combat->last_action; }

/** @ignore yes
 * This is _internal_ to the combat system and should not be used doing so
 * may have odd side-effects.
 */
void set_last_action(string last) { _combat->last_action = last; }

/**
 * This method returns the result of the last combat round.
 *
 * @return int the result.
 */
int query_last_result() { return _combat->last_result; }

/** @ignore yes
 * This is _internal_ to the combat system and should not be used doing so
 * may have odd side-effects.
 */
void set_last_result(int last) { _combat->last_result = last; }

/**
 * This method determines whether or not the object is fighting another
 * object.
 * @param ob the object being tested
 * @param actively is the opponent being actively fought at this time?
 * @return 1 if it is in combat, 0 if it is not.
 */
varargs int is_fighting(object ob, int actively) {
  
  if(!objectp(ob))
    return 0;

  // Only return those things we are actively fighting right now. Things
  // we're hunting or cannot see are excluded.
  if(actively)
    return userp(ob) ? _combat->attacker_list[ob->query_name()] :
      _combat->attacker_list[ob];
    
  return userp(ob) ? _combat->attacker_list[ob->query_name()] ||
    _combat->hunting_list[ob->query_name()] :
    _combat->attacker_list[ob] || _combat->hunting_list[ob];
}

/**
 * Is the player currently fighting anyone.
 *
 * @return 1 for yes, 0 for no.
 */
int query_fighting() {
  if(!environment() || base_name(environment()) == "/room/rubbish")
    return 0;
  
  // Are any of our opponents here? If so we're fighting.
  if(sizeof(filter(query_attacker_list(),
                   (: environment($1) == environment(this_object()) :))))
    return 1;
  
  // Are any of the people here fighting us? If so we're fighting
  if(sizeof(filter(all_inventory(environment(this_object())),
                   (: $1 && living($1) && $1->is_fighting(this_object(), 1) :))))
    return 1;
  
  return 0;
}

/** @ignore yes */
mixed stats() {
  int i;
  int j;
  object *weapons;
  mixed ret;

  init_tactics();

  ret = ({
    ({ "attitude", _tactics->attitude }),
      ({ "response", _tactics->response }),
      ({ "mercy", _tactics->mercy }),
      ({ "parry", _tactics->parry }),
      ({ "attack", _tactics->attack }),
      ({ "unarmed parry", ({ "no", "yes" })[ _tactics->parry_unarmed ] }),
      ({ "attack zone", _tactics->focus_zone ? _tactics->focus_zone :
        "none" }),
      ({ "distance", _tactics->ideal_distance ? _tactics->ideal_distance :
        "none" }),
      
      });
  weapons = (object *)this_object()->query_weapons();
  if(!sizeof(weapons))
    return ret + weapon_logic::stats();

  for ( i = 0; i < sizeof( weapons ); i++ )
    ret += ({ ({ "weapon #"+ i, weapons[ i ]->short() }) }) +
      (mixed)weapons[ j ]->weapon_stats();

  return ret + weapon_logic::stats();
}