Logo Search packages:      
Sourcecode: xconq version File versions  Download package

combat.c

/* The combat-related actions of Xconq.
   Copyright (C) 1987-1989, 1991-2000 Stanley T. Shebs.

Xconq is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.  See the file COPYING.  */

#include "conq.h"
#include "kernel.h"

static int one_attack(Unit *atker, Unit *defender);
static void fire_on_unit(Unit *atker, Unit *other);
static void attack_unit(Unit *atker, Unit *other);
static void maybe_hit_unit(Unit *atker, Unit *other, int fire, int fallsoff);
static void hit_unit(Unit *unit, int hit, Unit *atker);
static void model_1_attack(Unit *unit, int x, int y);
static int real_attack_value(Unit *unit);
static int real_defense_value(Unit *unit);

static void reckon_damage(int fire);
static void reckon_damage_here(int x, int y);
static void report_damage(Unit *unit, Unit *atker, Unit *mainunit);
static int will_report_damage(Unit *unit);
static void rescue_one_occupant(Unit *occ);
static int retreat_unit(Unit *unit, Unit *atker);
static int retreat_in_dir(Unit *unit, int dir);
static void attempt_to_capture_unit(Unit *atker, Unit *other);
static void capture_unit_2(Unit *unit, Unit *pris, Side *prevside);
static void capture_occupant(Unit *unit, Unit *pris, Unit *occ, Side *newside);
static void detonate_on_cell(int x, int y);
static void hit_unit_with_detonation(Unit *unit, int hit, Unit *atker);
static int found_blocking_elevation(int u, int ux, int uy, int uz, int u2, int u2x, int u2y, int u2z);
static Unit *mobile_enemy_threat(Unit *unit, int range);
static int enough_ammo(Unit *unit, Unit *other);

static void damage_terrain(int u, int x, int y);
static int damaged_terrain_type(int t);

/* We can't declare all the action functions as static because some of them
   are in other files, but don't let them be visible to all files. */

#undef  DEF_ACTION
#define DEF_ACTION(name,code,args,prepfn,netprepfn,DOFN,checkfn,ARGDECL,doc)  \
  extern int DOFN ARGDECL;

#include "action.def"

int max_u_detonate_effect_range = -1;

int max_t_detonate_effect_range = -1;

int max_detonate_on_approach_range = -1;

/* Remember what the main units involved are, so display is handled
   relative to them and not to any occupants. */

static Unit *amain, *omain;

int numsoundplays;

/* Attack action. */

/* This is an attack on a given unit at a given level of commitment. */

int
prep_attack_action(Unit *unit, Unit *unit2, Unit *defender, int n)
{
    if (unit == NULL || unit->act == NULL || unit2 == NULL)
      return FALSE;
    unit->act->nextaction.type = ACTION_ATTACK;
    unit->act->nextaction.args[0] = defender->id;
    unit->act->nextaction.args[1] = n;
    unit->act->nextaction.actee = unit2->id;
    return TRUE;
}

int
do_attack_action(Unit *unit, Unit *unit2, Unit *defender, int n)
{
    int u2 = unit2->type, u3 = defender->type;
    int withdrawchance, surrenderchance;

    action_point(unit2->side, defender->x, defender->y);
    action_point(defender->side, defender->x, defender->y);
    /* Defender might be a type that can sneak away to avoid attack. */
    withdrawchance = uu_withdraw_per_attack(u2, u3);
    if (withdrawchance > 0) {
      if (probability(withdrawchance)) {
          if (retreat_unit(defender, unit2)) {
            if (alive(unit))
              use_up_acp(unit, uu_acp_to_attack(u2, u3));
            return A_ANY_DONE;
          }
      }
    }
    /* Defender might instead choose to surrender right off. */
    surrenderchance = uu_surrender_per_attack(u2, u3);
    if (surrenderchance > 0
      && unit2->side
      && unit2->side->tech[u3] >= u_tech_to_own(u3)) {
      if (probability(surrenderchance)) {
          capture_unit(unit2, defender, H_UNIT_CAPTURED);
          if (alive(unit))
            use_up_acp(unit, uu_acp_to_attack(u2, u3));
          return A_ANY_DONE;
      }
    }
    /* Carry out a normal attack. */
    one_attack(unit, defender);
    if (alive(unit))
      use_up_acp(unit, uu_acp_to_attack(u2, u3));
    /* The defender in an attack has to take time to defend itself. */
    if (alive(defender)) 
      use_up_acp(defender, uu_acp_to_defend(u2, u3));
    return A_ANY_DONE;
}

int
check_attack_action(Unit *unit, Unit *unit2, Unit *defender, int n)
{
    int u, u2, u3, acp, u2x, u2y, dfx, dfy, dist, m;

    /* In combat model 1, we can't attack units directly. */
    if (g_combat_model() == 1)
      return A_ANY_ERROR;
    if (!in_play(unit))
      return A_ANY_ERROR;
    if (!in_play(unit2))
      return A_ANY_ERROR;
    if (!in_play(defender))
      return A_ANY_ERROR;
    /* We can't attack ourselves. */
    if (unit2 == defender)
      return A_ANY_ERROR;
    if (!indep(unit2) && unit2->side == defender->side)
      return A_ANY_ERROR;
    u = unit->type;
    u2 = unit2->type;
    u3 = defender->type;
    acp = uu_acp_to_attack(u2, u3);
    if (acp < 1)
      return A_ANY_CANNOT_DO;
    if (!has_enough_acp(unit, acp))
      return A_ANY_NO_ACP;
    /* Check whether we can attack from inside a transport. */
    if (unit2->transport && uu_occ_combat(u2, unit2->transport->type) == 0)
      return A_ANY_ERROR;
    u2x = unit2->x;  u2y = unit2->y;
    dfx = defender->x;  dfy = defender->y;
    dist = distance(u2x, u2y, dfx, dfy);
    if (dist < uu_attack_range_min(u2, u3))
      return A_ANY_TOO_NEAR;
    if (dist > uu_attack_range(u2, u3))
      return A_ANY_TOO_FAR;
    if (uu_hit(u2, u3) <= 0)
      return A_ANY_ERROR;
    /* We have to have a minimum level of supply to be able to attack. */
    for_all_material_types(m) {
      if (unit2->supply[m] < um_to_attack(u2, m))
        return A_ANY_NO_MATERIAL;
    }
    /* (should prorate ammo needs by intensity of attack) */
    if (!enough_ammo(unit2, defender))
      return A_ANY_NO_AMMO;
    /* Allow attacks even if zero damage, this amounts to "harassment". */
    return A_ANY_OK;
}

/* Overrun action. */

/* Overrun is an attempt to occupy a given cell that may include attempts
   to attack and/or capture any units in the way. */

int
prep_overrun_action(Unit *unit, Unit *unit2, int x, int y, int z, int n)
{
    if (unit == NULL || unit->act == NULL || unit2 == NULL)
      return FALSE;
    unit->act->nextaction.type = ACTION_OVERRUN;
    unit->act->nextaction.args[0] = x;
    unit->act->nextaction.args[1] = y;
    unit->act->nextaction.args[2] = z;
    unit->act->nextaction.args[3] = n;
    unit->act->nextaction.actee = unit2->id;
    return TRUE;
}

int
do_overrun_action(Unit *unit, Unit *unit2, int x, int y, int z, int n)
{
    int u, u2, u3, acpused, mpcost, acpcost, speed, ox, oy, oz;
    int withdrawchance, surrenderchance, gotonext;
    Unit *defender;

    u = unit->type;  u2 = unit2->type;
    ox = unit2->x;  oy = unit2->y;  oz = unit2->z;
    action_point(unit2->side, x, y);
    acpused = 0;
    switch(g_combat_model()) {
      case 0:
      /* Attack every defender in turn. */
      for_all_stack(x, y, defender) {
          u3 = defender->type;
          /* Don't attack any of our buddies. */
          if (unit_trusts_unit(unit2, defender))
            continue;
          action_point(defender->side, x, y);
          gotonext = FALSE;
          /* Defender might be a type that can sneak away to avoid
               attack. */
          withdrawchance = uu_withdraw_per_attack(u2, u3);
          if (withdrawchance > 0) {
            if (probability(withdrawchance)) {
                if (retreat_unit(defender, unit2)) {
                  gotonext = TRUE;
                }
            }
          }
          /* Defender might instead choose to surrender right off. */
          surrenderchance = uu_surrender_per_attack(u2, u3);
          if (surrenderchance > 0
            && unit2->side
            && unit2->side->tech[u3] >= u_tech_to_own(u3)) {
            if (probability(surrenderchance)) {
                capture_unit(unit2, defender, H_UNIT_CAPTURED);
                gotonext = TRUE;
            }
          }
          /* (should automatically prefer direct attack if better odds) */
          if (can_fire(unit)) {
            do_fire_at_action(unit, unit, defender, -1); 
            acpused += u_acp_to_fire(u2);
            /* Unlike the case below, target acp consumption has
               already been dealt with in fire_on_unit, which is
               called by do_fire_at_action. */
          } else if (can_attack(unit)) {
            if (!gotonext)
              one_attack(unit2, defender);
            if (alive(unit))
              use_up_acp(unit, uu_acp_to_attack(u2, u3));
            acpused += uu_acp_to_attack(u2, u3);
            /* The target of an attack has to take time to defend
               itself. */
            if (!gotonext && alive(defender)) {
                use_up_acp(defender, uu_acp_to_defend(u, u3));
            }
          }
      }
      if (!alive(unit2))
        return A_OVERRUN_FAILED;
      if (in_blocking_zoc(unit2, x, y, z))
        return A_OVERRUN_FAILED;
      /* Try to enter the cleared cell now - might still have
         friendlies filling it up already, so check first. */
      if (can_occupy_cell(unit2, x, y)) {
          mpcost = move_unit(unit2, x, y);
          /* Note that we'll say the action succeeded even if
             the cell did not have enough room for us to actually
             be in it, which is a little weird. */
          /* Now add up any extra movement costs of entering the new cell. */
          mpcost += zoc_move_cost(unit2, ox, oy, oz);
          acpcost = 0;
          speed = unit_speed(unit2, x, y);
          if (speed > 0)
            acpcost = (mpcost * 100) / speed;
          acpcost -= acpused;
          if (acpcost > 0) {
            /* Take the movement cost out of the moving unit if
                   possible. */
            use_up_acp(unit2, acpcost);
          }
          /* Count the unit as having actually moved. */
          if (unit2->act)
            ++(unit2->act->actualmoves);
      }
      break;
      case 1:
      model_1_attack(unit2, x, y);
      break;
      default:
      break;
    }
    return A_OVERRUN_SUCCEEDED;
}

int
check_overrun_action(Unit *unit, Unit *unit2, int x, int y, int z, int n)
{
    int u, u2, u2x, u2y, u2z, u3, totcost, speed, mpavail, m;
    Unit *defender;

    if (!in_play(unit))
      return A_ANY_ERROR;
    if (!in_play(unit2))
      return A_ANY_ERROR;
    if (!inside_area(x, y))
      return A_ANY_ERROR;
    if (n == 0)
      return A_ANY_ERROR;
    u = unit->type;
    u2 = unit2->type;
    /* if (!type_can_attack(u)) return A_ANY_CANNOT_DO check deleted to enable 
    overruning after killing  targets by fire, or into empty cells by noncombat units. */
    if (!has_enough_acp(unit, 1))
      return A_ANY_NO_ACP;
    /* Check whether we can attack from inside a transport. */
    if (unit2->transport && uu_occ_combat(u2, unit2->transport->type) == 0)
      return A_ANY_ERROR;
    u2x = unit2->x;  u2y = unit2->y;  u2z = unit2->z;
    /* We have to be in the same cell or an adjacent one. */
    if (!between(0, distance(u2x, u2y, x, y), 1))
      return A_ANY_TOO_FAR;
    /* Now start looking at the move costs. */
    u3 = (unit2->transport ? unit2->transport->type : NONUTYPE);
    totcost = total_move_cost(u2, u3, u2x, u2y, u2z, x, y, u2z);
    speed = unit_speed(unit2, x, y);
    mpavail = (unit->act->acp * speed) / 100;
    /* Zero mp always disallows movement, unless intra-cell. */
    if (mpavail <= 0 && !(u2x == x && u2y == y && u2z == u2z))
      return A_MOVE_NO_MP;
    /* The free mp might get us enough moves, so add it before comparing. */
    if (mpavail + u_free_mp(u2) < totcost)
      return A_MOVE_NO_MP;
    /* We have to have a minimum level of supply to be able to attack. */
    for_all_material_types(m) {
      if (unit2->supply[m] < um_to_attack(u2, m))
        return A_ANY_NO_MATERIAL;
    }
    switch(g_combat_model()) {
      case 0:
      for_all_stack(x, y, defender) {
          /* If we can't attack the unit, then we can't possibly overrun. */
          if (uu_acp_to_attack(u2, defender->type) == 0)
            return A_ANY_ERROR;
          /* (should test if units here can be attacked en masse) */
          /* (should prorate ammo needs by intensity of overrun) */
          if (!enough_ammo(unit2, defender))
            return A_ANY_NO_AMMO;
      }
      break;
      case 1:
      /* Assume we can always attempt to overrun */
      break;
      default:
      return A_ANY_ERROR;
    }
    return A_ANY_OK;
}

/* Return true if the attacker defeated the defender, and can therefore
   try to move into the defender's old position. */

static int
one_attack(Unit *atker, Unit *defender)
{
    int ua = atker->type, ud = defender->type;
    int ax = atker->x, ay = atker->y, ox = defender->x, oy = defender->y;
    int counter;
    Side *as = atker->side, *os = defender->side;
    SideMask observers;

    amain = atker;  omain = defender;
    attack_unit(atker, defender);
    /* Do a counterattack if appropriate.  We must be able to counterattack,
       and have enough acp to do so. */
    counter = uu_counterattack(ua, ud);
    if (counter > 0
      && has_enough_acp(defender, uu_acp_to_defend(ua, ud))) {
      /* (should use value to set strength/commitment of counterattack) */
      attack_unit(defender, atker);
    }
    if (0 /* recording attacks */) {
      observers = add_side_to_set(atker->side, NOSIDES);
      observers = add_side_to_set(defender->side, observers);
      /* (should let other watching sides see event also) */
      record_event(H_UNIT_ASSAULTED, observers, atker->id, defender->id,
                 ox, oy);
    }
    reckon_damage(FALSE);
    see_exact(as, ax, ay);
    see_exact(as, ox, oy);
    see_exact(os, ax, ay);
    see_exact(os, ox, oy);
    update_cell_display(as, ax, ay, UPDATE_ALWAYS);
    update_cell_display(as, ox, oy, UPDATE_ALWAYS);
    update_cell_display(os, ax, ay, UPDATE_ALWAYS);
    update_cell_display(os, ox, oy, UPDATE_ALWAYS);
    all_see_cell(ax, ay);
    all_see_cell(ox, oy);
    attempt_to_capture_unit(atker, defender);
    /* If the defender was not captured, it might turn the tables! */
    /* (Note that we cannot cache the attacker's and defender's types,
       because the type might have changed due to damage, and we want
       to know if the new type might countercapture.) */
    if (alive(defender)
      && alive(atker)
      && defender->side == os
      && uu_countercapture(atker->type, defender->type) > 0) {
      attempt_to_capture_unit(defender, atker);
    }
    return (alive(atker) && unit_at(ox, oy) == NULL);
}

/* Implement "combat model 1", which emulates Civ-type games.  Combat
   is multi-round, ending in the demise of one or the other combatant,
   plus anything stacked with it. */

int total_encounter_weights;

static void
model_1_attack(Unit *unit, int x, int y)
{
    int u = unit->type, u2, u3, d, maxdef, def, att, winround, limit, dmg;
    Obj *choice, *chtype, *chutype, *chside;
    Unit *unit2, *defender, *occ, *defocc, *victim;
    char *hitmovietype;
    Side *side3;
    SideMask sidemask;    

    for_all_stack(x, y, unit2) {
      u2 = unit2->type;
      if (u_encounter_result(u2) != lispnil) {
          /* The encounter result overrides any other outcome. */
          choice = choose_from_weighted_list(u_encounter_result(u2),
                                     &total_encounter_weights,
                                     FALSE);
          if (consp(choice))
            chtype = car(choice);
          else
            chtype = choice;
          if (match_keyword(chtype, K_UNIT)) {
            /* Dig out a unit type spec. */
            chutype = cadr(choice);
            if (consp(chutype)) {
                chside = cadr(chutype);
                chutype = car(chutype);
            } else {
                chside = lispnil;
            }
            u3 = utype_from_name(c_string(chutype));
            /* Change the unit's type. */
            change_unit_type(unit2, u3, H_UNIT_TYPE_CHANGED);
            unit2->hp = unit2->hp2 = u_hp(u3);
            /* Pick out a side if one was specified. */
            if (numberp(chside)) {
                side3 = side_n(c_number(chside));
            } else {
                side3 = unit->side;
            }
            change_unit_side(unit2, side3, -1, unit);
            /* The unit is changing side voluntarily, so set the
               original side also. */
            set_unit_origside(unit2, unit2->side);
          } else if (match_keyword(chtype, K_VANISH)) {
            kill_unit(unit2, H_UNIT_VANISHED);
          } else {
            run_warning("Unknown encounter type %s", c_string(chtype));
          }
          if (consp(choice) && stringp(caddr(choice))) {
            notify(unit->side, "%s", c_string(caddr(choice)));
          }
          return;
      }
    }
    /* Non-combat units can't do anything. */
    if (u_attack(u) == 0)
      return;
    /* Choose the defender of a stack. */
    maxdef = -1;
    defender = NULL;
    for_all_stack(x, y, unit2) {
      /* Don't attack any of our buddies. */
      if (unit_trusts_unit(unit, unit2))
        continue;
      /* Identify the best defender. */
      def = real_defense_value(unit2);
      if (def > maxdef) {
          maxdef = def;
          defender = unit2;
      }
    }
    /* If the location is empty for some reason, escape quietly. */
    if (defender == NULL)
      return;
    /* For cities, find a defender. */
    if (u_advanced(defender->type)) {
        /* We don't want defenseless occs (facilities) to count as defenders,
         * so initialize maxdefs to 0 instead of -1. */
      maxdef = 0;
      defocc = NULL;
      for_all_occupants(defender, occ) {
          u2 = occ->type;
          def = u_defend(u2);
          if (def > maxdef) {
            maxdef = def;
            defocc = occ;
          }
      }
      /* Cities with no defenders can be captured directly. */
      if (defocc == NULL) {
          attempt_to_capture_unit(unit, defender);
          return;
      }
      defender = defocc;
    }
    d = defender->type;
    limit = 100;
    while (alive(unit) && alive(defender) && limit-- > 0) {
      att = real_attack_value(unit);
      def = real_defense_value(defender);
      winround = probability((att * 100) / (att + def));
      if (winround) {
          dmg = roll_dice(uu_damage(u, d));
          victim = defender;
          defender->hp2 -= dmg;
      } else {
          dmg = roll_dice(uu_damage(d, u));
          victim = unit;
          unit->hp2 -= dmg;
      }
      if (defender->hp2 <= 0) {
          report_combat(unit, defender, "destroy");
      } else if (unit->hp2 <= 0) {
          report_combat(defender, unit, "destroy");
      }
      /* Pick the hit movie to show. */
      hitmovietype = ((victim->hp2 <= 0) ? "death" : "hit-short");
      /* Make up the list of sides that will see it. */
      sidemask = NOSIDES;
      for_all_sides(side3) {
          /* Let all sides that can see the attacker's cell see it. */
          if (units_visible(side3, victim->x, victim->y))
            sidemask = add_side_to_set(side3, sidemask);
      }
      /* Show the movie. */
      for_all_sides(side3) {
          if (side_in_set(side3, sidemask))
            schedule_movie(side3, hitmovietype, victim->id);
      }
      play_movies(ALLSIDES);
      damage_unit(unit, combat_dmg, defender);
      damage_unit(defender, combat_dmg, unit);
    }
    /* The whole stack dies if its defender dies. */
    if (!alive(defender) && defender->transport == NULL) {
      for_all_stack(x, y, unit2) {
          if (!u_advanced(unit2->type)) {
            /* (should except spies etc?) */
            unit2->hp2 = 0;
            report_combat(unit, unit2, "destroy");
          }
      }
      reckon_damage_here(x, y);
      /* But capture cities instead of destroying them. */
      for_all_stack(x, y, unit2) {
          if (u_advanced(unit2->type)) {
            attempt_to_capture_unit(unit, unit2);
          }
      }
      /* Occupants of city may be damaged. */
      reckon_damage_here(x, y);
    }
}

/* The real attack value of a unit after adjustments for combat exp, 
terrain effects, occupants etc. */

int
real_attack_value(Unit *unit)
{
      Unit *unit2;
      int t, u, att, effect;
      
      u = unit->type;
      att = u_attack(u);
      /* First factor in combat experience. */
      if (u_cxp_max(u) > 0 && unit->cxp > 0) {
            effect = u_full_cxp_affects_attack(u);
            if (effect != 100) {
                  att *= (100 + (effect - 100) * unit->cxp / u_cxp_max(u)) / 100;
            }
      }
      /* Then factor in terrain effects. */
      t = terrain_at(unit->x, unit->y);
      effect = ut_terrain_affects_attack(u, t);
      if (effect != 100) {
            att *= effect / 100;
      }
      /* Then factor in effect of transport. */
      if (unit->transport) {
            if (is_active(unit->transport)) {
                  effect = uu_transport_affects_attack(unit->transport->type, u);               
                  if (effect != 100) {
                        att *= effect / 100;
                  }
            }
      }     
      /* Then factor in effect of occupants. */
      for_all_occupants(unit, unit2) {
            if (is_active(unit2)) {
                  effect = uu_occ_affects_attack(unit2->type, u);             
                  if (effect != 100) {
                        att *= effect / 100;
                  }
            }
      }     
      /* Then factor in effect of friendly units in the same cell. */
      for_all_stack_with_occs(unit->x, unit->y, unit2) {
            if (is_active(unit2)
                && trusted_side(unit2->side, unit->side)) {
                  effect = uu_neighbour_affects_attack(unit2->type, u);             
                  if (effect != 100) {
                        att *= effect / 100;
                  }
            }
      }
      return att;
}


/* The real defense value of a unit after adjustments for combat exp, 
terrain effects, occupants etc. */

int
real_defense_value(Unit *unit)
{
      Unit *unit2;
      int t, u, def, effect;
      
      u = unit->type;
      def = u_defend(u);
      /* First factor in combat experience. */
      if (u_cxp_max(u) > 0 && unit->cxp > 0) {
            effect = u_full_cxp_affects_defense(u);
            if (effect != 100) {
                  def *= (100 + (effect - 100) * unit->cxp / u_cxp_max(u)) / 100;
            }
      }
      /* Then factor in terrain effects. */
      t = terrain_at(unit->x, unit->y);
      effect = ut_terrain_affects_defense(u, t);
      if (effect != 100) {
            def *= effect / 100;
      }
      /* Then factor in effect of transport. */
      if (unit->transport) {
            if (is_active(unit->transport)) {
                  effect = uu_transport_affects_defense(unit->transport->type, u);              
                  if (effect != 100) {
                        def *= effect / 100;
                  }
            }
      }     
      /* Then factor in effect of occupants. */
      for_all_occupants(unit, unit2) {
            if (is_active(unit2)) {
                  effect = uu_occ_affects_defense(unit2->type, u);                  
                  if (effect != 100) {
                        def *= effect / 100;
                  }
            }
      }     
      /* Then factor in effect of friendly units in the same cell. */
      for_all_stack_with_occs(unit->x, unit->y, unit2) {
            if (is_active(unit2)
                && trusted_side(unit2->side, unit->side)) {
                  effect = uu_neighbour_affects_defense(unit2->type, u);                  
                  if (effect != 100) {
                        def *= effect / 100;
                  }
            }
      }
      return def;
}


/* Fire-at action. */

/* Shooting at a given unit. */

int
prep_fire_at_action(Unit *unit, Unit *unit2, Unit *unit3, int m)
{
    if (unit == NULL || unit->act == NULL || unit2 == NULL || unit3 == NULL)
      return FALSE;
    unit->act->nextaction.type = ACTION_FIRE_AT;
    unit->act->nextaction.args[0] = unit3->id;
    unit->act->nextaction.args[1] = m;
    unit->act->nextaction.actee = unit2->id;
    return TRUE;
}

int
do_fire_at_action(Unit *unit, Unit *unit2, Unit *unit3, int m)
{
    int ux = unit->x, uy = unit->y, ox, oy, oz;
    SideMask sidemask;
    Side *side;

    action_point(unit2->side, unit3->x, unit3->y);
    action_point(unit3->side, unit3->x, unit3->y);
    /* Make up the list of sides that will see the fire. */
    sidemask = NOSIDES;
    for_all_sides(side) {
      /* Let all sides that can see the attacker's cell see it. */
      if (units_visible(side, unit2->x, unit2->y))
        sidemask = add_side_to_set(side, sidemask);
      /* Let all sides that can see the unit3's cell see it. */
      if (units_visible(side, unit3->x, unit3->y))
        sidemask = add_side_to_set(side, sidemask);
    }
    /* Show the fire. */
    for_all_sides(side) {
      if (side_in_set(side, sidemask))
        update_fire_at_display(side, unit, unit3, m, TRUE);
    }
    ox = unit3->x;  oy = unit3->y;  oz = unit3->z;
    amain = unit;  omain = unit3;
    fire_on_unit(unit, unit3);
    reckon_damage(TRUE);
    if (alive(unit))
      use_up_acp(unit, u_acp_to_fire(unit2->type));
    /*      if (alive(unit3)) use_up_acp(unit3, 1); */
    /* Each side sees what happened to its own unit. */
    update_unit_display(unit2->side, unit2, TRUE);
    if (unit != unit2)
      update_unit_display(unit->side, unit, TRUE);
    update_unit_display(unit3->side, unit3, TRUE);
    /* The attacking side also sees the remote cell. */
    update_cell_display(unit2->side, ox, oy, UPDATE_ALWAYS);
    update_cell_display(unit3->side, ox, oy, UPDATE_ALWAYS);
    /* Victim might see something in attacker's cell. */
    update_cell_display(unit3->side, ux, uy, UPDATE_ALWAYS);
    /* Actually, everybody might be seeing the combat. */
    all_see_cell(ux, uy);
    all_see_cell(ox, oy);
    /* Permit attempts to capture after fire, but only from adjacent
       or same cell. */
    if (distance(unit2->x, unit2->y, unit3->x, unit3->y) < 2) {
      Side *os = unit3->side;

      attempt_to_capture_unit(unit2, unit3);
      /* If the unit3 was not captured, it might turn the tables! */
      /* (Note that we cannot cache the attacker's and unit3's
         types, because the type might have changed due to damage,
         and we want to know if the new type might countercapture.)  */
      if (alive(unit3)
          && alive(unit2)
          && unit3->side == os
          && uu_countercapture(unit2->type, unit3->type) > 0) {
          attempt_to_capture_unit(unit3, unit2);
      }
    }
    /* Always expend the ammo (but only if m is a valid mtype). */
    return A_ANY_DONE;
}

/* Test a fire action for plausibility. */

int
check_fire_at_action(Unit *unit, Unit *unit2, Unit *unit3, int m)
{
    int u, u2, u3, ux, uy, uz, acp, dist, m2;

    /* In combat model 1, we can't attack units directly. */
    if (g_combat_model() == 1)
      return A_ANY_ERROR;
    if (!in_play(unit))
      return A_ANY_ERROR;
    if (!in_play(unit2))
      return A_ANY_ERROR;
    if (!in_play(unit3))
      return A_ANY_ERROR;
    /* We can't attack ourselves. */
    if (unit2 == unit3)
      return A_ANY_ERROR;
    u = unit->type; u2 = unit2->type;  u3 = unit3->type;
    ux = unit->x;  uy = unit->y;  uz = unit->z;
    acp = u_acp_to_fire(u2);
    if (acp < 1)
      return A_ANY_CANNOT_DO;
    if (!has_enough_acp(unit, acp))
      return A_ANY_NO_ACP;
    /* Check whether we can attack from inside a transport. */
    if (unit2->transport && uu_occ_combat(u2, unit2->transport->type) == 0)
      return A_ANY_ERROR;
    /* Check that target is in range. */
    dist = distance(ux, uy, unit3->x, unit3->y);
    if (dist > u_range(u2))
      return A_ANY_TOO_FAR;
    if (dist < u_range_min(u2))
      return A_ANY_TOO_NEAR;
    if (!indep(unit2) && unit2->side == unit3->side)
      return A_ANY_ERROR;
    /* Gunners won't shoot at anything they know they can't hit. */
    if ((uu_fire_hit(u2, u3) != -1 ? uu_fire_hit(u2, u3) : uu_hit(u2, u3)) == 0)
      return A_ANY_ERROR;
    /* Check intervening elevations. */
    if (found_blocking_elevation(u2, ux, uy, uz, u3,
                         unit3->x, unit3->y, unit3->z))
      return A_FIRE_BLOCKED;
    /* We have to have a minimum level of supply to be able to attack. */
    for_all_material_types(m2) {
      if (unit2->supply[m2] < um_to_fire(u2, m2))
        return A_ANY_NO_MATERIAL;
    }
    /* Check for enough of right kind of ammo. */
    if (is_material_type(m)) {
      if (unit->supply[m] == 0)
        return A_ANY_NO_AMMO;
    } else {
      if (!enough_ammo(unit2, unit3))
        return A_ANY_NO_AMMO;
    }
    return A_ANY_OK;
}

/* Fire-into action. */

/* Shooting at a given location. */

int
prep_fire_into_action(Unit *unit, Unit *unit2, int x, int y, int z, int m)
{
    if (unit == NULL || unit->act == NULL || unit2 == NULL)
      return FALSE;
    unit->act->nextaction.type = ACTION_FIRE_INTO;
    unit->act->nextaction.args[0] = x;
    unit->act->nextaction.args[1] = y;
    unit->act->nextaction.args[2] = z;
    unit->act->nextaction.args[3] = m;
    unit->act->nextaction.actee = unit2->id;
    return TRUE;
}

/* One can always shoot, if the cell is visible, but there might not
   not be anything to hit!  No counterattacks when shooting, and the
   results might not be visible to the shooter. */

int
do_fire_into_action(Unit *unit, Unit *unit2, int x, int y, int z, int m)
{
    int ux = unit->x, uy = unit->y, ox, oy, oz;
    SideMask sidemask;
    Unit *other;
    Side *side;
    
    /* Make up the list of sides that will see the fire. */
    sidemask = NOSIDES;
    for_all_sides(side) {
      /* Let all sides that can see the attacker's cell see it. */
      if (units_visible(side, unit2->x, unit2->y))
        sidemask = add_side_to_set(side, sidemask);
      /* Let all sides that can see the fired-into cell see it. */
      if (units_visible(side, x, y))
        sidemask = add_side_to_set(side, sidemask);
    }
    /* Show the fire. */
    for_all_sides(side) {
      if (side_in_set(side, sidemask))
            update_fire_into_display(side, unit2, x, y, z, m, TRUE);
    }
    /* If any units at target, hit them. */
    for_all_stack(x, y, other) {
      ox = other->x;  oy = other->y;  oz = other->z;
      amain = unit;  omain = other;
      fire_on_unit(unit2, other);
      reckon_damage(TRUE);
      /* Each side sees what happened to its unit that is being hit. */
      update_unit_display(other->side, other, TRUE);
      /* The attacking side also sees the remote cell. */
      update_cell_display(unit->side, ox, oy, UPDATE_ALWAYS);
      update_cell_display(other->side, ox, oy, UPDATE_ALWAYS);
      /* Victim might see something in attacker's cell. */
      update_cell_display(other->side, ux, uy, UPDATE_ALWAYS);
      /* Actually, everybody might be seeing the combat. */
      all_see_cell(ux, uy);
      all_see_cell(ox, oy);
      /* don't take moves though! */
    }
    /* Firing side gets just one update. */
    update_unit_display(unit2->side, unit2, TRUE);
    if (unit != unit2)
      update_unit_display(unit->side, unit, TRUE);
    if (alive(unit))
      use_up_acp(unit, u_acp_to_fire(unit2->type));
    /* Always expend the ammo (but only if m is a valid material). */
    /* We're always "successful", even though the bombardment may have
       had little or no actual effect. */
    return A_ANY_DONE;
}

/* Test a shoot action for plausibility. */

int
check_fire_into_action(Unit *unit, Unit *unit2, int x, int y, int z, int m)
{
    int u, u2, u2x, u2y, u2z, acp, dist, m2;

    /* In combat model 1, we can't fire at anything. */
    if (g_combat_model() == 1)
      return A_ANY_ERROR;
    if (!in_play(unit))
      return A_ANY_ERROR;
    if (!in_play(unit2))
      return A_ANY_ERROR;
    u2x = unit2->x;  u2y = unit2->y;  u2z = unit2->z;
    /* Check that target location is meaningful. */
    if (!inside_area(x, y))
      return A_FIRE_INTO_OUTSIDE_WORLD;
    u = unit->type;
    u2 = unit2->type;
    acp = u_acp_to_fire(u2);
    if (acp < 1)
      return A_ANY_CANNOT_DO;
    if (!has_enough_acp(unit, acp))
      return A_ANY_NO_ACP;
    /* Check whether we can attack from inside a transport. */
    if (unit2->transport && uu_occ_combat(u2, unit2->transport->type) == 0)
      return A_ANY_ERROR;
    /* Check that target is in range. */
    dist = distance(u2x, u2y, x, y);
    if (dist > u_range(u2))
      return A_ANY_TOO_FAR;
    if (dist < u_range_min(u2))
      return A_ANY_TOO_NEAR;
    /* Check intervening elevations and terrain. */
    if (found_blocking_elevation(u2, u2x, u2y, u2z, NONUTYPE, x, y, z))
      return A_FIRE_BLOCKED;
    /* We have to have a minimum level of supply to be able to attack. */
    for_all_material_types(m2) {
      if (unit2->supply[m2] < um_to_fire(u2, m2))
        return A_ANY_NO_MATERIAL;
    }
    /* Check for enough of right kind of ammo. */
    if (is_material_type(m)) {
      if (unit->supply[m] == 0)
        return A_ANY_NO_AMMO;
    } else {
      /* should just assume amount is appropriate? */
    }
    return A_ANY_OK;
}

static int tmpx0, tmpy0, tmpe0, tmpe1, tmpdist01, cellwid, fangle;

static int elevation_blocks(int x, int y);

static int
elevation_blocks(int x, int y)
{
    int interpol, celldist0, dist0, el;

    celldist0 = distance(tmpx0, tmpy0, x, y);
    dist0 = celldist0 * cellwid;
    interpol = (tmpe1 - tmpe0) - (fangle * tmpdist01) / 100;
    /* The following is * dist0^2 / tmpdist01^2, but arranged to avoid
       overflow. */
    interpol *= dist0;
    interpol /= tmpdist01;
    interpol *= dist0;
    interpol /= tmpdist01;
    interpol += (fangle * dist0) / 100;
    interpol += tmpe0;
    el = (elevations_defined() ? elev_at(x, y) : 0);
    el += t_thickness(terrain_at(x, y));
    Dprintf("Arc at %d: %d, elev %d\n", celldist0, interpol, el);
    return (el > interpol);
}

int
found_blocking_elevation(int u, int ux, int uy, int uz,
                   int u2, int u2x, int u2y, int u2z)
{
    int t, weaponheight, bodyheight, rslt, x1, y1;

    /* Note that a flat world with no elevation defined could
       still have thick terrain that screens. */
    /* (should detect absence of thick terrain and return immed) */
    /* Adjacent cells can't be screened by elevation. */
    /* (should accommodate possibility that target is at top of
       cliff in adj and back away from its edge, thus screened;
       need to recog cliffs vs slopes in terrain) */
    if (distance(ux, uy, u2x, u2y) <= 1)
      return FALSE;
    tmpx0 = ux;  tmpy0 = uy;
    t = terrain_at(ux, uy);
    tmpe0 = (elevations_defined() ? elev_at(ux, uy) : 0);
    weaponheight = ut_weapon_height(u, t);
    tmpe0 += weaponheight;
    tmpe1 = (elevations_defined() ? elev_at(u2x, u2y) : 0);
    bodyheight = ut_body_height(u, t);
    tmpe1 += bodyheight;
    cellwid = area.cellwidth;
    if (cellwid <= 0)
      cellwid = 1;
    fangle = u_fire_angle_max(u);
    tmpdist01 = distance(ux, uy, u2x, u2y) * cellwid;
    Dprintf("Checking arc: %d,%d el %d -> %d,%d el %d\n", ux, uy, tmpe0, u2x, u2y, tmpe1);
    rslt = search_straight_line(ux, uy, u2x, u2y, elevation_blocks, &x1, &y1);
    if (rslt)
      Dprintf("blocked at %d,%d\n", x1, y1);
    return rslt;
}
 
static void
fire_on_unit(Unit *atker, Unit *other)
{
    int m, dist = distance(atker->x, atker->y, other->x, other->y), consump;

    if (alive(atker) && alive(other)) {
      if (enough_ammo(atker, other)) {
          maybe_hit_unit(atker, other, TRUE,
                     (dist > u_hit_falloff_range(atker->type)));
          for_all_material_types(m) {
            if (um_hit_by(other->type, m) > 0) {
                consump = um_consumption_per_fire(atker->type, m);
                if (consump < 0)
                  consump = um_consumption_per_attack(atker->type, m);
                atker->supply[m] -= consump;
            }
          }
          /* The *victim* can lose acp. */
          use_up_acp(other, uu_acp_to_be_fired_on(other->type, atker->type));
      }
    }
    /* (should ping victim to see if it wants to respond to the attack?) */
}

/* Test to see if enough ammo is available to make the attack.  Need
   enough of *all* types - semi-bogus but too complicated otherwise?  */

int
enough_ammo(Unit *unit, Unit *other)
{
    int m;

    for_all_material_types(m) {
      if (um_hit_by(other->type, m) > 0 &&
          unit->supply[m] < um_consumption_per_attack(unit->type, m))
        return FALSE;
    }
    return TRUE;
}

/* Single attack, no counterattack.  Check and use ammo - usage
   independent of outcome, but types used depend on unit types
   involved. */

static void
attack_unit(Unit *atker, Unit *other)
{
    int m;

    if (alive(atker) && alive(other)) {
      if (enough_ammo(atker, other)) {
          maybe_hit_unit(atker, other, FALSE, FALSE);
          for_all_material_types(m) {
            if (um_hit_by(other->type, m) > 0) {
                atker->supply[m] -=
                  um_consumption_per_attack(atker->type, m);
            }
          }
      }
    }
    /* (should ping victim to see if it wants to respond to the attack?) */
}

/* Make a single hit and maybe hit some passengers also.  Power of hit
   is constant, but chance is affected by terrain, quality, and
   occupants' protective abilities.  If a hit is successful, it may
   have consequences on the defender's occupants, but limited by the
   protection that the transport provides. */

static void
maybe_hit_unit(Unit *atker, Unit *other, int fire, int fallsoff)
{
    int chance, t, hit = 0, a = atker->type, o = other->type;
    int teffect, cxpeffect, cxpmax, effect, prot;
    int dist, disthit, rangedelta, hitdelta, rangeamt;
    int dmgspec;
    char *hitmovietype;
    Unit *occ, *unit2;
    Side *side3;
    SideMask sidemask;    

    Dprintf("%s tries to hit %s", unit_desig(atker), unit_desig(other));
    if (fire && uu_fire_hit(a, o) != -1)
      chance = uu_fire_hit(a, o);
    else
      chance = uu_hit(a, o);
    /* Combat experience tends to raise the hit chance, so do that
       first, reduces roundoff weirdnesses later. */
    cxpmax = u_cxp_max(a);
    if (cxpmax > 0 && atker->cxp > 0) {
      cxpeffect = uu_hit_cxp(a, o);
      if (cxpeffect != 100) {
          effect = 100 + (atker->cxp * (cxpeffect - 100)) / cxpmax;
          chance = (chance * effect) / 100;
      }
    }
    /* (should modify due to cxp of defender too) */
    /* Account for terrain effects. */
    t = terrain_at(atker->x, atker->y);
    if (fire && ut_fire_attack_terrain_effect(a, t) != -1)
      teffect = ut_fire_attack_terrain_effect(a, t);
    else
      teffect = ut_attack_terrain_effect(a, t);
    chance = (chance * teffect) / 100;
    t = terrain_at(other->x, other->y);
    if (fire && ut_fire_defend_terrain_effect(o, t) != -1)
      teffect = ut_fire_defend_terrain_effect(o, t);
    else
      teffect = ut_defend_terrain_effect(o, t);
    chance = (chance * teffect) / 100;
    /* Account for protection by occupants. */
    for_all_occupants(other, occ) {
      if (in_play(occ) && completed(occ)) {
          prot = uu_protection(occ->type, o);
          if (prot != 100)
            chance = (chance * prot) / 100;
      }
    }
    /* Account for protection by neighbours. */
    for_all_stack(other->x, other->y, unit2) {
      if (unit2 != other
          && in_play(unit2)
          && completed(unit2)
          && unit2->side == other->side) {
          prot = uu_stack_protection(unit2->type, o);
          if (prot != 100)
            chance = (chance * prot) / 100;
      }
    }
    /* Note that this code differs from that above in that it treats
       all units in the same cell equally, whether occs, suboccs or
       cellmates. This also means that one occ can protect another occ
       in the same transport. Moreover, the protective unit also
       protects itself. These rules simulate real situations such as
       when triple-A protects all nearby units (including itself)
       against bombers, or when a city wall protects all other
       occupants against ground attack. */
    for_all_stack_with_occs(other->x, other->y, unit2) {
      if (is_active(unit2)
          /* We also extend protection to our buddies! */
          && trusted_side(unit2->side, other->side)) {
          /* This is when a unit, such as triple-A, extends unique
             protection against a specific attacker, such as
             bombers, to all other units in the cell. */
          prot = uu_cellwide_protection_against(unit2->type, a);
          if (prot != 100)
            chance = (chance * prot) / 100;
          /* This is when a unit (such as a garrison) specifically
             protects a second unit, such as a fort (but not other
             nearby units), against all forms of attack. It thus
             works the same way as uu_protection and
             uu_stack_protection. */
          prot = uu_cellwide_protection_for(unit2->type, o);          
          if (prot != 100)
            chance = (chance * prot) / 100;
      }
    }
    if (fallsoff) {
      dist = distance(atker->x, atker->y, other->x, other->y);
      disthit = uu_hit_max_range_effect(a, o);
      rangedelta = u_range(a) - u_hit_falloff_range(a);
      rangeamt = dist - u_hit_falloff_range(a);
      hitdelta = uu_hit(a, o) - disthit;
      chance = uu_hit(a, o)
        - ((uu_hit(a, o) - disthit) * rangeamt) / rangedelta;
    }
    Dprintf(", probability of hit is %d%%", chance);
    /* Compute the hit itself. */
    if (probability(chance)) {
      if (fire && uu_fire_damage(a, o) != -1)
        dmgspec = uu_fire_damage(a, o);
      else
        dmgspec = uu_damage(a, o);
      /* Account for attacker's experience. */
      if (cxpmax > 0 && atker->cxp > 0) {
          cxpeffect = uu_damage_cxp(a, o);
          if (cxpeffect != 100) {
            effect = 100 + (atker->cxp * (cxpeffect - 100)) / cxpmax;
            dmgspec = multiply_dice(dmgspec, effect);
          }
      }
      hit = roll_dice(dmgspec);
    }
    if (hit > 0) {
      Dprintf(", damage will be %d hp", hit);
    } else {
      Dprintf(", missed");
    }
    /* (should record a raw statistic?) */
    /* Ablation is a chance for occupants or stack to take part of a
       hit themselves. */
    if (hit > 0) {
      /* (should decide how ablation computed) */
    }
    /* Affect the hitting unit's morale.  Note that morale is still reduced
       even if the hit becomes 0 because of a successful retreat. */
    if (hit > 0) {
      change_morale(atker, 1, uu_morale_hit(a, o));
    }
    if (hit > 0) {
      chance = uu_retreat_chance(a, o);
      /* Chance of a retreat rises to 100% as morale goes to 0. */
      if (u_morale_max(o) > 0 && chance < 100)
        chance = 100 - ((100 - chance) * other->morale) / u_morale_max(o);
      if (probability(chance)) {
          if (retreat_unit(other, atker)) {
            report_combat(atker, other, "retreat");
            hit = 0; /* should only be reduced hit, may still be > 0 */
          }
      }
    }
    hit_unit(other, hit, atker);
    /* Pick the hit movie to show. */
    hitmovietype =
      ((hit >= other->hp) ? "death" : ((hit > 0) ? "hit" : "miss"));
    /* Make up the list of sides that will see it. */
    sidemask = NOSIDES;
    for_all_sides(side3) {
      /* Let all sides that can see the attacker's cell see it. */
      if (units_visible(side3, atker->x, atker->y))
        sidemask = add_side_to_set(side3, sidemask);
      /* Let all sides that can see the defender's cell see it. */
      if (units_visible(side3, other->x, other->y))
        sidemask = add_side_to_set(side3, sidemask);
    }
    /* Show the movie. */
    for_all_sides(side3) {
      if (side_in_set(side3, sidemask))
        schedule_movie(side3, hitmovietype, other->id);
    }
    Dprintf("\n");
    /* Recurse into occupants, maybe hit them too.  */
    for_all_occupants(other, occ) {
      if (is_active(other)) {
          if (probability(uu_protection(o, occ->type)))
            maybe_hit_unit(atker, occ, fire, fallsoff);
      } else {
          /* No protection by incomplete transport. */
          maybe_hit_unit(atker, occ, fire, fallsoff);
      }
    }
    /* We get combat experience only if there could have been some damage. */
    if (chance > 0) {
      if (atker->cxp < u_cxp_max(a))
        atker->cxp += uu_cxp_per_combat(a, o);
      if (other->cxp < u_cxp_max(o))
        other->cxp += uu_cxp_per_combat(o, a);
      /* Occupants already gained their experience in the recursive call. */
    }
}

/* Do the hit itself. */

static void
hit_unit(Unit *unit, int hit, Unit *atker)
{
    int u = unit->type, u2, hpmin, dmgspec, tpdmg, tp;
    Side *aside;

    /* Some units might detonate automatically upon being hit. */
    if (hit > 0
        && atker != NULL
        && probability(uu_detonate_on_hit(u, atker->type))
        && !was_detonated(unit)) {
      detonate_unit(unit, unit->x, unit->y, unit->z);
      /* If the detonating unit still exists, then continue on to
         normal damage computation. */
    }
    if (hit > 0) {
      /* Record the loss of hp. */
      unit->hp2 -= hit;
      if (atker != NULL) {
          /* Attacker might not be able to do any more damage.  Note
             that the positioning of this code is such that all the
             usual side effects of combat happen, but the victim
             doesn't get any more worse off than it is already. */
          hpmin = uu_hp_min(atker->type, u);
          if (hpmin > 0 && hpmin > unit->hp2)
            unit->hp2 = hpmin;
          /* Affect the morale of the unit being hit. */
          change_morale(unit, -1, uu_morale_hit_by(atker->type, u));
      }
      /* Collateral damage may include loss of tooling. */
      if (unit->tooling && 1 /* any_tp_damage */) {
          for_all_unit_types(u2) {
            dmgspec = uu_tp_damage(u, u2);
            tpdmg = roll_dice(dmgspec);
            if (tpdmg != 0) {
                tp = unit->tooling[u2] - tpdmg;
                tp = max(0, min(tp, uu_tp_max(u, u2)));
                unit->tooling[u2] = tp;
            }
          }
      }
    }
    /* Maybe record for statistical analysis. */
    /* (this is only useful if code always goes through here - is that true?) */
    if (atker != NULL) {
      aside = atker->side;
      u2 = atker->type;
      if (aside->atkstats[u2] == NULL)
        aside->atkstats[u2] = (long *) xmalloc(numutypes * sizeof(long));
      if (aside->hitstats[u2] == NULL)
        aside->hitstats[u2] = (long *) xmalloc(numutypes * sizeof(long));
      ++((aside->atkstats[u2])[u]);
      (aside->hitstats[u2])[u] += hit;
    }
    /* Some units may detonate automatically just before dying. */
    if (hit > 0
        && unit->hp2 <= 0
        && probability(u_detonate_on_death(u))
        && !was_detonated(unit)) {
      detonate_unit(unit, unit->x, unit->y, unit->z);
    }
}

/* Hits on the main units have to be done later, so that mutual
   destruction works properly.  This function also does all the notifying. */

/* (What if occupants change type when killed, but transport vanishes?) */

static void
reckon_damage(int fire)
{
    /* Entertain everybody. */
    play_movies(ALLSIDES);
    /* Report the damage in more detail, now before the actual damage
       is taken (which may cause many units to disappear). */
    /* Normally we report the defender and then the attacker's damage,
       but if the defender dies, we report its counterattack results
       first and its death second. */
    if (!(omain->hp2 <= 0 && amain->hp2 > 0)) {
      report_damage(omain, amain, omain);
      if (!fire && will_report_damage(amain))
        report_damage(amain, omain, amain);
    } else {
      if (!fire)
        report_damage(amain, omain, amain);
      report_damage(omain, amain, omain);
    }
    damage_unit(omain, combat_dmg, amain);
    damage_unit(amain, combat_dmg, omain);
}

static void
reckon_damage_here(int x, int y)
{
    Unit *unit;

    for_all_stack(x, y, unit) {
      damage_unit(unit, combat_dmg, NULL);
    }
}

void
reckon_damage_around(int x, int y, int r)
{
    if (r > 0) {
      apply_to_area(x, y, r, reckon_damage_here);
    } else {
      reckon_damage_here(x, y);
    }
}

/* Given the units involved in combat, decide how to report it.  The
   strings like "destroy" and "miss" are symbolic, don't normally
   appear in the final output. */

static void
report_damage(Unit *unit, Unit *atker, Unit *mainunit)
{
    Unit *occ;

    /* Don't report supposed actions by dead units. */
    if (!alive(atker))
      return;
    if (unit->hp2 <= 0) {
      if (unit == mainunit) {
          report_combat(atker, unit, "destroy");
      } else {
          report_combat(atker, unit, "destroy-occupant");
      }
    } else if (unit->hp2 < unit->hp) {
      if (unit == mainunit) {
          report_combat(atker, unit, "hit");
      } else {
          report_combat(atker, unit, "hit-occupant");
      }
    } else {
      if (unit == mainunit) {
          report_combat(atker, unit, "miss");
      } else {
          report_combat(atker, unit, "miss-occupant");
      }
    }
    for_all_occupants(unit, occ) {
      report_damage(occ, atker, mainunit);
    }
}

/* Return true if the given unit or one of its occupants has been
   damaged. */

static int
will_report_damage(Unit *unit)
{
    Unit *occ;

    if (unit->hp2 < unit->hp)
      return TRUE;
    for_all_occupants(unit, occ) {
      if (will_report_damage(occ))
        return TRUE;
    }
    return FALSE;
}

/* Make the intended damage become real, and do any consequences. */
/* (should include agent unit with hevts if appropriate) */
void
damage_unit(Unit *unit, enum damage_reasons dmgreason, Unit *agent)
{
    int newacp;
    HistEventType hevttype;
    Obj *dameff;
    Unit *occ;

    /* Process all the occupants first. */
    for_all_occupants(unit, occ) {
      damage_unit(occ, dmgreason, agent);
    }
    /* If no damage was recorded, just return. */
    if (unit->hp2 == unit->hp) {
      set_was_detonated(unit, FALSE);
      return;
    }
    /* If unit is to die, do the consequences. */
    if (unit->hp2 <= 0) {
      if (u_wrecked_type(unit->type) == NONUTYPE) {
          rescue_occupants(unit);
          hevttype = (dmgreason == combat_dmg ? H_UNIT_KILLED :
                  (dmgreason == accident_dmg ? H_UNIT_DIED_IN_ACCIDENT :
                   /* (should define died-from-attrition type) */
                   (dmgreason == attrition_dmg ? H_UNIT_DIED_IN_ACCIDENT :
                    0)));
          kill_unit(unit, hevttype);
      } else {
          hevttype = (dmgreason == combat_dmg ? H_UNIT_WRECKED :
                  (dmgreason == accident_dmg ? H_UNIT_WRECKED_IN_ACCIDENT :
                   (dmgreason == attrition_dmg ? H_UNIT_WRECKED_IN_ACCIDENT :
                    0)));
          change_unit_type(unit, u_wrecked_type(unit->type), hevttype);
          /* Restore to default hp for the new type. */
          unit->hp = unit->hp2 = u_hp(unit->type);
          /* Get rid of occupants if overfull. */
          eject_excess_occupants(unit);
          /* change_unit_type already reported the wrecking as an event,
             do we need any additional reportage? */
      }
    } else {
      record_event(H_UNIT_DAMAGED, add_side_to_set(unit->side, NOSIDES),
                 unit->id, unit->hp, unit->hp2);
      /* Change the unit's hp. */
      unit->hp = unit->hp2;
      /* Perhaps adjust the acp down. */
      if (unit->act != NULL
          && unit->act->acp > 0
          && (dameff = u_acp_damage_effect(unit->type)) != lispnil) {
          newacp = damaged_acp(unit, dameff);
          /* The damaged acp limits the remaining acp, rather than trying
             to do some sort of proportional adjustment, which would be
             hard to get right. */
          /* (should account for occupant effects on acp) */
          unit->act->acp = min(unit->act->acp, newacp);
      }
    }
    /* Clear any detonation flag that might have been set. */
    if (alive(unit))
      set_was_detonated(unit, FALSE);
    /* Let the unit's owner know about all this. */
    update_unit_display(unit->side, unit, TRUE);
}

/* Occupants may be able to avoid the fate of their transport. */

void
rescue_occupants(Unit *unit)
{
    Unit *occ;

    for_all_occupants(unit, occ) {
      if (probability(uu_occ_escape(unit->type, occ->type))) {
          rescue_one_occupant(occ);
      }
    }
}

static void
rescue_one_occupant(Unit *occ)
{
    int rslt, dir, tmp, nx, ny;
    Unit *other;

    /* Try to escape into another unit in this cell. */
    for_all_stack(occ->x, occ->y, other) {
      if (other != occ->transport) {
          rslt = check_enter_action(occ, occ, other);
          if (valid(rslt)) {
            do_enter_action(occ, occ, other);
            return;
          }
      }
    }
    /* Try to move into an adjacent cell. */
    for_all_directions_randomly(dir, tmp) {
      if (interior_point_in_dir(occ->x, occ->y, dir, &nx, &ny)) {
          if (retreat_in_dir(occ, dir))
            return;
      }
    }
}

/* Retreat is a special kind of movement that a unit uses to avoid
   damage during combat. It bypasses some of the normal move rules. */

static int
retreat_unit(Unit *unit, Unit *atker)
{
    int dir;
    extern int retreating_from;

    /* First check that this type is able to retreat. */
    if (unit->act == NULL || !mobile(unit->type))
      return FALSE;
    retreating_from = atker->type;
    if (unit->x == atker->x && unit->y == atker->y) {
      dir = random_dir();
    } else {
      dir = approx_dir(unit->x - atker->x, unit->y - atker->y);
    }
    if (retreat_in_dir(unit, dir))
      return TRUE;
    if (flip_coin()) {
      if (retreat_in_dir(unit, left_dir(dir)))
        return TRUE;
      if (retreat_in_dir(unit, right_dir(dir)))
        return TRUE;
    } else {
      if (retreat_in_dir(unit, right_dir(dir)))
        return TRUE;
      if (retreat_in_dir(unit, left_dir(dir)))
        return TRUE;
    }
    retreating_from = NONUTYPE;
    return FALSE;
}

/* Try to beat a retreat in the given direction. */

static int
retreat_in_dir(Unit *unit, int dir)
{
    int nx, ny, rslt;
    Unit *other;
    extern int retreating;
    extern int retreating_from;

    /* (should it be possible for a unit to retreat out of the world?) */
    if (!interior_point_in_dir(unit->x, unit->y, dir, &nx, &ny))
      return FALSE;
    retreating = TRUE;
    rslt = check_move_action(unit, unit, nx, ny, unit->z);
    if (valid(rslt)) {
      do_move_action(unit, unit, nx, ny, unit->z);
      retreating = FALSE;
      retreating_from = NONUTYPE;
      return TRUE;
    }
    /* No luck moving; see if there's a friendly unit to enter. */
    for_all_stack(nx, ny, other) {
      rslt = check_enter_action(unit, unit, other);
      if (valid(rslt)) {
          do_enter_action(unit, unit, other);
          retreating = FALSE;
          retreating_from = NONUTYPE;
          return TRUE;
      }
    }
    return FALSE;
}

/* Capture action. */

/* Prepare a capture action to be executed later. */

int
prep_capture_action(Unit *unit, Unit *unit2, Unit *unit3)
{
    if (unit == NULL || unit->act == NULL || unit2 == NULL)
      return FALSE;
    unit->act->nextaction.type = ACTION_CAPTURE;
    unit->act->nextaction.args[0] = unit3->id;
    unit->act->nextaction.actee = unit2->id;
    return TRUE;
}

/* Execute a capture action. */

int
do_capture_action(Unit *unit, Unit *unit2, Unit *unit3)
{
    int rslt;

    attempt_to_capture_unit(unit2, unit3);
    use_up_acp(unit, uu_acp_to_capture(unit2->type, unit3->type));
    if (unit3->side == unit2->side)
      rslt = A_CAPTURE_SUCCEEDED;
    else
      rslt = A_CAPTURE_FAILED;
    return rslt;
}

/* Check the validity of a capture action. */

int
check_capture_action(Unit *unit, Unit *unit2, Unit *unit3)
{
    int u, u2, u3, acp, m;

    /* In combat model 1, we can't capture units directly. */
    if (g_combat_model() == 1)
      return A_ANY_ERROR;
    if (!in_play(unit))
      return A_ANY_ERROR;
    if (!in_play(unit2))
      return A_ANY_ERROR;
    if (!in_play(unit3))
      return A_ANY_ERROR;
    /* We can't capture ourselves. */
    if (unit2 == unit3)
      return A_ANY_ERROR;
    /* We can't capture units on our side. */
    if (unit2->side == unit3->side)
      return A_ANY_ERROR;    
    u = unit->type;
    u2 = unit2->type;
    u3 = unit3->type;
    acp = uu_acp_to_capture(u2, u3);
    if (acp < 1)
      return A_ANY_CANNOT_DO;
    if (capture_chance(u2, u3, unit3->side) == 0)
      return A_ANY_CANNOT_DO;
    if (distance(unit2->x, unit2->y, unit3->x, unit3->y) > 1)
      return A_ANY_ERROR;
    if (!has_enough_acp(unit, acp))
      return A_ANY_NO_ACP;
    /* We have to have a minimum level of supply to be able to capture. */
    /* (attack material table not really appropriate) */
    for_all_material_types(m) {
      if (unit2->supply[m] < um_to_attack(u2, m))
        return A_ANY_NO_MATERIAL;
    }
    return A_ANY_OK;
}

/* Handle capture possibility and repulse/slaughter. */

/* The chance to capture an enemy is modified by several factors.
   Neutrals have a different chance to be captured, and presence of
   occupants should also has an effect.  Can't capture anything that is
   on a kind of terrain that the capturer can't go on, unless victim has
   "bridge effect". */

/* (Need a little better treatment of committed assaults, where lack of
   success == death.) */

static void
attempt_to_capture_unit(Unit *atker, Unit *other)
{
    int a = atker->type, o = other->type, chance, prot;
    int ox = other->x, oy = other->y;
    Unit *occ, *unit2;
    Side *as = atker->side, *os = other->side;
    
    /* Low-tech sides are not going to succeed at even touching high-tech
       types, let alone capturing them. */
    if (atker->side
      && atker->side->tech[o] < u_tech_to_own(o))
      return;
    chance = capture_chance(a, o, other->side);
    if (alive(atker) && alive(other) && chance > 0) {
      if (impassable(atker, ox, oy) && !uu_bridge(o, a))
        return;
      /* Can possibly detonate on *any* attempt to capture! */
      if (probability(uu_detonate_on_capture(o, a))
          && !was_detonated(other)) {
          detonate_unit(other, other->x, other->y, other->z);
          /* Might not be possible to capture anything anymore. */
          if (!alive(atker) || !alive(other))
            return;
          /* Types of units might have changed, recalc things. */
          a = atker->type;  o = other->type;
          as = atker->side;  os = other->side;
          chance = capture_chance(a, o, other->side);
      }
      /* Occupants can protect the transport. */
      for_all_occupants(other, occ) {
          if (is_active(occ)) {
            prot = uu_protection(occ->type, o);
            if (prot != 100)
              chance = (chance * prot) / 100;
          }
      }
      for_all_stack(other->x, other->y, unit2) {
          if (unit2 != other
            && in_play(unit2)
            && completed(unit2)
            && unit2->side == other->side) {
            prot = uu_stack_protection(unit2->type, o);
            if (prot != 100)
              chance = (chance * prot) / 100;
          }
      }
      /* Note that this code differs from that above in that it
         treats all units in the same cell equally, whether occs,
         suboccs or part of the stack. This also means that one occ
         can protect another occ in the same transport. Moreover,
         the protective unit also protects itself. These rules
         simulate real situations such as when triple-A protects all
         nearby units (including itself) against bombers, or when a
         city wall protects all other occupants against ground
         attack. */
      for_all_stack_with_occs(other->x, other->y, unit2) {
          if (is_active(unit2)
            /* We also extend protection to our buddies! */
            && trusted_side(unit2->side, other->side)) {
            /* This is when a unit, such as triple-A, provides
               unique protection against a specific attacker, such
               as bombers, to all other units in the cell. */
            prot = uu_cellwide_protection_against(unit2->type, a);
            if (prot != 100)
              chance = (chance * prot) / 100;

            /* This is when a unit (such as a garrison)
               specifically protects a second unit, such as a fort
               (but not other nearby units), against all forms of
               attack. It thus works the same way as uu_protection
               and uu_stack_protection. */
            prot = uu_cellwide_protection_for(unit2->type, o);        
            if (prot != 100)
              chance = (chance * prot) / 100;
          }
      }
      /* Test whether the capture actually happens. */
      if (probability(chance)) {
          capture_unit(atker, other, H_UNIT_CAPTURED);
      } else if (atker->transport != NULL && 
               (impassable(atker, ox, oy) ||
                impassable(atker, atker->x, atker->y))) {
          /* was the capture attempt a one-way trip? */
          /* (should fix the test above - needs to be more accurate) */
          report_combat(atker, other, "resist/slaughter");
          kill_unit(atker, H_UNIT_KILLED /* should be something appropriate */);
      } else {
          report_combat(atker, other, "resist");
          /* (should record failed attempt to capture?) */
      }
      if (chance > 0) {
          if (atker->cxp < u_cxp_max(a))
            atker->cxp += uu_cxp_per_capture(a, o);
          /* (should not increment if side just changed?) */
          if (other->cxp < u_cxp_max(o))
            other->cxp += uu_cxp_per_capture(o, a);
      }
    }
}

int
capture_chance(int u, int u2, Side *side2)
{
    int chance, indepchance;

    chance = uu_capture(u, u2);
    if (side2 != NULL)
      return chance;
    indepchance = uu_indep_capture(u, u2);
    return (indepchance < 0 ? chance : indepchance);
}

/* There are many consequences of a unit being captured. */

void
capture_unit(Unit *unit, Unit *pris, int captype)
{
    int u = unit->type, px = pris->x, py = pris->y;
    Unit *occ;
    Side *ps = pris->side, *us = unit->side, *newside;

    newside = unit->side;
    /* Return a unit to its original side if we are buds with that side. */
    if (pris->origside != newside && trusted_side(us, pris->origside))
      newside = pris->origside;
    if (probability(uu_scuttle(pris->type, u))) {
      /* (should add terrain effect on success too) */
      /* (should characterize as a scuttle) */
      kill_unit(pris, H_UNIT_DISBANDED);
    }
    if (!unit_allowed_on_side(pris, newside)) {
      kill_unit(pris, H_UNIT_KILLED);
    }
    if (alive(pris)) {
      if (newside == pris->origside) {
          report_combat(unit, pris, "liberate");
      } else {
          report_combat(unit, pris, "capture");
      }
      /* Decide the fate of each occupant of our prisoner. */
      for_all_occupants(pris, occ) {
          capture_occupant(unit, pris, occ, newside);
      }
      /* The change of side itself.  This happens recursively to any
         remaining occupants as well. */
      change_unit_side(pris, newside, captype, unit);
      /* Garrison the newly-captured unit with hp from the capturing unit. */
      garrison_unit(unit, pris);
      capture_unit_2(unit, pris, ps);
      /* The people at the new location may change sides immediately. */
      if (people_sides_defined()
          && any_people_side_changes
          && probability(people_surrender_chance(pris->type, px, py))) {
          change_people_side_around(px, py, pris->type, unit->side);
      }
      if (control_sides_defined()) {
          if (ut_control_range(pris->type, terrain_at(px, py)) >= 0) {
            change_control_side_around(px, py, pris->type, unit->side);
          }
      }
         kick_out_enemy_users(unit->side, px, py);
    }

    /* Update everybody's view of the situation. */
    see_exact(ps, px, py);
    update_cell_display(ps, px, py, UPDATE_ALWAYS);
    all_see_cell(px, py);
}

/* Given that the main unit is going to be captured, decide what each occupant
   will do. */

static void
capture_occupant(Unit *unit, Unit *pris, Unit *occ, Side *newside)
{
    int u = unit->type;
    Unit *subocc;

    if (probability(uu_occ_escape(u, occ->type))) {
      /* The occupant escapes, along with all its suboccupants. */
      /* Retreat is not a perfect model, but close enough for now. */
      retreat_unit(occ, unit);
      report_combat(unit, occ, "escape");
    } else if (probability(uu_scuttle(occ->type, u))) {
      /* (should add terrain effect on success too) */
      /* (should characterize as a scuttle) */
      kill_unit(occ, H_UNIT_DISBANDED);
    } else if (capture_chance(u, occ->type, occ->side) > 0) {
      /* Side change will actually happen later. */
      for_all_occupants(occ, subocc) {
          capture_occupant(unit, occ, subocc, newside);
      }
    } else {
      /* Occupant can't live as a prisoner, but suboccs might, so recurse
         through them. */
      for_all_occupants(occ, subocc) {
          capture_occupant(unit, occ, subocc, newside);
      }
      /* Any suboccupants that didn't escape will die. */
      /* (what if subocc captured tho? should move elsewhere) */
      kill_unit(occ, H_UNIT_KILLED);
    }
}

/* Do additional consequences of capture. */

static void
capture_unit_2(Unit *unit, Unit *pris, Side *prevside)
{
    int chance, x, y, notesee;
    Unit *occ, *unit2;
    Side *newside;

    /* Our new unit's experience might be higher or lower, depending on what
       capture really means (change of crew perhaps). */
    pris->cxp = (pris->cxp * u_cxp_on_capture(pris->type)) / 100;
    pris->cxp = min(unit->cxp, u_cxp_max(pris->type));
    /* Getting captured is always bad for morale, but getting
       liberated is good. */
    if (pris->side == pris->origside)
      change_morale(pris, 1, ((pris->morale + 1) * 50) + 50);
    else
      pris->morale = pris->morale / 2;
    /* Clear any actions and plans. */
    /* Don't use init_unit_actorstate!  We just want to cancel any pending
       action, but leave all the acp values untouched. */
    if (pris->act)
      pris->act->nextaction.type = ACTION_NONE;
    /* (should probably adjust side's acp sums by new unit's amounts) */
    init_unit_plan(pris);
    /* Maybe get a pile of interesting information. */
    /* Independent units won't know anything about other independents
       though (they're independent, eh?). */
    newside = unit->side;
    if (newside && !newside->see_all && prevside != indepside) {
      notesee = FALSE;
      chance = u_see_terrain_captured(pris->type);
      if (!g_terrain_seen() && probability(chance)) {
          for_all_cells(x, y) {
            if (terrain_view(prevside, x, y) != UNSEEN
                && terrain_view(newside, x, y) == UNSEEN) {
                set_terrain_view(newside, x, y,
                             terrain_view(prevside, x, y));
                /* (should also view see-always units here) */
                update_cell_display(newside, x, y, UPDATE_ALWAYS);
                notesee = TRUE;
            }
          }
      }
      if (1 /* any see others chance for this type */) {
          for_all_side_units(prevside, unit2) {
            if (in_play(unit2)) {
                chance = uu_see_others_captured(pris->type, unit2->type);
                if (probability(chance)) {
                  see_exact(newside, unit2->x, unit2->y);
                  update_cell_display(newside, unit2->x, unit2->y,
                                  UPDATE_ALWAYS);
                  notesee = TRUE;
                }
            }
          }
      }
      /* Only say something to the capturing side if we actually got
         any new information - after several captures, may not be
         anything new to find out. */
      if (notesee) {
          notify(newside, "Enemy information fell into your hands!");
      }
    }
    /* Likewise for occupants. */
    for_all_occupants(pris, occ) {
      capture_unit_2(unit, occ, prevside);
    }
}

/* A detonate action just blasts the vicinity indiscriminately. */

int
prep_detonate_action(Unit *unit, Unit *unit2, int x, int y, int z)
{
    if (unit == NULL || unit->act == NULL || unit2 == NULL)
      return FALSE;
    unit->act->nextaction.type = ACTION_DETONATE;
    unit->act->nextaction.args[0] = x;
    unit->act->nextaction.args[1] = y;
    unit->act->nextaction.args[2] = z;
    unit->act->nextaction.actee = unit2->id;
    return TRUE;
}

int
do_detonate_action(Unit *unit, Unit *unit2, int x, int y, int z)
{
    int u2 = unit2->type;

    detonate_unit(unit2, x, y, z);
    /* Note that if the maxrange is further than the actual range of this
       detonation, only just-damaged units will be looked at. */
    reckon_damage_around(x, y, max_u_detonate_effect_range);
    /* Unit might have detonated outside its range of effect, so need
       this to make its own damage is accounted for. */
    if (alive(unit2))
      reckon_damage_around(unit2->x, unit2->y, 0);
    use_up_acp(unit, u_acp_to_detonate(u2));
    return A_ANY_DONE;
}

int
check_detonate_action(Unit *unit, Unit *unit2, int x, int y, int z)
{
    int u, u2, acp;

    if (!in_play(unit))
      return A_ANY_ERROR;
    if (!in_play(unit2))
      return A_ANY_ERROR;
    if (!inside_area(x, y))
      return A_ANY_ERROR;
    u = unit->type;
    u2 = unit2->type;
    /* The unit must actually be able to detonate. */
    acp = u_acp_to_detonate(u2);
    if (acp < 1)
      return A_ANY_CANNOT_DO;
    /* Can only detonate in our own or an adjacent cell. */
    /* (In other words, the detonating unit doesn't get to teleport
       its detonation effects to any desired faraway location.) */
    if (distance(unit2->x, unit2->y, x, y) > 1)
      return A_ANY_ERROR;
    if (!has_enough_acp(unit, acp))
      return A_ANY_NO_ACP;
    return A_ANY_OK;
}

static int tmpdetx, tmpdety;

/* Actual detonation may occur by explicit action or automatically; this
   routine makes the detonation effects happen, pyrotechnics and all. */

int
detonate_unit(Unit *unit, int x, int y, int z)
{
    int u = unit->type, dir, x1, y1, dmg, maxrange;
    Unit *unit2;
    Side *side;

    if (max_u_detonate_effect_range < 0) {
      int u2, u3, range;

      for_all_unit_types(u2) {
          for_all_unit_types(u3) {
            range = uu_detonation_range(u2, u3);
            max_u_detonate_effect_range =
              max(range, max_u_detonate_effect_range);
          }
      }
    }
    if (max_t_detonate_effect_range < 0) {
      int u2, t, range;

      for_all_unit_types(u2) {
          for_all_terrain_types(t) {
            range = ut_detonation_range(u2, t);
            max_t_detonate_effect_range =
              max(range, max_t_detonate_effect_range);
          }
      }
    }
    report_combat(unit, NULL, "detonate");
    for_all_sides(side) {
      if (active_display(side)
          && (side_sees_unit(side, unit)
            || units_visible(side, unit->x, unit->y))) {
          schedule_movie(side, "flash", unit->x, unit->y);
          /* a hack */
          if (strstr(u_type_name(u), "nuclear")
            || strstr(u_type_name(u), "nuke"))
            schedule_movie(side, "nuke", x, y);
      }
    }
    set_was_detonated(unit, TRUE);
    /* Hit the detonating unit first. */
    hit_unit_with_detonation(unit, u_hp_per_detonation(u), NULL);
    /* Hit units at ground zero. */
    for_all_stack(x, y, unit2) {
      if (unit2 != unit) {
          hit_unit_with_detonation(unit2, uu_detonation_damage_at(u, unit2->type), unit);
      }
    }
    damage_terrain(u, x, y);
    /* Hit units and/or terrain in adjacent cells, if this is defined. */
    if (max_u_detonate_effect_range >= 1) {
        for_all_directions(dir) {
          if (interior_point_in_dir(x, y, dir, &x1, &y1)) {
            for_all_stack(x1, y1, unit2) {
                dmg = uu_detonation_damage_adj(u, unit2->type);
                hit_unit_with_detonation(unit2, dmg, unit);
            }
          }
      }
    }
    if (max_t_detonate_effect_range >= 1) {
        for_all_directions(dir) {
          if (point_in_dir(x, y, dir, &x1, &y1)) {
            damage_terrain(u, x1, y1);
          }
      }
    }
    /* Hit units that are further away. */
    maxrange = max(max_u_detonate_effect_range, max_t_detonate_effect_range);
    if (maxrange >= 2) {
      tmpunit = unit;
      tmpdetx = x;  tmpdety = y;
      apply_to_area(x, y, maxrange, detonate_on_cell);
    }
    /* (should test compatibility of any new terrain types with each other;
        only after changes over with) */
    /* Entertain everybody. */
    play_movies(ALLSIDES);
    return TRUE;
}

static void
detonate_on_cell(int x, int y)
{
    int dist, dmg, sdmg;
    Unit *unit2;

    dist = distance(tmpdetx, tmpdety, x, y);
    if (dist > 1 && dist <= max_u_detonate_effect_range) {
      /* Since this code bypasses the occ recursion in
         maybe_hit_unit, we should also hit all occupants directly
         with the blast. */
      for_all_stack_with_occs(x, y, unit2) {
          if (dist <= uu_detonation_range(tmpunit->type, unit2->type)) {
            dmg = uu_detonation_damage_adj(tmpunit->type, unit2->type);
            /* Reduce by inverse square of the distance. */
            sdmg = (dmg * 100) / (dist * dist);
            dmg = prob_fraction(sdmg);
            hit_unit_with_detonation(unit2, dmg, tmpunit);
          }
      }
    }
    if (dist > 1 && dist <= max_t_detonate_effect_range) {
      damage_terrain(tmpunit->type, x, y);
    }
}

static void
hit_unit_with_detonation(Unit *unit, int hit, Unit *atker)
{
    char *hitmovietype;
    Side *side;

    hit_unit(unit, hit, atker);
    for_all_sides(side) {
      if (active_display(side)
          /* (should figure out visibility rules) */
          && (g_see_all()
              || side == unit->side
              || 0 /* visible to other sides */)) {
          hitmovietype = ((hit >= unit->hp) ? "death" :
                      ((hit > 0) ? "hit" : "miss"));
          schedule_movie(side, hitmovietype, unit->id);
      }
    }
}

/* Do the effect of detonation on the terrain at the given location. */

void
damage_terrain(int u, int x, int y)
{
    int t, t2, dir;

    /* Damage the cell's terrain. */
    t = terrain_at(x, y);
    if (probability(ut_detonation_damage(u, t))) {
      t2 = damaged_terrain_type(t);
      if (t2 == NONTTYPE) {
          run_error("bad damaged type?");
          return;
      } else if (t2 != t) {
          change_terrain_type(x, y, t2);
      }
    }
    /* Apply to auxiliary terrain also. */
    if (1 /* if any aux terrain */) {
      for_all_terrain_types(t) {
          switch (t_subtype(t)) {
            case cellsubtype:
            /* We already did this one. */
              break;
            case bordersubtype:
            if (1 /* any damage possible */) {
                for_all_directions(dir) {
                  if (border_at(x, y, dir, t)
                      && probability(ut_detonation_damage(u, t))) {
                      t2 = damaged_terrain_type(t);
                      if (t2 == NONTTYPE) {
                        set_border_at(x, y, dir, t, FALSE);
                      } else if (t2 != t) {
                        set_border_at(x, y, dir, t, FALSE);
                        set_border_at(x, y, dir, t2, TRUE);
                        /* There is potentially a problem with
                           some game designs here; if the new
                           type t2 can also be damaged by the
                           detonation, then the loop here will
                           damage it in turn, at least if t2
                           *follows* t in the list of terrain
                           types.  Preventing this would
                           require a lot of buffering, so it's
                           left as a limitation for now. */
                      }
                  }
                }
            }
              break;
            case connectionsubtype:
            if (1 /* any damage possible */) {
                for_all_directions(dir) {
                  if (connection_at(x, y, dir, t)
                      && probability(ut_detonation_damage(u, t))) {
                      t2 = damaged_terrain_type(t);
                      if (t2 == NONTTYPE) {
                        set_connection_at(x, y, dir, t, FALSE);
                      } else if (t2 != t) {
                        set_connection_at(x, y, dir, t, FALSE);
                        set_connection_at(x, y, dir, t2, TRUE);
                        /* Same issue here as with border
                                   damage. */
                      }
                  }
                }
            }
              break;
            case coatingsubtype:
            /* don't know how to damage coatings yet */
              break;
          }
      }
    }
}

int
damaged_terrain_type(int t)
{
    int t2, tot, othertot, test, sum, rslt;

    tot = othertot = 0;
    for_all_terrain_types(t2) {
      if (t_subtype(t2) == t_subtype(t)) {
          tot += tt_damaged_type(t, t2);
      } else {
          othertot += tt_damaged_type(t, t2);
      }
    }
    rslt = NONTTYPE;
    if ((tot + othertot) > 0) {
      test = xrandom(tot + othertot);
      sum = 0;
      for_all_terrain_types(t2) {
          if (t_subtype(t2) == t_subtype(t)) {
            sum += tt_damaged_type(t, t2);
            if (test < sum) {
                rslt = t2;
                break;
            }
          }
      }
    }
    /* Random values between tot and othertot will have
       fallen through the loop, and the rslt is NONTTYPE,
       which indicates that the terrain must be removed
       if possible. */
    /* Paranoia check */
    if (rslt != NONTTYPE && t_subtype(rslt) != t_subtype(t))
      run_error("badness in damaged_terrain_type");
    return rslt;
}

/* Tests for whether given units or types can do particular types of
   actions. */

int
could_hit(int u1, int u2)
{
    return (uu_hit(u1, u2) > 0 || uu_fire_hit(u1, u2) > 0);
}

int
can_attack(Unit *unit)
{
    return type_can_attack(unit->type);
}

int
type_can_attack(int u)
{
    int u2;
      
    for_all_unit_types(u2) {
      if (uu_acp_to_attack(u, u2) > 0
          && uu_hit(u, u2) > 0
          && uu_damage(u, u2) > 0)
        return TRUE;
    }
    return FALSE;
}

int
can_fire(Unit *unit)
{
    return type_can_fire(unit->type);
}

int
type_can_fire(int u)
{
    int u2, fhit, fdam;
      
    if (u_acp_to_fire(u) == 0)
      return FALSE;
    for_all_unit_types(u2) {
      fhit = uu_fire_hit(u, u2);
      fdam = uu_fire_damage(u, u2);
      if ((fhit == -1 ? (uu_hit(u, u2) > 0) : (fhit > 0))
           && (fdam == -1 ? (uu_damage(u, u2) > 0) : (fdam > 0)))
        return TRUE;
    }
    return FALSE;
}

int
type_can_capture(int u)
{
    int u2;
      
    for_all_unit_types(u2) {
      /* Capture can happen by both direct action and as result of an
         attack in combat model 0. */
      if (g_combat_model() == 0) {
            if ((uu_acp_to_attack(u, u2) > 0 || uu_acp_to_capture(u, u2) > 0)
                && (uu_capture(u, u2) > 0 || uu_indep_capture(u, u2) > 0))
              return TRUE;
      /* Capture can only happen as result of an attack in combat model 1. */
      } else if (g_combat_model() == 1) {
            if (uu_acp_to_attack(u, u2) > 0
                && (uu_capture(u, u2) > 0 || uu_indep_capture(u, u2) > 0))
              return TRUE;
      }
    }
    return FALSE;
}

int
can_detonate(Unit *unit)
{
    return (u_acp_to_detonate(unit->type) > 0);
}

int
can_capture_directly(Unit *unit)
{
    return type_can_capture_directly(unit->type);
}

/* Tests if type can capture without first attacking. */

int
type_can_capture_directly(int u)
{
    int u2;

    /* In combat model 1, we can't capture units directly. */
    if (g_combat_model() == 1)
      return FALSE;
    for_all_unit_types(u2) {
      if (uu_acp_to_capture(u, u2) > 0
          && (uu_capture(u, u2) > 0 
            || uu_indep_capture(u, u2) > 0)) {
          return TRUE;
      }
    }
    return FALSE;
}

/* Tests if type can have any occupants at all. */

int
type_can_carry(int u)
{
    int u2;

    for_all_unit_types(u2) {
      if (could_carry(u, u2))
        return TRUE;
    }
    return FALSE;
}

/* True if unit has enough occupants assigned to its defense. */

int
defended_by_occupants(Unit *unit)
{
    Unit *unit2;
    int defenders = 0;

    for_all_occupants(unit, unit2) {
      if (is_active(unit2)
          && unit2->plan
          && unit2->plan->maingoal
          && unit2->plan->maingoal->type == GOAL_UNIT_OCCUPIED
          && unit2->plan->maingoal->args[0] == unit->id
          /* Occs that cannot defend the transport should not be
             assigned to occupy it, but check anyway for ability to
             protect. */
          && occ_can_defend_transport(unit2->type, unit->type)) {
          ++defenders;
      }
    }
    if (enough_to_garrison(unit, defenders))
      return TRUE;
    else 
      return FALSE;
}

/* True if first type can both occupy and protect second type. */

int
occ_can_defend_transport(int o, int t)
{
      /* Never true if transport cannot carry occupant. */
      if (!could_carry(t, o))
          return FALSE;
      if (g_combat_model() == 0) {
            /* Specific protection of transport by its occupant.
            Note: zero protection is 100, full protection is 0! */
            if (uu_protection(o, t) < 100)
                return TRUE;
            /* Cellwide protection of all units by occupant. */
            if (uu_cellwide_protection_for(o, t) < 100)
                return TRUE;
      } else if (g_combat_model() == 1) {
            /* Advanced units are protected by any kind of occupant
            that has a positive defense value. */
             if (u_advanced(t) && u_defend(o) > 0) {
                  return TRUE;
            /* Non-advanced units are protected by this table. */ 
            } else if (!u_advanced(t)
                      /*      Note: More than 100 is protection. */
                       && uu_occ_affects_defense(o, t) > 100) {
                  return TRUE;
            }
      }
      return FALSE;     
}

/* True if the specified number of defenders is sufficient to defend
   the unit. Note that this garrison does not have to exist! */

int
enough_to_garrison(Unit *unit, int defenders)
{
    int u = unit->type;
    Unit *unit2;
      
    /* We only worry about enemies within our tactical range. */  
    unit2 = mobile_enemy_threat(unit, u_ai_tactical_range(u));
    /* We are threatened by the enemy! */
    if (unit2 && defenders < u_ai_war_garrison(u))
      return FALSE;
    else if (defenders < u_ai_peace_garrison(u))
      /* No enemy in sight. */ 
      return FALSE;
    else
      return TRUE;
}

/* Returns the first mobile enemy unit found within the specified
   range that can hit or capture our unit. */

Unit *
mobile_enemy_threat(Unit *unit, int range)
{
    int x, y;
    Unit *unit2;
      
    /* Go through all cells within the specified range. */
    for_all_cells_within_range(unit->x, unit->y, range, x, y) {
      if (!inside_area(x, y))
        continue;
      if (!terrain_visible(unit->side, x, y))
        continue;
      /* Important to count also occupants here. */
      for_all_stack_with_occs(x, y, unit2) {
          /* Only count visible mobile enemy units that actually
             threaten us. */
          if (is_active(unit2)
            /* The NULL side rarely attacks anybody :-) */
            && unit2->side
            && mobile(unit2->type)
            && side_sees_image(unit->side, unit2)
            && !trusted_side(unit->side, unit2->side)
            && (could_hit(unit2->type, unit->type)
                || capture_chance(unit2->type, unit->type, unit->side) > 0)) {
            return unit2;
          }
      }
    }
    return NULL;
}


Generated by  Doxygen 1.6.0   Back to index