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

plan.c

/* Unit plan handling for Xconq.
   Copyright (C) 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"

extern short any_auto_repair;

static void plan_passive(Unit *unit);
static void plan_offense(Unit *unit);
static void plan_offense_support(Unit *unit);
static void plan_defense(Unit *unit);
static void plan_exploration(Unit *unit);
static void plan_explorer_support(Unit *unit);
static void plan_improve(Unit *unit);
static void plan_colonize(Unit *unit);
static void plan_colonize_support(Unit *unit);
static int good_cell_to_colonize(int x, int y);
static void plan_random(Unit *unit);
static void wake_at(int x, int y);
static int resupply_if_low(Unit *unit);
static int rearm_if_low(Unit *unit);
static int repair_if_damaged(Unit *unit);
static int repairs_here(Unit *unit, int x, int y);
static int alternate_target_here(int x, int y);
static int side_planning_to_capture(Side *side, int u, int x, int y);
static int do_for_occupants(Unit *unit);
static int go_after_victim(Unit *unit, int range);
static int fire_at_opportunity(Unit *unit);
static int explore_reachable_cell(Unit *unit, int range);
static int capture_useful_if_nearby(Unit *unit);
static int capture_indep_if_nearby(Unit *unit);
static void random_walk(Unit *unit);
static int worth_capturing(Side *side, int u2, Side *oside, int x, int y);
static int supplies_here(Unit *unit, int x, int y, int m);
static int indep_captureable_here(int x, int y);
static int useful_type(Side *side, int u);
static int could_capture_any(int u);
static Plan *create_plan(void);
static int might_be_captured(Unit *unit);
static int occupant_could_capture(Unit *unit, int etype);
static int can_capture_neighbor(Unit *unit);
static int occupant_can_capture_neighbor(Unit *unit);
static int find_closest_unit(Side *side, int x0, int y0, int maxdist,
                       int (*pred)(int x, int y), int *rxp, int *ryp);
static int reachable_unknown(int x, int y);
static int adj_known_ok_terrain(int x, int y, Side *side, int u);
static int normal_completion_time(int u, int u2);
static int self_build_base_for_self(Unit *unit);

#if 0
static int go_after_captive(Unit *unit, int range);
static int range_left(Unit *unit);
static int find_worths(int range);
static int attack_worth(Unit *unit, int e);
static int threat(Side *side, int u, int x0, int y0);
static int move_patrol(Unit *unit);
static int build_time(Unit *unit, int prod);
static int out_of_ammo(Unit *unit);
static int explorable_cell(int x, int y);
static int should_capture_maker(Unit *unit);
extern int adj_unit(int x, int y);
#endif

#define CLEAR_AGENDA 99

/* (should have a generic struct for all plan type attrs) */

char *plantypenames[] = {

#undef  DEF_PLAN
#define DEF_PLAN(NAME,code) NAME,

#include "plan.def"

    NULL
};

/* Every unit that can act needs a plan object, types that can't act
   should have it cleared out.  Note that incomplete units are expected
   to be able to act in the future, so it's acceptable to run this for
   incomplete units to give them a plan. */

void
init_unit_plan(Unit *unit)
{
    if (u_acp(unit->type) > 0
      /* Acp-independent units still need a plan. */
      || acp_indep(unit)) {
      /* Might already have a plan, so don't always realloc. */
      if (unit->plan == NULL) {
          unit->plan = create_plan();
      }
      /* Put the plan into a default state, side will work it up later. */
      /* (should release goals also) */
      clear_task_agenda(unit->plan);
      /* Zero the plan just as xmalloc would. */
      memset(unit->plan, 0, sizeof(Plan));
      unit->plan->type = PLAN_PASSIVE;
      unit->plan->creation_turn = g_turn();
      /* Allow AIs to make this unit do things. */
      unit->plan->aicontrol = TRUE;
      /* Enable supply alarms by default. */
      unit->plan->supply_alarm = TRUE;
      /* Clear the task outcome. */
      unit->plan->last_task_outcome = TASK_UNKNOWN;
    } else {
      /* Brainless units don't need anything, can free up plan. */
      if (unit->plan != NULL) {
          free_plan(unit->plan);
      }
      unit->plan = NULL;
    }
}

void
set_unit_plan_type(Side *side, Unit *unit, int type)
{
    int oldtype;

    if (unit->plan) {
      oldtype = unit->plan->type;
      if (type != oldtype) {
          if (type == PLAN_NONE) {
            type = PLAN_PASSIVE;
            force_replan(side, unit, FALSE);
          }
          unit->plan->type = type;
          if (side != NULL)
            update_unit_display(side, unit, TRUE);
      }
    }
}

void
set_unit_asleep(Side *side, Unit *unit, int flag, int recurse)
{
    int oldflag;
    Unit *occ;

    if (unit->plan) {
      oldflag = unit->plan->asleep;
      if (flag != oldflag) {
          unit->plan->asleep = flag;
          if (side != NULL)
            update_unit_display(side, unit, TRUE);
      }
    }
    if (recurse) {
      for_all_occupants(unit, occ) {
          set_unit_asleep(side, occ, flag, recurse);
      }
    }
}

void
set_unit_reserve(Side *side, Unit *unit, int flag, int recurse)
{
    int oldflag;
    Unit *occ;

    if (unit->plan) {
      oldflag = unit->plan->reserve;
      if (flag != oldflag) {
          unit->plan->reserve = flag;
          if (side != NULL)
            update_unit_display(side, unit, TRUE);
      }
    }
    if (recurse) {
      for_all_occupants(unit, occ) {
          set_unit_reserve(side, occ, flag, recurse);
      }
    }
}

void
set_unit_ai_control(Side *side, Unit *unit, int flag, int recurse)
{
    int oldflag;
    Unit *occ;

    if (unit->plan) {
      oldflag = unit->plan->aicontrol;
      if (flag != oldflag) {
          unit->plan->aicontrol = flag;
          if (side != NULL)
            update_unit_display(side, unit, TRUE);
      }
    }
    if (recurse) {
      for_all_occupants(unit, occ) {
          set_unit_ai_control(side, occ, flag, recurse);
      }
    }
}

void
set_unit_curadvance(Side *side, Unit *unit, int a)
{    
      unit->curadvance = a;
}

void
set_unit_autoplan(Side *side, Unit *unit, int flag)
{    
      unit->autoplan = flag;
}

void
set_unit_autoresearch(Side *side, Unit *unit, int flag)
{    
      unit->autoresearch = flag;
}

void
set_unit_autobuild(Side *side, Unit *unit, int flag)
{    
      unit->autobuild = flag;
}

void
set_unit_buildingdone(Side *side, Unit *unit, int flag)
{    
      unit->buildingdone = flag;
}

void
set_unit_researchdone(Side *side, Unit *unit, int flag)
{    
      unit->researchdone = flag;
}

void
set_unit_main_goal(Side *side, Unit *unit, Goal *goal)
{
    if (unit->plan) {
      unit->plan->maingoal = goal;
    }
}

void
set_unit_waiting_for_transport(Side *side, Unit *unit, int flag)
{
    if (unit->plan) {
      unit->plan->waitingfortransport = flag;
    }
}

/* Execute the plan. */

int
execute_plan(Unit *unit)
{
    Plan *plan = unit->plan;

    if (!in_play(unit) || !completed(unit)) {
      DMprintf("%s shouldn't be planning yet\n", unit_desig(unit));
      return 0; 
    }
    DMprintf("%s using plan %s\n", unit_desig(unit), plan_desig(plan));
    /* Units that are asleep or in reserve do nothing. */
    /* (This never happens according to debugging). */
    if (plan->asleep || plan->reserve) {
      return 0;
    }

    if (plan->type == PLAN_PASSIVE && plan->execs_this_turn > 10) {
                DMprintf(" not found\n");
      
    }

    if (plan->execs_this_turn > 1000 && g_units_may_go_into_reserve()) {
      DMprintf("%s executed plan 1000 times this turn, going into reserve\n",
             unit_desig(unit));
      plan->reserve = TRUE;
      return 1;
    }
    /* Unit actually has a plan, dispatch on its type. */
    switch (plan->type) {
      case PLAN_NONE:
      /* Unit has not gotten a plan yet, leave it alone. */
      break;
      case PLAN_PASSIVE:
      plan_passive(unit);
      break;
      case PLAN_OFFENSIVE:
      plan_offense(unit);
      break;
      case PLAN_DEFENSIVE:
      plan_defense(unit);
      break;
      case PLAN_EXPLORATORY:
      plan_exploration(unit);
      break;
      case PLAN_COLONIZING:
      plan_colonize(unit);
      break;
      case PLAN_IMPROVING:
      plan_improve(unit);
      break;
      case PLAN_RANDOM:
      plan_random(unit);
      break;
      default:
      case_panic("plan type", plan->type);
      break;
    }
    ++(plan->execs_this_turn);
    return 1;
}

/* See if we're too far away from an assigned position, set a task
   to move back if so. */

int
move_into_formation(Unit *unit)
{
    int nx, ny, dist; 
    Plan *plan = unit->plan;
    Goal *goal;
    Unit *leader;

    leader = plan->funit;
    if (leader != NULL) {
      goal = plan->formation;
      /* Ensure that the leader is still someone we want to follow. */
      if (!in_play(leader)
          || !unit_trusts_unit(unit, leader)
          || goal->args[0] != leader->id) {
          notify(unit->side, "%s leader is gone, cancelling formation",
               unit_handle(unit->side, unit));
          /* (should free goal object?) */
          plan->formation = NULL;
          plan->funit = NULL;
          /* Unit is available to do something else. */
          return FALSE;
      }
      nx = leader->x + goal->args[1];  ny = leader->y + goal->args[2];
      dist = goal->args[3];
      if (distance(unit->x, unit->y, nx, ny) > dist) {
          /* (should perhaps insert after current task?) */
          set_move_to_task(unit, nx, ny, dist);
          return TRUE;
      }
    }
    return FALSE;
}

int task_is_in_agenda(Plan *plan, Task *task);

/* See if there are any standing orders that currently apply to the given unit,
   and schedule a task if so.  Return TRUE if a task was added. */

int
execute_standing_order(Unit *unit, int addtask)
{
    Unit *transport;
    Side *side = unit->side;
    StandingOrder *sorder;

    for (sorder = side->orders; sorder != NULL; sorder = sorder->next) {
      if (sorder->types[unit->type] && unit->plan) {
          switch (sorder->condtype) {
            case sorder_at:
            if (unit->x == sorder->a1 && unit->y == sorder->a2) {
                /* If the task is already in the plan, don't do
                   anything. */
                if (task_is_in_agenda(unit->plan, sorder->task))
                  return FALSE;
                if (addtask)
                  add_task(unit, 0, clone_task(sorder->task));
                return TRUE;
            }
            break;
            case sorder_in:
            transport = unit->transport;
            if (transport != NULL && transport->id == sorder->a1) {
                /* If the task is already in the plan, don't do
                   anything. */
                if (task_is_in_agenda(unit->plan, sorder->task))
                  return FALSE;
                if (addtask)
                  add_task(unit, 0, clone_task(sorder->task));
                return TRUE;
            }
            break;
            case sorder_near:
            if (distance(unit->x, unit->y, sorder->a1, sorder->a2) <= sorder->a3) {
                /* If the task is already in the plan, don't do
                   anything. */
                if (task_is_in_agenda(unit->plan, sorder->task))
                  return FALSE;
                if (addtask)
                  add_task(unit, 0, clone_task(sorder->task));
                return TRUE;
            }
            break;
            default:
            run_warning("Unknown order condition type");
            break;
          }
      }
    }
    return FALSE;
}

int tasks_match(Task *task1, Task *task2);

int
task_is_in_agenda(Plan *plan, Task *task)
{
    Task *task2;

    for (task2 = plan->tasks; task2 != NULL; task2 = task2->next) {
      if (tasks_match(task, task2))
        return TRUE;
    }
    return FALSE;
}

int
tasks_match(Task *task1, Task *task2)
{
    int i;

    if (task1->type != task2->type)
      return FALSE;
    for (i = 0; i < MAXTASKARGS; ++i)
      if (task1->args[i] != task2->args[i])
      return FALSE;
    return TRUE;
}

/* Passive units just work from the task queue or else wait to be told
   what to do. */

static void
plan_passive(Unit *unit)
{
    Plan *plan = unit->plan;

    /* Don't allow passive units under ai control. */
    if (ai_controlled(unit)) {
      force_replan(unit->side, unit, FALSE);
      return;
    }
    /* Special-case human cities/towns in the intro game to automatically
       start producing infantry initially. */
    /* (would be more efficient to put in a once-per-turn location,
       should look for one) */
    if (g_turn() <= 1
      && mainmodule != NULL
      && ((mainmodule->name != NULL
           && strcmp(mainmodule->name, INTRO_GAME) == 0)
          || (mainmodule->origmodulename != NULL
            && strcmp(mainmodule->origmodulename, INTRO_GAME) == 0))
      && ((strcmp(u_type_name(unit->type), "city") == 0)
          || (strcmp(u_type_name(unit->type), "town") == 0))) {
      push_build_task(unit, 0, 99, 0, 0);
    }
    if (plan->supply_is_low && plan->supply_alarm) {
      plan->supply_alarm = FALSE;
      if (0 /* auto resupply */) {
          set_resupply_task(unit, NONMTYPE);
      } else if (plan->tasks
               && (plan->tasks->type == TASK_RESUPPLY
                   || (plan->tasks->type == TASK_MOVE_TO
                     && plan->tasks->next
                     && plan->tasks->next->type == TASK_RESUPPLY))) {
          /* do nothing */
      } else {           
          clear_task_agenda(plan);
          set_waiting_for_tasks(unit, TRUE);
      }
    }
    if (plan->tasks) {
      /* (should check that doctrine being followed correctly) */
      execute_task(unit);
    } else if (unit->side
             && unit->side->orders
             && execute_standing_order(unit, TRUE)) {
      execute_task(unit);
    } else if (plan->formation && move_into_formation(unit)) {
      execute_task(unit);
    } else {
      /* Our goal is now to get guidance from the side. */
      set_waiting_for_tasks(unit, TRUE);
    }
}

/* A unit operating offensively advances and attacks when possible. */

int find_alternate_hit_target(Unit *unit, Task *task, int *xp, int *yp);

static void
plan_offense(Unit *unit)
{
    int u = unit->type;
    int x, y, w, h, range, x1, y1, nx, ny;
    Plan *plan = unit->plan;
    Task *lasttask;
    Unit *unit2;

    if (resupply_if_low(unit)) {
      if (plan->tasks)
        execute_task(unit);
      return;
    }
    if (rearm_if_low(unit)) {
      if (plan->tasks)
        execute_task(unit);
      return;
    }
    if (repair_if_damaged(unit)) {
      if (plan->tasks)
        execute_task(unit);
      return;
    }
    if (plan->tasks) {
      execute_task(unit);
      if (plan->last_task_outcome == TASK_FAILED) {
          lasttask = &(plan->last_task);
          if (lasttask->type == TASK_HIT_UNIT
            && lasttask->args[2] != NONUTYPE
            && !target_visible(unit, lasttask)) {
            /* Target seems to have disappeared, look around for it. */
            DMprintf("%s hit target has disappeared, looking for it; ",
                   unit_desig(unit));
            if (find_alternate_hit_target(unit, lasttask, &nx, &ny)) {
                if (plan->tasks
                  && plan->tasks->type == lasttask->type
                  && plan->tasks->args[0] == lasttask->args[0]
                  && plan->tasks->args[1] == lasttask->args[1]
                  && plan->tasks->args[2] == lasttask->args[2]
                  && plan->tasks->args[3] == lasttask->args[3]
                  ) {
                  pop_task(plan);
                }
                push_hit_unit_task(unit, nx, ny, lasttask->args[2], lasttask->args[3]);
                DMprintf(" found at %d,%d\n", nx, ny);
            } else {
                DMprintf(" not found\n");
            }
          }
      }
      /* Irrespective of what happened, we don't want to step on the task
         yet. */
      return;
    }
    if (plan->maingoal && mobile(u)) {
      switch (plan->maingoal->type) {
        case GOAL_UNIT_OCCUPIED:
          /* Move to occupy our goal if necessary. */
          unit2 = find_unit(plan->maingoal->args[0]);
          if (in_play(unit2) && unit->transport != unit2)
            set_occupy_task(unit, unit2);
          break;

        case GOAL_CELL_OCCUPIED:
          /* Move to occupy our goal if necessary. */
          x = plan->maingoal->args[0];  
          y = plan->maingoal->args[1];
          if (unit->x != x || unit->y != y)
            set_move_to_task(unit, x, y, 0);
          break;

        case GOAL_VICINITY_HELD:
          x = plan->maingoal->args[0];  y = plan->maingoal->args[1];
          w = plan->maingoal->args[2];  h = plan->maingoal->args[3];
          if (distance(x, y, unit->x, unit->y) > max(w, h)) {
            /* Outside the goal area - move in towards it. */
            if (random_point_near(x, y, w / 2, &x1, &y1)) {
                x = x1;  y = y1;
            }
            DMprintf("%s to go on offensive to %d,%d\n",
                   unit_desig(unit), x, y);
            set_move_to_task(unit, x, y, max(w, h) / 2);
            if (unit->transport
                && mobile(unit->transport->type)
                && unit->transport->plan) {
                set_move_to_task(unit->transport, x, y, max(w, h) / 2);
            }
          } else {
            range = max(w, h);
            /* No special goal, look for something to fight with. */
            /* Sometimes be willing to look a little farther out. */
            if (probability(50))
              range *= 2;
            if (do_for_occupants(unit)) {
                /* Occupants have decided for us, fall through. */
            } else if (go_after_victim(unit, range)) {
                /* Found a victim to go after, fall through. */
            } else if (probability(20) && self_build_base_for_self(unit)) {
            } else if (!g_see_all()) {
                DMprintf("%s will explore instead\n", unit_desig(unit));
                plan_exploration(unit); /* or patrol */
                /* Running under exploration rules now. */
                return;
            } else {
                /* Do a random walk instead of just sitting there. */
                DMprintf("%s to walk randomly\n", unit_desig(unit));
                random_walk(unit);
            }
          }
          break;
        default:
          DMprintf("offensive unit has some goal\n");
          break;
      }
    } else if (mobile(u)) {
      /* Play it safe. Search every cell within 3 times the tactical range.
      But don't search the whole world! */
      range = min(3 * u_ai_tactical_range(u), operating_range_best(u));
      if (probability(50))
        range = min(range, 2 * u_acp(u));
      if (do_for_occupants(unit)) {
      } else if (go_after_victim(unit, range)) {
          /* No special goal, but found something to fight with. */
      } else if (!g_see_all()) {
          DMprintf("%s will explore instead\n", unit_desig(unit));
          plan_exploration(unit); /* or patrol */
          /* Running under exploration rules now. */
          return;
      } else {
          /* should go to a "best location" if possible. */
          /* (should do a sentry task) */
      }
    } else if (can_fire(unit) && fire_at_opportunity(unit)) {
    } else {
      plan_offense_support(unit);
    }
    if (plan->tasks) {
      execute_task(unit);
    } else {
      DMprintf("%s found nothing to do offensively", unit_desig(unit));
      /* Only do this if units may go into reserve. */
      if (flip_coin() && g_units_may_go_into_reserve()) {
          DMprintf("- going into reserve");
          plan->reserve = TRUE;
      }
      DMprintf("\n");
    }
}

/* Look through list of occupants to see if an occupant needs the
   transport to do something. */

int
do_for_occupants(Unit *unit)
{
    Unit *occ;
    Goal *goal;
    Task *task;

    for_all_occupants(unit, occ) {
      if (occ->plan) {
          /* Get the unit towards its goal, if it has one. */
          goal = occ->plan->maingoal;
          if (goal != NULL
            && goal->type == GOAL_VICINITY_HELD
            && (distance(goal->args[0], goal->args[1], unit->x, unit->y)
                > goal->args[2])) {
            set_move_to_task(unit, goal->args[0], goal->args[1],
                          max(goal->args[2] / 2, 1));
            DMprintf("%s will go where occupant %s wants to go (goal %s)\n",
                   unit_desig(unit), unit_desig(occ), goal_desig(goal));
            return TRUE;
          }
          /* If the unit does not have a goal, see if it has a task. */
          for_all_tasks(occ->plan, task) {
            if ((task->type == TASK_MOVE_TO
                 || task->type == TASK_HIT_UNIT)
                && (task->args[0] != unit->x
                  || task->args[1] != unit->y)
                  && distance(task->args[0], task->args[1], unit->x, unit->y) > 1
                  ) {
                /* Note that we assume the transport is mobile,
                   which is OK currently because of where this
                   routine is called from. */
                set_move_to_task(unit, task->args[0], task->args[1], 1);
                DMprintf("%s will go where occupant %s wants to go (task %s)\n",
                       unit_desig(unit), unit_desig(occ), task_desig(task));
                return TRUE;
            }
          }
      }
    }
    return FALSE;
}

int
self_build_base_for_self(Unit *unit)
{
    int u = unit->type, u2, cando = FALSE;

    for_all_unit_types(u2) {
      if (uu_acp_to_create(u, u2) > 0
          && ((uu_creation_cp(u, u2) >= u_cp(u2)
             && side_can_build(unit->side, u2))
            || uu_acp_to_build(u, u2) > 0)
          /* (should check if any advantage to building) */
          ) {
         cando = TRUE;
         break;
      }
    }
    if (cando) {
      DMprintf("%s building %s as a base for itself\n",
                 unit_desig(unit), u_type_name(u2));
      set_build_task(unit, u2, 1, 0, 0);
      return TRUE;
    }
    return FALSE;
}

static void
plan_offense_support(Unit *unit)
{
    int u = unit->type, u2, u3 = NONUTYPE, backup = NONUTYPE;
    Task *task;

    for_all_unit_types(u2) {
          if (mobile(u2)
            && (type_can_attack(u2) || type_can_fire(u2))
            && side_can_build(unit->side, u2)
            && uu_acp_to_create(u, u2) > 0) {
            backup = u2;
            if (flip_coin()) {
                u3 = u2;
                break;
            }
          }
    }
    if (u3 == NONUTYPE)
      u3 = backup;
    if (is_unit_type(u3)) {
      task = unit->plan->tasks;
      if (task == NULL || task->type != TASK_BUILD) {
          DMprintf("%s supporting offense by building %s\n",
                 unit_desig(unit), u_type_name(u3));
          set_build_task(unit, u3, 2, 0, 0);
      } else {
          DMprintf("%s already building, leaving alone\n",
                 unit_desig(unit));
      }
    } else {
      DMprintf("%s has no way to support an offensive\n", unit_desig(unit));
    }
}

int
find_alternate_hit_target(Unit *unit, Task *task, int *xp, int *yp)
{
    int range;

    tmpunit = unit;
    tmputype = task->args[2];
    tmpside = side_n(task->args[3]);
    /* (should adjust search radius for speed?) */
    range = u_acp(tmputype) + 1;
    return search_around(task->args[0], task->args[1], range,
                   alternate_target_here, xp, yp, 1);
}

static int
alternate_target_here(int x, int y)
{
    UnitView *uview;

    for_all_view_stack(tmpunit->side, x, y, uview) {
      if (view_type(uview) == tmputype
          && view_side(uview) == tmpside)
        return TRUE;
    }
    return FALSE;
}

/* Defensive units don't go out looking for trouble, but they should
   react strongly to threats. */

static void
plan_defense(Unit *unit)
{
    int u = unit->type, range, x, y, w, h, x1, y1;
    Plan *plan = unit->plan;
    Unit *unit2;

    if (resupply_if_low(unit)) {
      if (plan->tasks)
        execute_task(unit);
      return;
    }
    if (rearm_if_low(unit)) {
      if (plan->tasks)
        execute_task(unit);
      return;
    }
    if (repair_if_damaged(unit)) {
      if (plan->tasks)
        execute_task(unit);
      return;
    }
    if (plan->tasks) {
      /* (should analyze and maybe decide to change task) */
      execute_task(unit);
      return;
    }
    if (plan->maingoal) {
      switch (plan->maingoal->type) {
        case GOAL_UNIT_OCCUPIED:
          /* Move to occupy our goal if necessary. */
          unit2 = find_unit(plan->maingoal->args[0]);
          if (in_play(unit2) && unit->transport != unit2)
            set_occupy_task(unit, unit2);
          break;

        case GOAL_CELL_OCCUPIED:
          /* Move to occupy our goal if necessary. */
          x = plan->maingoal->args[0];  y = plan->maingoal->args[1];
          if (unit->x != x || unit->y != y)
            set_move_to_task(unit, x, y, 0);
          break;

        case GOAL_VICINITY_HELD:
          x = plan->maingoal->args[0];  y = plan->maingoal->args[1];
          w = plan->maingoal->args[2];  h = plan->maingoal->args[3];
          if (distance(x, y, unit->x, unit->y) > max(w, h)) {
            /* Outside the goal area - move in towards it. */
            if (random_point_near(x, y, w / 2, &x1, &y1)) {
                x = x1;  y = y1;
            }
            DMprintf("%s to go on defensive to %d,%d\n",
                   unit_desig(unit), x, y);
            set_move_to_task(unit, x, y, max(w, h) / 2);
            if (unit->transport
                && mobile(unit->transport->type)
                && unit->transport->plan) {
                set_move_to_task(unit->transport, x, y, max(w, h) / 2);
            }
          } else {
            range = max(w, h);
            /* No special goal, look for something to fight with. */
            /* Sometimes be willing to look a little farther out. */
            if (probability(50))
              range *= 2;
            if (do_for_occupants(unit)) {
                /* Occupants have decided for us, fall through. */
            } else if (go_after_victim(unit, range)) {
                /* Found a victim to go after, fall through. */
            } else if (probability(20) && self_build_base_for_self(unit)) {
            } else if (!g_see_all()) {
                DMprintf("%s will explore instead\n", unit_desig(unit));
                plan_exploration(unit); /* or patrol */
                /* Running under exploration rules now. */
                return;
            }
          }
          break;
        default:
          DMprintf("defensive unit has some goal\n");
          break;
      }
      /* (might be able to defend by interposing self?) */
      return;
    }
    /* Generally useful to capture things, so even units on defense
       should take opportunities if they can. */
    if (capture_indep_if_nearby(unit))
      return;
    if (capture_useful_if_nearby(unit))
      return;
    if (can_fire(unit)) {
      /* No special goal, look for something to shoot at. */
      if (fire_at_opportunity(unit)) {
          execute_task(unit);
          return;
      }
      /* Nothing to shoot at, so just hang out. */
      /* (should consider moving to better shooting position if possible) */

    /* Also go for out-of-range victim that we can fire at. */
    } else if (can_attack(unit) || can_fire(unit)) {
      /* Use the tactical range. */ 
      if (go_after_victim(unit, u_ai_tactical_range(unit->type))) {
          execute_task(unit);
          return;
      }
      /* Nobody close by, just hang out, shifting around a bit
           occasionally. */
      if (mobile(unit->type) && probability(10)) {
          if (random_point_near(unit->x, unit->y, u_acp(u), &x1, &y1)) {
            DMprintf("%s to shift defensive position to %d,%d\n",
                   unit_desig(unit), x1, y1);
            set_move_to_task(unit, x1, y1, 0);
            execute_task(unit);
            return;
          }
      }
    } else {
      /* No specific goal, no combat ability - so nothing to do! */
      /* (but should test for ability to detonate or capture first) */
    }
    if (plan->tasks) {
      execute_task(unit);
    } else if (!plan->reserve && g_units_may_go_into_reserve()) {
      /* Just stay in reserve for now. */
      DMprintf("%s going into defensive reserve\n", unit_desig(unit));
      plan->reserve = TRUE;
    } else {
      /* can never get here? */
    }
}

static void
plan_colonize(Unit *unit)
{
    Plan *plan = unit->plan;
    Unit *unit2;
    int x, y, u2;

    if (!mobile(unit->type)) {
      plan_colonize_support(unit);
    }

    if (resupply_if_low(unit)) {
      if (plan->tasks)
        execute_task(unit);
      return;
    }
    if (repair_if_damaged(unit)) {
      if (plan->tasks)
        execute_task(unit);
      return;
    }
    /* Set build task if we are colonizing and have found a good spot. */
    if (plan->maingoal && plan->maingoal->type == GOAL_COLONIZE) {
      u2 = plan->maingoal->args[0];
      if (good_cell_to_colonize(unit->x, unit->y)
          /* Check for nexthere prevents two colonizers from trying to
             colonize the same cell and blocking each other from doing so
             again and again due to terrain capacity restrictions. */
           && unit->nexthere == NULL) {
          set_build_task(unit, u2, 1, 0, 0);    
          DMprintf("%s colonizing by building %s\n", 
                 unit_desig(unit), u_type_name(u2));
      }
    }
    /* Then proceed with the tasks. */
    if (plan->tasks) {
      execute_task(unit);
      return;
    }
    /* The task queue was empty. Get a new task. */
    if (plan->maingoal) {
      switch (plan->maingoal->type) {

        case GOAL_UNIT_OCCUPIED:
          /* Move to occupy our goal if necessary. */
          unit2 = find_unit(plan->maingoal->args[0]);
          if (in_play(unit2) && unit->transport != unit2)
            set_occupy_task(unit, unit2);
          break;

        case GOAL_CELL_OCCUPIED:
          /* Move to our goal if necessary. */
          x = plan->maingoal->args[0];  
          y = plan->maingoal->args[1];
          if (unit->x != x || unit->y != y)
            set_move_to_task(unit, x, y, 0);
          break;

        case GOAL_COLONIZE:
          /* Move in a random dir along a straight line. */
          set_move_dir_task(unit, random_dir(), xrandom(20));
          break;

        default:
          /* Should never happen! */
          DMprintf("Colonizer %s has weird goal %s\n",
                 unit_desig(unit), goal_desig(plan->maingoal));
          break;
      }
    } else {
      /* Should never happen! */
      DMprintf("Colonizer %s has no goal\n", unit_desig(unit));
    }
    /* Execute the new task. */
    if (plan->tasks) {
      execute_task(unit);
      return;
    } else {
      /* Holding occupied unit and waiting for something to happen. */
      DMprintf("Colonizer %s with goal %s unable to find new task\n",
             unit_desig(unit), goal_desig(plan->maingoal));
    } 
}

static void
plan_colonize_support(Unit *unit)
{
    int u, type[MAXUTYPES], numtypes = 0;

    /* Load all colonizing types into type vector. */
    for_all_unit_types(u) {
      if (side_can_build(unit->side, u)
          && could_create(unit->type, u)
          && u_colonizer_worth(u) > 0) {
          type[numtypes] = u;
          numtypes++;
      }
    }
    /* Then pick one of them at random. */
    if (numtypes) {
      u = type[xrandom(numtypes)];
      DMprintf("%s supporting colonization by building %s\n",
             unit_desig(unit), u_type_name(u));
      push_build_task(unit, u, 1, 0, 0);
      return;
    } else {
      /* Load all possible types instead if we failed. */
      for_all_unit_types(u) {
          if (side_can_build(unit->side, u)
            && could_create(unit->type, u)) {
            type[numtypes] = u;
            numtypes++;
          }
      }
    }
    /* Then pick one of them at random. */
    if (numtypes) {
      u = type[xrandom(numtypes)];
      DMprintf("%s can't build colonizers, building %s instead\n",
             unit_desig(unit), u_type_name(u));
      push_build_task(unit, u, 1, 0, 0);
      return;
      /* Nothing can be built. */
    } else {
      DMprintf("Sorry, %s can't build anything!\n", unit_desig(unit));
    }
}

static void
plan_improve(Unit *unit)
{
#if 0
    int u, type[MAXUTYPES], numtypes = 0;
#endif

    /* Execute the task if we have one. */
    if (unit->plan->tasks) {
      execute_task(unit);
      return;
    }
#if 0
    /* Load all immobile types (facilities) into type vector. */
    for_all_unit_types(u) {
      if (side_can_build(unit->side, u)
          && could_create(unit->type, u)
          && u_facility(u)) {
            type[numtypes] = u;
            numtypes++;
      } 
    }
    /* Then pick one of them at random. */
    if (numtypes) {
      u = type[xrandom(numtypes)];
      DMprintf("%s improving itself by building %s\n",
             unit_desig(unit), u_type_name(u));
      push_build_task(unit, u, 1, 0, 0);
      return;
      /* Load all possible types instead if we failed. */
    } else for_all_unit_types(u) {
      if (side_can_build(unit->side, u)
          && could_create(unit->type, u)) {
            type[numtypes] = u;
            numtypes++;
      } 
    }
    /* Then pick one of them at random. */
    if (numtypes) {
      u = type[xrandom(numtypes)];
      DMprintf("%s can't build facility, building %s instead\n",
             unit_desig(unit), u_type_name(u));
      push_build_task(unit, u, 1, 0, 0);
      return;
      /* Nothing can be built. */
    } else
      DMprintf("Sorry, %s can't build anything!\n", unit_desig(unit));
#endif
}

static int 
good_cell_to_colonize(int x, int y)
{
    int x1, y1, u, m, supply[MAXMTYPES], maxsize;
    Unit *unit2;

    /* Then check if another advanced unit is too close to this cell. */
    for_all_units(unit2) {
      if (u_advanced(unit2->type)
          && (distance(x, y, unit2->x, unit2->y)
            < g_ai_advanced_unit_separation()))
        return FALSE;
    }
    /* Then check that the minimal size goal can be attained for all
       possible advanced units (even if we start with a village it may
       eventually grow into a metropolis). */
    for_all_unit_types(u) {
      if (!u_advanced(u))
        continue;
      maxsize = PROPHI;

      /* First zero the supply vector. */
      for_all_material_types (m)
        supply[m] = 0;
      /* Go through all cells within reach and compute supply of
           materials. */
      for_all_cells_within_range(x, y, u_reach(u), x1, y1) {
          if (!inside_area(x1, y1))
            continue;
          /* Don't count supply in cells already used by others. */
          if (user_defined() && user_at(x1, y1) != NOUSER)
            continue;
          /* Add the supply of materials from this cell. */
          for_all_material_types (m)
            supply[m] += production_at(x1, y1, m); 
      }
      /* Calculate max sustainable city size at this location. */
      for_all_material_types (m) {
          if (um_consumption_per_size(u, m) > 0)
            maxsize = min(maxsize, supply[m] / um_consumption_per_size(u, m));
      }
      /* Return FALSE if there is not enough food around to sustain
         the minimal size goal for this unit type. */
      if (maxsize < u_minimal_size_goal(u))
        return FALSE;
    }
    return TRUE;
}

static void
plan_exploration(Unit *unit)
{
    Unit *unit2;
    Plan *plan = unit->plan;
    int u = unit->type;
    int x, y, w, h, range, x1, y1;

    /* If the world has no secrets, exploration is sort of pointless. */
    if (g_see_all() && g_units_may_go_into_reserve()) {
      plan->reserve = TRUE;
      return;
    }
    if (resupply_if_low(unit)) {
      if (plan->tasks)
        execute_task(unit);
      return;
    }
    if (repair_if_damaged(unit)) {
      if (plan->tasks)
        execute_task(unit);
      return;
    }
    if (capture_indep_if_nearby(unit))
      return;
    if (capture_useful_if_nearby(unit))
      return;
    if (plan->tasks) {
      /* (should see if a change of task is worthwhile) */
      execute_task(unit);
      return;
    }
    if (plan->maingoal) {
      switch (plan->maingoal->type) {
        case GOAL_UNIT_OCCUPIED:
          /* Move to occupy our goal if necessary. */
          unit2 = find_unit(plan->maingoal->args[0]);
          if (in_play(unit2) && unit->transport != unit2)
            set_occupy_task(unit, unit2);
          break;

        case GOAL_CELL_OCCUPIED:
          /* Move to occupy our goal if necessary. */
          x = plan->maingoal->args[0];  y = plan->maingoal->args[1];
          if (unit->x != x || unit->y != y)
            set_move_to_task(unit, x, y, 0);
          break;

        case GOAL_VICINITY_KNOWN:
        case GOAL_VICINITY_HELD:
          if (mobile(u)) {
            x = plan->maingoal->args[0];  y = plan->maingoal->args[1];
            w = plan->maingoal->args[2];  h = plan->maingoal->args[3];
            if (distance(x, y, unit->x, unit->y) > max(w, h)) {
                /* Out of the area, move into it. */
                if (random_point_near(x, y, max(w, h) / 2, &x1, &y1)) {
                  x = x1;  y = y1;
                }
                DMprintf("%s to explore towards %d,%d\n",
                       unit_desig(unit), x, y);
                set_move_to_task(unit, x, y, max(w, h) / 2);
            } else {
                if (explore_reachable_cell(unit, max(w, h) + 2)) {
                  /* Found a cell to explore. */
                } else {
                  if (flip_coin()) {
                      DMprintf("%s clearing goal\n", unit_desig(unit));
                      plan->maingoal = NULL;
                  }
                  DMprintf("%s to walk randomly\n", unit_desig(unit));
                  random_walk(unit);
                }
            }
          } else {
            plan_explorer_support(unit);
          }
          break;
        case GOAL_WORLD_KNOWN:
          if (mobile(u)) {
            if (explore_reachable_cell(unit, area.maxdim)) {
                /* Found a cell to explore. */
            } else {
                DMprintf("%s to walk randomly\n", unit_desig(unit));
                random_walk(unit);
            }
          } else {
            plan_explorer_support(unit);
          }
          break;
        default:
          DMprintf("%s goal %s?\n",
                 unit_desig(unit), goal_desig(unit->plan->maingoal));
          break;
      }
    } else {
      /* No specific goal, just poke around. */
      if (mobile(u)) {
          range = area.maxdim / 2;
          if (explore_reachable_cell(unit, range)) {
          } else if (flip_coin()) {
            /* (should call a plan eraser) */
            unit->plan->type = PLAN_PASSIVE;
          } else {
            DMprintf("%s to walk randomly\n", unit_desig(unit));
            random_walk(unit);
          }
      } else {
          plan_explorer_support(unit);
      }
    }
    if (plan->tasks) {
        execute_task(unit);
    }
}

static void
plan_explorer_support(Unit *unit)
{
    int u = unit->type, u2, u3, backup;
    Task *task;

    for_all_unit_types(u2) {
          if (mobile(u2)
            && side_can_build(unit->side, u2)
            && 1 /* better on more kinds of terrain? */
            && uu_acp_to_create(u, u2) > 0) {
            backup = u2;
            if (flip_coin()) {
                u3 = u2;
                break;
            }
          }
    }
    if (u3 == NONUTYPE)
      u3 = backup;
    if (u3 != NONUTYPE) {
      task = unit->plan->tasks;
      if (task == NULL || task->type != TASK_BUILD) {
          DMprintf("%s supporting exploration by building %s\n",
                 unit_desig(unit), u_type_name(u3));
          push_build_task(unit, u3, 2, 0, 0);
      } else {
          DMprintf("%s already building, leaving alone\n",
                 unit_desig(unit));
      }
    } else {
      DMprintf("%s has no way to support exploration\n", unit_desig(unit));
    }
}

/* These are used by AIs as well. */

int victim_x, victim_y, victim_rating, victim_utype, victim_sidenum;

int
victim_here(int x, int y)
{
    int u2 = NONUTYPE, rating, dist;
    Unit *unit3;
    Side *side = tmpunit->side, *oside = NULL;
    UnitView *uview;

    for_all_view_stack(side, x, y, uview) {
      u2 = view_type(uview);
      oside = view_side(uview);
      if (enemy_side(side, oside)
          && ((could_hit(tmpunit->type, u2)
             /* Also consider damage by fire. Moreover, if the
                unit can carry occupants they may be vulnerable
                even though the unit itself is not. */
             && (uu_damage(tmpunit->type, u2) > 0
                 || uu_fire_damage(tmpunit->type, u2) > 0
                 || type_can_carry(u2))         
             && (!worth_capturing(side, u2, oside, x, y)
                 || capture_chance(tmpunit->type, u2, oside) > 0))
            || capture_chance(tmpunit->type, u2, oside) > 0)
          ) {
          /* If our unit can't capture the prospective victim, but
             somebody else is planning to do so, leave it alone and
             keep looking for a victim. */
          if ((uu_acp_to_attack(tmpunit->type, u2) < 1
             || capture_chance(tmpunit->type, u2, oside) <= 0)
            && side_planning_to_capture(side, u2, x, y))
            continue;
          /* If we can capture this unit, get excited and go for it
             right away (perhaps too hasty though? what if a stack
             protector is here also?) */
          if (capture_chance(tmpunit->type, u2, oside) > 0) {
            victim_utype = u2;
            victim_sidenum = side_number(oside);
            return TRUE;
          }
          /* If we're carrying occupants that might be hurt by the
             intended victim, lose interest. */
          if (tmpunit->occupant != NULL
            && could_hit(u2, tmpunit->type)
            && uu_damage(u2, tmpunit->type) > 0
            /* (should) and valuable occupants not protected... */
            )
            continue;
          /* Use the basic hit worth as a relative rating. */
          rating = uu_zz_bhw(tmpunit->type, u2);
          /* Further-away units are less interesting than closer ones. */
          dist = distance(tmpunit->x, tmpunit->y, x, y);
          if (dist > u_acp(tmpunit->type))
            rating /= max(1, isqrt(dist - u_acp(tmpunit->type)));
          /* A large city is more worth capturing. */
          rating *= view_size(uview);
          /* Real enemies are more important targets. */
          if (oside != indepside)
            rating *= 2;
          /* Always attack units that threaten one of our own cities. */
          /* (should not scan if no advanced units in game) */
          for_all_side_units(side, unit3) {
            if (u_advanced(unit3->type)
                && distance(unit3->x, unit3->y, x, y) < 5)
              rating *= 5 - distance(unit3->x, unit3->y, x, y);
          }
          if (rating > victim_rating
            || (rating == victim_rating && flip_coin())) {
            victim_x = x;  victim_y = y;
            victim_rating = rating;
            victim_utype = u2;
            victim_sidenum = oside->id;
          }
      }
    }
    return FALSE;
}

/* This decides whether a given unit type seen at a given location is worth
   trying to capture. */

int
worth_capturing(Side *side, int u2, Side *side2, int x, int y)
{
    int u, bestchance = 0;

    /* See how likely we are to be able to capture the type. */
    for_all_unit_types(u) {
      bestchance = max(capture_chance(u, u2, side2), bestchance);
    }
    if (bestchance == 0)
      return FALSE;
    /* (should account for other considerations too, like which types of units we have) */
    return TRUE;
}

/* Note: go_after_victim can be used for both attacking and firing units since
victim_here only tests for could_hit.  */

/* This routine looks for somebody, anybody to attack. */

int
go_after_victim(Unit *unit, int range)
{
    int x, y, rslt;

    tmpunit = unit;
    DMprintf("%s seeking victim within %d; found ",
           unit_desig(unit), range);
    victim_rating = -9999;
    rslt = search_around(unit->x, unit->y, range, victim_here, &x, &y, 1);
    if (rslt) {
      DMprintf("s%d %s at %d,%d\n", victim_sidenum, u_type_name(victim_utype), x, y);
      /* Set up a task to go after the unit found. */
      /* (should be able to set capture task if better) */
      set_hit_unit_task(unit, x, y, victim_utype, victim_sidenum);
      if (unit->transport != NULL
          && mobile(unit->transport->type)
          && unit->transport->plan) {
          set_move_to_task(unit->transport, x, y, 1);
      }
    } else if (victim_rating > -9999) {
      DMprintf("s%d %s (rated %d) at %d,%d\n",
             victim_sidenum, u_type_name(victim_utype), victim_rating, victim_x, victim_y);
      /* Set up a task to go after the unit found. */
      /* (should be able to set capture task if better) */
      set_hit_unit_task(unit, victim_x, victim_y, victim_utype, victim_sidenum);
      if (unit->transport != NULL
          && mobile(unit->transport->type)
          && unit->transport->plan) {
          set_move_to_task(unit->transport, victim_x, victim_y, 1);
      }
      /* We succeeded after all. */
      rslt = TRUE;
    } else {
      DMprintf("nothing\n");
    }
    return rslt;
}

#if 0       /* Unused. */

/* The point of this new function is to limit the search for captives
   to a given range. */

int
go_after_captive(Unit *unit, int range)
{
    int x, y, rslt;

    tmpunit = unit;

    DMprintf("%s searching for useful capture within %d; found ",
           unit_desig(unit), range);
    rslt = search_around(unit->x, unit->y, range, 
                   useful_captureable_here, &x, &y, 1);
    if (rslt) {
      DMprintf("one at %d,%d\n", x, y);
      /* Set up a task to go after the unit found. */
      set_capture_task(unit, x, y);
      if (unit->transport
          && mobile(unit->transport->type)
          && unit->transport->plan) {
          set_move_to_task(unit->transport, x, y, 1);
      }
      return (execute_task(unit) != TASK_FAILED);
    } else {
      DMprintf("nothing\n");
      return FALSE;
    }
}

#endif

/* Given a location and a unit (in tmpunit), try to identify a target. */
/* (should move to place to share with AIs) */

int target_x, target_y, target_rating, target_utype, target_sidenum;

int
target_here(int x, int y)
{
    int u2 = NONUTYPE, rating, dist;
    Side *side = tmpunit->side, *oside = NULL;
    Unit *unit3;
    UnitView *uview;

    for_all_view_stack(side, x, y, uview) {
      u2 = view_type(uview);
      oside = view_side(uview);
      /* (should move all tests inside loop) */
      if (is_unit_type(u2)
          && enemy_side(side, oside)
          && could_hit(tmpunit->type, u2)
          /* Also consider damage by fire. Moreover, if the unit can
             carry occupants they may be vulnerable even though the
             unit itself is not. */
            && (uu_damage(tmpunit->type, u2) > 0
                || uu_fire_damage(tmpunit->type, u2) > 0
                || type_can_carry(u2))          
          /* and have correct ammo */
          && !side_planning_to_capture(side, u2, x, y)
          ) {
          rating = uu_zz_bfw(tmpunit->type, u2);
          /* Further-away units are less interesting than closer ones. */
          dist = distance(tmpunit->x, tmpunit->y, x, y);
          if (dist > 0)
            rating /= dist;
          /* A larger city is more worth capturing. */
          rating *= view_size(uview);
          /* Real enemies are more important targets. */
          if (oside != NULL)
            rating *= 2;
          /* Always attack units that threaten one of our own cities. */
          for_all_side_units(side, unit3) {
            if (u_advanced(unit3->type)
                && distance(unit3->x, unit3->y, x, y) < 5)
              rating *= 5 - distance(unit3->x, unit3->y, x, y);
          }
          if (rating > target_rating
            || (rating == target_rating && flip_coin())) {
            target_x = x;  target_y = y;
            target_rating = rating;
            target_utype = u2;
            target_sidenum = side_number(oside);
          }
      }
    }
    return FALSE;
}

int
fire_at_opportunity(Unit *unit)
{
    int x, y, range, rslt;

    tmpunit = unit;
    range = u_range(unit->type);
    target_rating = -9999;
    DMprintf("%s seeking target within %d; found ",
             unit_desig(unit), range);
    rslt = search_around(unit->x, unit->y, range, target_here, &x, &y, 1);
    if (rslt) {
      DMprintf("s%d %s at %d,%d\n",
             target_sidenum, u_type_name(target_utype), x, y);
      /* Set up a task to shoot at the unit found. */
      set_hit_unit_task(unit, x, y, target_utype, target_sidenum);
    } else if (target_rating > -9999) {
      DMprintf("s%d %s (rated %d) at %d,%d\n",
             target_sidenum, u_type_name(target_utype), target_rating,
             x, y);
      /* Set up a task to shoot at the unit found. */
      set_hit_unit_task(unit, target_x, target_y, target_utype, target_sidenum);
    } else {
      DMprintf("nothing\n");
    }
    return rslt;
}

/* This is called by individual plans to see if a particular unit type at a
   particular location is scheduled to be captured. */

static int
side_planning_to_capture(Side *side, int u, int x, int y)
{
    Task *task;
    Unit *unit;

    for_all_side_units(side, unit) {
      if (in_play(unit)
          && unit->plan) {
          for_all_tasks(unit->plan, task) {
            if (task->type == TASK_CAPTURE
                && task->args[0] == x && task->args[1] == y
                && (task->args[2] == NONUTYPE || task->args[2] == u))
              return TRUE;
            if (task->type == TASK_HIT_UNIT
                && task->args[0] == x && task->args[1] == y
                && (task->args[2] == NONUTYPE || task->args[2] == u)
                && uu_capture(unit->type, u) > 0)
              return TRUE;
          }
      }
    }
    return FALSE;
}

/* Check to see if our grand plans are at risk of being sideswiped by lack of
   supply, and set up a resupply task if so. */

int
resupply_if_low(Unit *unit)
{
    int u = unit->type, lowm;
    Task *curtask = unit->plan->tasks;

    if (!mobile(u))
      return FALSE;
    lowm = low_on_supplies_one(unit);
    if (lowm != NONMTYPE) {
      /* See if we're already moving to a supply source. */
      if (curtask != NULL
          && curtask->type == TASK_MOVE_TO /* or other types? */
          && supplies_here(unit, curtask->args[0], curtask->args[1], lowm))
        /* Let the movement task execute. */
        return FALSE;
      /* See if we already have a resupply task for the same material. */
      if (curtask != NULL
          && curtask->type == TASK_RESUPPLY
          && curtask->args[0] == lowm)
        return (execute_task(unit) != TASK_FAILED);
      /* Otherwise set up a task. */
      DMprintf("%s low on %s, looking for source\n",
             unit_desig(unit), m_type_name(lowm));
      set_resupply_task(unit, lowm);
      return (execute_task(unit) != TASK_FAILED);
    }
    return FALSE;
}

/* Return a type of essential material that the unit is running out of. */

int
low_on_supplies_one(Unit *unit)
{
    int u = unit->type, m;

    for_all_material_types(m) {
      if ((um_base_consumption(u, m) > 0 || um_consumption_per_move(u, m) > 0)
          && um_storage_x(u, m) > 0
          && unit->supply[m] <= (unit_doctrine(unit)->resupply_percent * um_storage_x(u, m)) / 100
          ) {
          return m;
      }
    }
    return NONMTYPE;
}

int
rearm_if_low(Unit *unit)
{
    int u = unit->type, lowm;
    Task *curtask = unit->plan->tasks;

    if (!mobile(u))
      return FALSE;
    lowm = low_on_ammo_one(unit);
    if (lowm != NONMTYPE) {
      if (curtask != NULL
          && curtask->type == TASK_MOVE_TO /* or other types? */
          && supplies_here(unit, curtask->args[0], curtask->args[1], lowm))
        /* Let the movement task execute. */
        return FALSE;
      /* See if we already have a resupply task for the same material. */
      if (curtask != NULL
          && curtask->type == TASK_RESUPPLY
          && curtask->args[0] == lowm)
        return (execute_task(unit) != TASK_FAILED);
      /* Otherwise set up a task. */
      DMprintf("%s low on %s, looking for source\n",
             unit_desig(unit), m_type_name(lowm));
      set_resupply_task(unit, lowm);
      return (execute_task(unit) != TASK_FAILED);
    }
    return FALSE;
}

/* Return a type of material that we want to use in combat. */

int
low_on_ammo_one(Unit *unit)
{
    int u = unit->type, m;

    for_all_material_types(m) {
      if (um_consumption_per_attack(u, m) > 0
          && um_storage_x(u, m) > 0
          && unit->supply[m] <= (unit_doctrine(unit)->rearm_percent * um_storage_x(u, m)) / 100
          ) {
          return m;
      }
    }
    return NONMTYPE;
}

int
supplies_here(Unit *unit, int x, int y, int m)
{
    Unit *unit2;

    for_all_stack(x, y, unit2) {
      if (unit2 != unit
          && unit_trusts_unit(unit2, unit)
          && unit2->supply[m] > 0
          && um_outlength(unit2->type, m) >= 0) {
          return TRUE;
      }
    }
    return FALSE;
}

int
repair_if_damaged(Unit *unit)
{
    int u = unit->type;
    Task *curtask = unit->plan->tasks;

    if (unit->hp > (u_hp_max(u) * unit_doctrine(unit)->repair_percent) / 100)
      return FALSE;
    if (u_hp_recovery(u) <= 0 && !any_auto_repair)
      return FALSE;
    /* See if we already have a repair task. */
    if (curtask != NULL
      && curtask->type == TASK_REPAIR)
      return (execute_task(unit) != TASK_FAILED);
    if (curtask != NULL
      && curtask->type == TASK_MOVE_TO /* or other types? */
      && repairs_here(unit, curtask->args[0], curtask->args[1]))
      /* Just let the movement task execute. */
      return FALSE;
    /* (should test whether any repairing units in existence) */
    /* Otherwise set up a task. */
    DMprintf("%s badly damaged, looking for repairs\n", unit_desig(unit));
    set_repair_task(unit);
    return (execute_task(unit) != TASK_FAILED);
}

int
repairs_here(Unit *unit, int x, int y)
{
    Unit *unit2;

    for_all_stack(x, y, unit2) {
      if (unit2 != unit
          && unit_trusts_unit(unit2, unit)
          && (uu_auto_repair(unit2->type, unit->type) > 0
            || 0 /* (should allow for explicit repair actions) */)) {
          return TRUE;
      }
    }
    return FALSE;
}

/* Look within a limited distance for any independent unit that could be
   captured, and set up tasks to go get it. */

int
capture_indep_if_nearby(Unit *unit)
{
    /* Use the unit's tactical range instead. */
    int     u = unit->type;
    int     range = u_ai_tactical_range(u);
    int x, y, rslt;
    Task *curtask = unit->plan->tasks;

    if (!mobile(u))
      return FALSE;
    if (!could_capture_any(unit->type))
      return FALSE;
    tmpunit = unit;
    /* See if we're already doing such a task. */
    if (curtask != NULL
      && ((curtask->type == TASK_MOVE_TO
           && indep_captureable_here(curtask->args[0], curtask->args[1]))
          || (curtask->type == TASK_CAPTURE
            && indep_captureable_here(curtask->args[0], curtask->args[1]))))
      return FALSE;
    DMprintf("%s searching for easy capture within %d; found ",
           unit_desig(unit), range);
    rslt = search_around(unit->x, unit->y, range, indep_captureable_here,
                   &x, &y, 1);
    if (rslt) {
      DMprintf("one at %d,%d\n", x, y);
      /* Set up a task to go after the unit found. */
      set_capture_task(unit, x, y, tmputype, tmpside->id);
      if (unit->transport
          && mobile(unit->transport->type)
          && unit->transport->plan) {
          set_move_to_task(unit->transport, x, y, 1);
      }
      return (execute_task(unit) != TASK_FAILED);
    } else {
      DMprintf("nothing\n");
    }
    return FALSE;
}

int
indep_captureable_here(int x, int y)
{
    int u2;
    Side *side = tmpunit->side, *side2;
    UnitView *uview;

    for_all_view_stack(side, x, y, uview) {
      u2 = view_type(uview);
      side2 = view_side(uview);
      if (side2 == indepside
          && side != indepside
          && capture_chance(tmpunit->type, u2, side2) > 10) {
            tmputype = u2;
            tmpside = side2;
            return TRUE;
      }
    }
    return FALSE;
}

/* Look within a limited distance for any type of unit that would be
   good to own, and set up tasks to go get it. */

int
capture_useful_if_nearby(Unit *unit)
{
    /* Use the unit's tactical range instead. */
    int u = unit->type;
    int     range = u_ai_tactical_range(u);
    int x, y, rslt;
    Task *curtask = unit->plan->tasks;

    if (!mobile(u))
      return FALSE;
    if (!could_capture_any(unit->type))
      return FALSE;
    tmpunit = unit;
    /* See if we're already doing such a task. */
    if (curtask != NULL
      && ((curtask->type == TASK_MOVE_TO
           && useful_captureable_here(curtask->args[0], curtask->args[1]))
          || (curtask->type == TASK_CAPTURE
            && useful_captureable_here(curtask->args[0], curtask->args[1]))))
      return FALSE;
    DMprintf("%s searching for useful capture within %d; found ",
           unit_desig(unit), range);
    rslt = search_around(unit->x, unit->y, range, useful_captureable_here,
                   &x, &y, 1);
    if (rslt) {
      DMprintf("one at %d,%d\n", x, y);
      /* Set up a task to go after the unit found. */
      set_capture_task(unit, x, y, tmputype, tmpside->id);
      if (unit->transport
          && mobile(unit->transport->type)
          && unit->transport->plan) {
          set_move_to_task(unit->transport, x, y, 1);
      }
      return (execute_task(unit) != TASK_FAILED);
    } else {
      DMprintf("nothing\n");
    }
    return FALSE;
}

int
useful_captureable_here(int x, int y)
{
    int u2;
    Side *side = tmpunit->side, *side2;
    UnitView *uview;

    for_all_view_stack(side, x, y, uview) {
      u2 = view_type(uview);
      side2 = view_side(uview);
      if (!trusted_side(side, side2)
          && capture_chance(tmpunit->type, u2, side2) > 0
          && useful_type(side, u2)
          ) {
          tmputype = u2;
          tmpside = side2;
          return TRUE;
      }
    }
    return FALSE;
}

/* Return true if the given type of unit is useful in some way to the
   given side.  This is almost always true. */

int
useful_type(Side *side, int u)
{
    if (!type_allowed_on_side(u, side))
      return FALSE;
    return TRUE;
}

int
could_capture_any(int u)
{
    int u2;

    for_all_unit_types(u2) {
      if (uu_capture(u, u2) > 0 || uu_indep_capture(u, u2) > 0)
        return TRUE;
      /* also check if u2 in game, on other side, etc? */
    }
    return FALSE;
}

/* This is a semi-testing routine that basically picks something for
   the unit to do without worrying about its validity.  The rest of
   the system should function correctly no matter what this thing
   comes up with.  Piles of warnings are likely, but that's OK. */

/* (should actually have two modes for this - testing, which allows
   bad data, and normal usage with no garbage generated, which is used
   with unintelligent AIs) */

static void
plan_random(Unit *unit)
{
    int dir, x1, y1, n, randmtype;
    TaskType tasktype;
    Task *task;
    Action action;
    char *argtypestr;

    if (flip_coin()) {
      if (unit->plan->tasks) {
          execute_task(unit);
          return;
      }
      /* Pick a random task. */
      tasktype = xrandom((int) NUMTASKTYPES);
      switch (tasktype) {
        case TASK_NONE:
          task = create_task(tasktype);
          add_task(unit, CLEAR_AGENDA, task);
          break;
        case TASK_BUILD:
          if (flip_coin())
            random_point_near(unit->x, unit->y, 4, &x1, &y1);
          else
            random_point(&x1, &y1);
          task = create_build_task(unit, xrandom(numutypes), xrandom(99),
                             x1, y1);
          add_task(unit, CLEAR_AGENDA, task);
          break;
        case TASK_CAPTURE:
          dir = random_dir();
          point_in_dir(unit->x, unit->y, dir, &x1, &y1);
          set_capture_task(unit, x1, y1, NONUTYPE, ALLSIDES);
          break;
        case TASK_COLLECT:
          if (nummtypes > 0) {
            randmtype = xrandom(nummtypes);
            random_point_near(unit->x, unit->y, 4, &x1, &y1);
            set_collect_task(unit, randmtype, x1, y1);
          } else {
            set_sentry_task(unit, xrandom(5));
          }
          break;
        case TASK_DISBAND:
          set_disband_task(unit);
          break;
        case TASK_HIT_POSITION:
          task = create_task(tasktype);
          if (flip_coin())
            random_point_near(unit->x, unit->y, 4, &x1, &y1);
          else
            random_point(&x1, &y1);
          task->args[0] = x1;  task->args[1] = y1;
          add_task(unit, CLEAR_AGENDA, task);
          break;
        case TASK_HIT_UNIT:
          task = create_task(tasktype);
          add_task(unit, CLEAR_AGENDA, task);
          break;
        case TASK_MOVE_DIR:
          dir = random_dir();
          n = xrandom(10);
          set_move_dir_task(unit, dir, n);
          break;
        case TASK_MOVE_TO:
          dir = random_dir();
          point_in_dir(unit->x, unit->y, dir, &x1, &y1);
          set_move_to_task(unit, x1, y1, 0);
          break;
        case TASK_OCCUPY:
          task = create_task(tasktype);
          add_task(unit, CLEAR_AGENDA, task);
          break;
        case TASK_PICKUP:
          task = create_task(tasktype);
          add_task(unit, CLEAR_AGENDA, task);
          break;
        case TASK_PRODUCE:
          if (nummtypes > 0) {
            randmtype = xrandom(nummtypes);
            task = create_produce_task(unit, randmtype, xrandom(100));
            add_task(unit, CLEAR_AGENDA, task);
          } else {
            set_sentry_task(unit, xrandom(5));
          }
          break;
        case TASK_REPAIR:
          set_repair_task(unit);
          break;
        case TASK_DEVELOP:
          task = create_task(tasktype);
          add_task(unit, CLEAR_AGENDA, task);
          break;
        case TASK_RESUPPLY:
          if (nummtypes > 0) {
            randmtype = xrandom(nummtypes);
            set_resupply_task(unit, randmtype);
          } else {
            set_sentry_task(unit, xrandom(5));
          }
          break;
        case TASK_SENTRY:
          set_sentry_task(unit, xrandom(5));
          break;
        default:
          case_panic("task type", tasktype);
          break;
      }
    }
    if (unit->plan && unit->plan->tasks)
      return;
    /* Otherwise go for a random action. */
    memset(&action, 0, sizeof(Action));
    action.type = (ActionType) xrandom((int) NUMACTIONTYPES);
    argtypestr = actiondefns[(int) action.type].argtypes;
    make_plausible_random_args(argtypestr, 0, &(action.args[0]), unit);
    if (flip_coin()) {
      action.actee = unit->id;
    } else {
      while (find_unit(action.actee = xrandom(numunits)+1) == NULL
             && probability(98));
    }
    unit->act->nextaction = action;
    DMprintf("%s will randomly try %s\n",
           unit_desig(unit), action_desig(&action));
}

/* This attempts to make some vaguely plausible arguments for an
   action, using the types of each arg as a guide.  It also generates
   *invalid* arguments occasionally, which tests error checking in the
   actions' code.  This is mainly useful for testing. */

void
make_plausible_random_args(char *argtypestr, int i, int *args, Unit *unit)
{
    char argch;
    int     slen, arg;

    slen = strlen(argtypestr);
    while (i < slen && i < 10) {
      argch = argtypestr[i];
      switch (argch) {
        case 'n':
          arg = (flip_coin() ? xrandom(10) :
               (flip_coin() ? xrandom(100) :
                (xrandom(20000) - 10000)));
          break;
        case 'u':
          /* Go a little outside range, so as to get some invalid types. */
          arg = xrandom(numutypes + 2) - 1;
          break;
        case 'm':
          arg = xrandom(nummtypes + 2) - 1;
          break;
        case 't':
          arg = xrandom(numttypes + 2) - 1;
          break;
        case 'a':
          arg = xrandom(numatypes + 2) - 1;
          break;
        case 'x':
          arg = (unit != NULL && flip_coin() ? (unit->x + xrandom(5) - 2) :
               (xrandom(area.width + 4) - 2));
          break;
        case 'y':
          arg = (unit != NULL && flip_coin() ? (unit->y + xrandom(5) - 2) :
               (xrandom(area.height + 4) - 2));
          break;
        case 'z':
          arg = (flip_coin() ? 0 : xrandom(10));
          break;
        case 'd':
          arg = random_dir();
          break;
        case 'U':
          /* Cast around for a valid unit. */
          while (find_unit(arg = xrandom(numunits)+1) == NULL
               && probability(98));
          break;
        case 'S':
          arg = xrandom(numsides + 3) - 1;
          break;
        default:
          run_warning("Garbled action arg type '%c'\n", argch);
          arg = 0;
          break;
      }
      args[i++] = arg;
    }
}

/* Random walking just attempts to move around. */

void
random_walk(Unit *unit)
{
    int dir = random_dir(), x1, y1, tries = 0;

    while (!interior_point_in_dir(unit->x, unit->y, dir, &x1, &y1)) {
      if (++tries > 500) {
          run_warning("something is wrong");
          break;
      }
      dir = random_dir();
    }
    set_move_to_task(unit, x1, y1, 0);
}

/* Record the unit as waiting for orders about what to do. */

void
set_waiting_for_tasks(Unit *unit, int flag)
{
    unit->plan->waitingfortasks = flag;
    if (unit->side != NULL)
      unit->side->numwaiting += (unit->plan->waitingfortasks ? 1 : -1);
}

/* General routine to wake a unit up (and maybe all its cargo). */

void
wake_unit(Side *side, Unit *unit, int wakeocc)
{
    Unit *occ;

    /* (should test that side is permitted to wake) */
    if (unit->plan) {
      unit->plan->asleep = FALSE;
      unit->plan->reserve = FALSE;
      update_unit_display(side, unit, TRUE);
    }
    if (wakeocc) {
      for_all_occupants(unit, occ)
        wake_unit(side, occ, wakeocc);
    }
}

/* The area wakeup. */

static int tmpflag;

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

    for_all_stack(x, y, unit) {
      if (side_controls_unit(tmpside, unit)) {
          wake_unit(tmpside, unit, tmpflag);
      }
    }
}

void
wake_area(Side *side, int x, int y, int n, int occs)
{
    tmpside = side;
    tmpflag = occs;
    apply_to_area(x, y, n, wake_at);
}

void
set_formation(Unit *unit, Unit *leader, int x, int y, int dist, int flex)
{
    Plan *plan = unit->plan;
    Goal *goal;

    if (plan == NULL)
      return;
    if (!in_play(unit))
      return;
    if (leader != NULL) {
      if (!in_play(leader))
        return;
      goal = create_goal(GOAL_KEEP_FORMATION, unit->side, TRUE);
      goal->args[0] = leader->id;
      goal->args[1] = x;  goal->args[2] = y;
      goal->args[3] = dist;
      goal->args[4] = flex;
      plan->formation = goal;
      plan->funit = leader;
    } else {
      /* A NULL leader means to clear the formation goal. */
      plan->formation = NULL;
      plan->funit = NULL;
    }
}

void
delay_unit(Unit *unit, int flag)
{
    if (in_play(unit) && unit->plan) {
      unit->plan->delayed = TRUE;
    }
}

#if 0       /* The four functions below are not used anywhere. */

/* Return the distance that we can go by shortest path before running out
   of important supplies.  Will return at least 1, since we can *always*
   move one cell to safety.  This is a worst-case routine, too complicated
   to worry about units getting refreshed by terrain or whatever. */

int
range_left(Unit *unit)
{
    int u = unit->type, m, least = 12345; /* bigger than any real value */
    
    for_all_material_types(m) {
      if (um_consumption_per_move(u, m) > 0) {
          least = min(least, unit->supply[m] / um_consumption_per_move(u, m));
      }
#if 0
      /* This code is too pessimistic if no account taken of supply line or
         production, so leave out for now. */
      if (um_base_consumption(u, m) > 0) {
          tmp = (u_speed(u) * unit->supply[m]) / um_base_consumption(u, m);
          least = min(least, tmp);
      }
#endif
    }
    return (least == 12345 ? 1 : least);
}

/* Estimate the goodness and badness of cells in the immediate vicinity. */

int
find_worths(range)
int range;
{
    return 0;
}

/* This is a heuristic estimation of the value of one unit type
   hitting on another.  Should take cost of production into account as
   well as the chance and significance of any effect. */

int
attack_worth(unit, e)
Unit *unit;
int e;
{
    int u = unit->type, worth;

    worth = uu_zz_bhw(u, e);
    /* Risk of death? */
/*    if (uu_damage(e, u) >= unit->hp)
      worth /= (could_capture(u, e) ? 1 : 4);
    if (could_capture(u, e)) worth *= 4; */
    return worth;
}

/* Support functions. */

/* Return true if the given position is threatened by the given unit type. */

int
threat(Side *side, int u, int x0, int y0)
{
#if 0
    int d, x, y, thr = 0;
    Side *side2;
    int view;

    for_all_directions(d) {
      point_in_dir(x0, y0, d, &x, &y);
      view = 0 /* side_view(side, x, y) */;
      if (view != UNSEEN && view != EMPTY) {
          side2 = side_n(vside(view));
          if (allied_side(side, side2)) {
            if (uu_capture(u, vtype(view)) > 0) thr += 1000;
            if (uu_zz_bhw(u, vtype(view)) > 0) thr += 100;
          }
      }
    }
    return thr;
#endif
    return 0;
}

#endif

void
pop_task(Plan *plan)
{
    Task *oldtask;

    if (plan->tasks) {
      oldtask = plan->tasks;
      plan->tasks = plan->tasks->next;
      free_task(oldtask);
    }
}

#if 0       /* The two functions below are unused. */

/* Patrol just does move_to, but cycling waypoints around when the first */
/* one has been reached. */

int
move_patrol(Unit *unit)
{
#if 0
    int tx, ty;

    if (unit->plan->orders.rept-- > 0) {
      if (unit->x == unit->plan->orders.p.pt[0].x &&
          unit->y == unit->plan->orders.p.pt[0].y) {
          tx = unit->plan->orders.p.pt[0].x;
          ty = unit->plan->orders.p.pt[0].y;
          unit->plan->orders.p.pt[0].x = unit->plan->orders.p.pt[1].x;
          unit->plan->orders.p.pt[0].y = unit->plan->orders.p.pt[1].y;
          unit->plan->orders.p.pt[1].x = tx;
          unit->plan->orders.p.pt[1].y = ty;
      }
      return move_to(unit, unit->plan->orders.p.pt[0].x, unit->plan->orders.p.pt[0].y,
                   (unit->plan->orders.flags & SHORTESTPATH));
    }
#endif
    return TRUE;
}

/* Basic routine to compute how long a unit will take to build something. */

int
build_time(Unit *unit, int prod)
{
    int schedule = 1 /* uu_make(unit->type, prod) */;
    int u, develop_delay = 0;

    /* Add penalty (or unpenalty!) for first unit of a type. */
    /* is "counts" a reliable way to test? */
    if (unit->side->counts[prod] <= 1) {
/*    develop_delay = ((schedule * u_develop(prod)) / 100);  */
      for_all_unit_types(u) {
          if (unit->side->counts[u] > 1) {
            develop_delay -=
              (1 /*uu_make(unit->type, u)*/ * 
               uu_tech_crossover(prod, u)) / 100;
          }
          if (develop_delay > 0) {
            schedule += develop_delay;
          }
      }
    }
    return schedule;
}

#endif

int
clear_task_agenda(Plan *plan)
{
    int numcleared;
    Task *oldtask;

    if (plan == NULL || plan->tasks == NULL)
      return 0;
    numcleared = 0;
    while (plan->tasks != NULL) {
      oldtask = plan->tasks;
      plan->tasks = plan->tasks->next;
      free_task(oldtask);
      ++numcleared;
    }
    return numcleared;
}

Plan *
create_plan(void)
{
    Plan *plan = (Plan *) xmalloc(sizeof(Plan));
    return plan;
}

void 
free_plan(Plan *plan)
{
    if (plan == NULL)
      run_error("no plan here?");
    /* Make tasks available for reallocation. */
    clear_task_agenda(plan);
    free(plan);
}

/* Describe a plan succinctly.  This is primarily for debugging, not
   for normal user display. */

char *planbuf = NULL;

char *
plan_desig(Plan *plan)
{
    Task *task;
    int extra = 0;

    if (planbuf == NULL)
      planbuf = xmalloc(1000);
    if (plan == NULL) {
      sprintf(planbuf, "no plan");
    } else if (plan->type == PLAN_NONE) {
      sprintf(planbuf, "unformed plan");
    } else {
      if (plan->tasks) {
          tmpbuf[0] = '\0';
          for_all_tasks(plan, task) {
            if (strlen(tmpbuf) < 100) {
                strcat(tmpbuf, " ");
                strcat(tmpbuf, task_desig(task));
            } else {
                ++extra;
            }
          }
          if (extra > 0) {
            tprintf(tmpbuf, " ... %d more ...", extra);
          }
      } else {
          sprintf(tmpbuf, " no tasks");
      }
      sprintf(planbuf, "type %s %s",
            plantypenames[plan->type], goal_desig(plan->maingoal));
      if (plan->formation) {
          strcat(planbuf, " ");
          strcat(planbuf, goal_desig(plan->formation));
      }
      if (plan->asleep)
        strcat(planbuf, " asleep");
      if (plan->reserve)
        strcat(planbuf, " reserve");
      if (plan->delayed)
        strcat(planbuf, " delayed");
      if (plan->waitingfortasks)
        strcat(planbuf, " waiting");
      if (plan->supply_alarm)
        strcat(planbuf, " supply_alarm");
      if (plan->supply_is_low)
        strcat(planbuf, " supply_is_low");
      strcat(planbuf, tmpbuf);
    }
    return planbuf;
}

/* True if unit is in immediate danger of being captured. */
/* Needs check on capturer transport being seen. */

int
might_be_captured(Unit *unit)
{
    int d, x, y;
    Unit *unit2;

    for_all_directions(d) {
      if (interior_point_in_dir(unit->x, unit->y, d, &x, &y)) {
      if (((unit2 = unit_at(x, y)) != NULL) &&
          (enemy_side(unit->side, unit2->side)) &&
          (uu_capture(unit2->type, unit->type) > 0))
            return TRUE;
      }
    }
    return FALSE;
}

/* Clear a unit's plan out. */

void
force_replan(Side *side, Unit *unit, int passive_only)
{
    extern int need_ai_planning;

    if (unit->plan == NULL)
      return;
    if ((passive_only ? unit->plan->type == PLAN_PASSIVE : TRUE)) {
      unit->plan->type = PLAN_PASSIVE;
      clear_task_agenda(unit->plan);
    }
    unit->plan->maingoal = NULL;
    unit->plan->formation = NULL;
    unit->plan->funit = NULL;
    unit->plan->asleep = FALSE;
    unit->plan->reserve = FALSE;
    set_waiting_for_tasks(unit, FALSE);
    unit->plan->delayed = FALSE;
    unit->plan->last_task_outcome = TASK_UNKNOWN;
    need_ai_planning = TRUE;
}

/* Auxiliary functions for unit planning in Xconq. */

/* router flags */

#define SAMEPATH 1
#define EXPLORE_PATH 2

/* These macros are a cache used for planning purposes by machines. */

#define markloc(x, y) (set_tmp1_at(x, y, mark))

#define markedloc(x, y) (tmp1_at(x, y) == mark)

#define get_fromdir(x, y) (tmp2_at(x, y))

#define set_fromdir(x, y, dir) (set_tmp2_at(x, y, dir))

#define get_dist(x, y) (tmp3_at(x, y))

#define set_dist(x, y, d) (set_tmp3_at(x, y, d))

int
occupant_could_capture(Unit *unit, int u2)
{
    Unit *occ;

    for_all_occupants(unit, occ)
      if (uu_capture(occ->type, u2) > 0)
      return TRUE;
    return FALSE;
}

/* Check to see if there is anyone around to capture. */

int
can_capture_neighbor(Unit *unit)
{
    int d, x, y;
    Side *side2;
    UnitView *uview;

    for_all_directions(d) {
      if (interior_point_in_dir(unit->x, unit->y, d, &x, &y)) {
          for_all_view_stack(unit->side, x, y, uview) {
            side2 = view_side(uview);
            if (!allied_side(unit->side, side2)) {
                if (uu_capture(unit->type, view_type(uview)) > 0) {
                  /* need some other way to change move order quickly */
                  return TRUE;
                }
            }
          }
      }
    }
    return FALSE;
}

/* check if our first occupant can capture something.  Doesn't look at
   other occupants. */

int
occupant_can_capture_neighbor(Unit *unit)
{
    Unit *occ = unit->occupant;

    if (occ != NULL && has_acp_left(occ) && occ->side == unit->side) {
      if (can_capture_neighbor(occ)) {
          return TRUE;
      }
    }
    return FALSE;
}

/* Find the closes unit, first prefering bases, and then transports. */

int
find_closest_unit(side, x0, y0, maxdist, pred, rxp, ryp)
Side *side;
int x0, y0, maxdist, (*pred)(int x, int y), *rxp, *ryp;
{
#if 0    
    Unit *unit;
    int u, dist;
    int found = FALSE;

    for_all_unit_types(u) {
      if (u_is_base(u)) {
          for (unit = NULL /* side_strategy(side)->unitlist[u]*/; unit != NULL; unit = unit->mlist) {
            if (alive(unit) &&
                (dist = distance(x0, y0, unit->x, unit->y)) <= maxdist) {
                if ((*pred)(unit->x, unit->y)) {
                  maxdist = dist - 1;
                  *rxp = unit->x;  *ryp = unit->y;
                  found = TRUE;
                }
            }
          }
      }
    }
    if (found) {
      return TRUE;
    }
    for_all_unit_types(u) {
      if (!u_is_base(u) && u_is_transport(u)) {
          for (unit = NULL /*side_strategy(side)->unitlist[u]*/; unit != NULL; unit = unit->mlist) {
            if (alive(unit)
                && distance(x0, y0, unit->x, unit->y) <= maxdist) {
                if ((*pred)(unit->x, unit->y)) {
                  maxdist = dist - 1;
                  *rxp = unit->x;  *ryp = unit->y;
                  found = TRUE;
                }
            }
          }
      }
    }
    if (found) {
      return TRUE;
    }
    /* (What's the point of finding a non-base/non-transport?) */
    for_all_unit_types(u) {
      if (!u_is_base(u) && !u_is_transport(u)) {
          for (unit = NULL/*side_strategy(side)->unitlist[u]*/; unit != NULL; unit = unit->mlist) {
            if (alive(unit)
                && distance(x0, y0, unit->x, unit->y) <= maxdist) {
                if ((*pred)(unit->x, unit->y)) {
                  maxdist = dist - 1;
                  *rxp = unit->x;  *ryp = unit->y;
                  found = TRUE;
                }
            }
          }
      }
    }
    if (found) {
      return TRUE;
    }
#endif
    return FALSE;
}

#if 0       /* Unused. */

/* Returns the type of missing supplies. */

int
out_of_ammo(Unit *unit)
{
    int u = unit->type, m;

    for_all_material_types(m) {
      if (um_consumption_per_attack(u, m) > 0 && unit->supply[m] <= 0)
          return m;
    }
    return (-1);
}

#endif

#if 0       /* The two functions below are unused. */
int
usable_cell(Unit *unit, int x, int y)
{
    int u = unit->type;
    UnitView *uview;

    if (!could_live_on(u, terrain_at(x, y)))
      return FALSE;
    for_all_view_stack(unit->side, x, y, uview) {
      if (allied_side(view_side(uview), unit->side)
          && could_carry(view_type(uview), u))
        return TRUE;
    }
    return FALSE;
}

Task *explorechain;

int
explorable_cell(x, y)
int x, y;
{
    return (terrain_view(tmpside, x, y) == UNSEEN);
}

#endif

/* Test whether the given location is an unknown cell that we can get
   next to. */
/* (should consider testing "within vision range", but that might
   require LOS tests and be expensive) */

static int
reachable_unknown(int x, int y)
{
    /* Only interior cells are reachable. */
    if (!inside_area(x, y))
      return FALSE;
    if (terrain_view(tmpside, x, y) == UNSEEN) {
      if (adj_known_ok_terrain(x, y, tmpside, tmpunit->type)) {
          return TRUE;
      } else {
          return FALSE;
      }
    } else {
      return FALSE;
    }
}

/* Test whether the given location has an adjacent cell that is ok for
   the given type to be out in the open. */

static int
adj_known_ok_terrain(int x, int y, Side *side, int u)
{
    int dir, x1, y1, t;

    if (!inside_area(x, y))
      return FALSE;
    for_all_directions(dir) {
      if (interior_point_in_dir(x, y, dir, &x1, &y1)) {
          if (terrain_view(side, x1, y1) == UNSEEN)
            continue;
          t = terrain_at(x1, y1);
          if (!terrain_always_impassable(u, t))
            return TRUE;
      }
    }
    return FALSE;
}

/* Go to the nearest cell that we can see how to get to. */

static int
explore_reachable_cell(Unit *unit, int range)
{
    int x, y;

    if (g_see_all() || g_terrain_seen())
      return FALSE;
    tmpunit = unit;
    tmpside = unit->side;
    DMprintf("%s searching within %d for cell to explore -",
           unit_desig(unit), range);
    if (search_around(unit->x, unit->y, range, reachable_unknown, &x, &y, 1)) {
      set_move_to_task(unit, x, y, 1);
      DMprintf("found one at %d,%d\n", x, y);
      return TRUE;
    }
    DMprintf("found nothing\n");
    return FALSE;
}

#if 0       /* The two functions below are unused. */

/* Check for any makers this unit should be capturing. */

int
should_capture_maker(unit)
Unit *unit;
{
    return 0;
}

/* Returns true if the given unit can't leave its cell for some reason. */

int
no_possible_moves(unit)
Unit *unit;
{
    int fx = unit->x, fy = unit->y, ut = unit->type;
    int d, x, y;
    int view;
    Side *side = unit->side;

    for_all_directions(d) {
      x = wrapx(fx + dirx[d]);  y = limity(fy + diry[d]);
      view = unit_view(side, x, y);
      if (view == EMPTY) {
          if (could_move(ut, terrain_at(x, y)))
            return FALSE;
      } else if (enemy_side(side_n(vside(view)) , side)
               && could_hit(ut, vtype(view))) {
          return FALSE;
      } else if (could_carry(vtype(view), ut) &&
               allied_side(side_n(vside(view)), side))
        return FALSE;
    }
    return TRUE;
}

#endif

/* Estimate the usual number of turns to finish construction. */

int
normal_completion_time(u, u2)
int u, u2;
{
    if (u_acp(u) == 0 || uu_cp_per_build(u, u2) == 0)
      return (-1);
    return (u_cp(u2) - uu_creation_cp(u, u2)) /
      (uu_cp_per_build(u, u2) * u_acp(u));
}

/* Similar, but using a specific unit and also accounting for toolup
   time. */

int
est_completion_time(Unit *unit, int u2)
{
    int u, tooluptime, tp;

    u = unit->type;
    if (uu_acp_to_create(u, u2) < 1)
      return (-1);
    tooluptime = 0;
    tp = (unit->tooling ? unit->tooling[u2] : 0);
    if (tp < uu_tp_to_build(u, u2)) {
      if (uu_acp_to_toolup(u, u2) < 1
          || uu_tp_per_toolup(u, u2) <= 0
          || u_acp(u) <= 0)
        return (-1);
      tooluptime = ((uu_tp_to_build(u, u2) - tp) * uu_acp_to_toolup(u, u2))
       / (uu_tp_per_toolup(u, u2) * u_acp(u));
    }
    return tooluptime + normal_completion_time(unit->type, u2);
}

/* A unit runs low on supplies at the halfway point.  Formula is the same
   no matter how/if occupants eat transports' supplies. */
/* (should reconcile with doctrine's resupply-percent) */

int
past_halfway_point(Unit *unit)
{
    int u = unit->type, m;

    for_all_material_types(m) {
      if (((um_base_consumption(u, m) > 0)
           || (um_consumption_per_move(u, m) > 0))
          /* should check that the transport is adequate for
             supplying the fuel */
          && (unit->transport == NULL)) {
          if (2 * unit->supply[m] <= um_storage_x(u, m))
            return TRUE;
      }
    }
    return FALSE;
}

Generated by  Doxygen 1.6.0   Back to index