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

actions.c

/* Implementations of Xconq actions.
   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.  */

/* The general theory of actions is that interface or AI code calls, for
   an action foo, the routine prep_foo_action, which just records the action
   for later execution.  The action loop in run_game eventually calls
   do_foo_action, which first calls check_foo_action to confirm that the
   action will succeed.  If check_foo_action does not find any errors, then
   the action cannot fail.  The main body of do_foo_action then implements
   the effects of the action.  Interfaces may call check_foo_action freely,
   but should never call do_foo_action directly. */

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

static int check_create_common(Unit *unit, Unit *unit2, int u3,
                         int x, int y);
static void set_created_unit_props(Unit *unit, int u2, Side *side,
                           int u2mor);
static int give_away(Unit *unit, int m, int amt);
static void notify_action(Unit *unit, Action *action);
static void play_action_movies(Unit *unit, Action *action);
static void adjust_tech_crossover(Side *side, int u);
static void adjust_tooling_crossover(Unit *unit, int u2);
static void distribute_material(Unit *unit, int m, int amt);

static void notify_tech(Side *side, int u, int oldtech, int newtech);
static void notify_tp(Side *side, Unit *unit, int u2, int oldtp, int newtp);


/* 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"

/* The table of all the types of actions. */

ActionDefn actiondefns[] = {

#undef  DEF_ACTION
#define DEF_ACTION(NAME,CODE,ARGS,prepfn,netprepfn,dofn,checkfn,argdecl,doc)  \
    { CODE, NAME, ARGS },

#include "action.def"

    { -1, NULL, NULL }
};

/* This is used to indicate that a move is a retreat; for normal movement it will
   always be false. */

int retreating;

/* This is a specific type of unit that the retreater is running away from. */

int retreating_from = NONUTYPE;

char *actiondesigbuf = NULL;

/* Do any action-related initialization. */

void
init_actions(void)
{
}

/* Just a placeholder action, so not much to do here. */

int
prep_none_action(Unit *unit, Unit *unit2)
{
    if (unit == NULL || unit->act == NULL || unit2 == NULL)
      return FALSE;
    unit->act->nextaction.type = ACTION_NONE;
    unit->act->nextaction.actee = unit2->id;
    return TRUE;
}

int
do_none_action(Unit *unit, Unit *unit2)
{
    return A_ANY_DONE;
}

int
check_none_action(Unit *unit, Unit *unit2)
{
    return A_ANY_DONE;
}

/* Material actions. */

/* Explicit material production. */

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

int
do_produce_action(unit, unit2, m, n)
Unit *unit, *unit2;
int m, n;
{
    int amt, excess;

    amt = min(n, um_material_per_production(unit2->type, m));
    unit2->supply[m] += n;
    /* Clip to storage space and pass around any extra. */
    excess = unit2->supply[m] - um_storage_x(unit2->type, m);
    if (excess > 0) {
      unit2->supply[m] -= excess;
      distribute_material(unit2, m, excess);
    }
    use_up_acp(unit, um_acp_to_produce(unit2->type, m));
    return A_ANY_DONE;
}

int
check_produce_action(unit, unit2, m, n)
Unit *unit, *unit2;
int m, n;
{
    int acp, m2, u2;

    if (!in_play(unit))
      return A_ANY_ERROR;
    if (!in_play(unit2))
      return A_ANY_ERROR;
    if (!is_material_type(m))
      return A_ANY_ERROR;
    u2 = unit2->type;
    acp = um_acp_to_produce(u2, m);
    if (acp < 1)
      return A_ANY_CANNOT_DO;
    if (!can_have_enough_acp(unit, acp))
      return A_ANY_CANNOT_DO;
    if (um_material_per_production(u2, m) < 1)
      return A_ANY_CANNOT_DO;
    if (!has_enough_acp(unit, acp))
      return A_ANY_NO_ACP;
    /* Check that the unit has any required supplies. */
    for_all_material_types(m2) {
      if (unit2->supply[m2] < um_to_produce(u2, m2))
        return A_ANY_NO_MATERIAL;
    }
    return A_ANY_OK;
}

/* Extraction of material from terrain. */

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

int
do_extract_action(unit, unit2, x, y, m, n)
Unit *unit, *unit2;
int x, y, m, n;
{
    int oldamt, amt, newamt, excess, t, newt;
    Unit *unit3;

    if (any_cell_materials_defined()
      && cell_material_defined(m)
      && material_at(x, y, m) >= n) {
      oldamt = material_at(x, y, m);
      amt = min(n, oldamt);
      unit2->supply[m] += amt;
      newamt = oldamt - amt;
      set_material_at(x, y, m, newamt);
      /* (should do with a common routine) */
      t = terrain_at(x, y);
      if (newamt == 0
          && probability(tm_change_on_exhaust(t, m))
          && tm_exhaust_type(t, m) != NONTTYPE) {
          newt = tm_exhaust_type(t, m);
          /* Change the terrain's type. */
          change_terrain_type(x, y, newt);
      }
    } else {
      for_all_stack(x, y, unit3) {
          if (in_play(unit3) && indep(unit3) && unit3->supply[m] >= n) {
            oldamt = unit3->supply[m];
            amt = min(n, oldamt);
            unit2->supply[m] += amt;
            newamt = oldamt - amt;
            unit3->supply[m] = newamt;
            break;
          }
      }
    }
    /* Clip to storage space and pass around any extra. */
    excess = unit2->supply[m] - um_storage_x(unit2->type, m);
    if (excess > 0) {
      unit2->supply[m] -= excess;
      distribute_material(unit2, m, excess);
    }
    use_up_acp(unit, um_acp_to_extract(unit2->type, m));
    return A_ANY_DONE;
}

int
check_extract_action(unit, unit2, x, y, m, n)
Unit *unit, *unit2;
int x, y, m, n;
{
    int acp, m2, u2;
    Unit *unit3;

    if (!in_play(unit))
      return A_ANY_ERROR;
    if (!in_play(unit2))
      return A_ANY_ERROR;
    if (!is_material_type(m))
      return A_ANY_ERROR;
    u2 = unit2->type;
    acp = um_acp_to_extract(u2, m);
    if (acp < 1)
      return A_ANY_CANNOT_DO;
    if (!can_have_enough_acp(unit, acp))
      return A_ANY_CANNOT_DO;
    if (!has_enough_acp(unit, acp))
      return A_ANY_NO_ACP;
    /* Check that the unit has any required supplies. */
    for_all_material_types(m2) {
      if (unit2->supply[m2] < um_to_extract(u2, m2))
        return A_ANY_NO_MATERIAL;
    }
    /* Look for case of extraction from terrain. */
    if (any_cell_materials_defined()
      && cell_material_defined(m)
      && material_at(x, y, m) >= n)
      return A_ANY_OK;
    /* Then look for extraction from independent unit. */
    for_all_stack(x, y, unit3) {
      if (in_play(unit3)
          && indep(unit3)
          && unit3->supply[m] >= n) {
          return A_ANY_OK;
      }
    }
    return A_ANY_ERROR; /* (should be "nothing to extract from") */
}

/* Transfer action. */

/* This action transfers material from one unit to another. */

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

int
do_transfer_action(Unit *unit, Unit *unit2, int m, int n, Unit *unit3)
{
    int actual;

    if (n > 0) {
      actual = transfer_supply(unit2, unit3, m, n);
    } else {
      actual = transfer_supply(unit3, unit2, m, -n);
    }
    use_up_acp(unit, 1);
    if (actual == n) {
      return A_ANY_DONE;
    } else if (actual == 0) {
      return A_ANY_ERROR;
    } else {
      /* (should be able to say that action did not do all that was requested) */
      return A_ANY_DONE;
    }
}

int
check_transfer_action(Unit *unit, Unit *unit2, int m, int n, Unit *unit3)
{
    if (!in_play(unit))
      return A_ANY_ERROR;
    if (!in_play(unit2))
      return A_ANY_ERROR;
    if (!is_material_type(m))
      return A_ANY_ERROR;
    if (n == 0)
      return A_ANY_ERROR;
    if (!in_play(unit3))
      return A_ANY_ERROR;
    if (um_acp_to_unload(unit2->type, m) < 1)
      return A_ANY_CANNOT_DO;
    if (unit3->act && um_acp_to_load(unit3->type, m) < 1)
      return A_ANY_CANNOT_DO;
    if (!can_have_enough_acp(unit, 1))
      return A_ANY_CANNOT_DO;
    if (n > 0) {
      if (unit2->supply[m] <= 0)
        return A_ANY_ERROR;
      if (um_storage_x(unit3->type, m) == 0)
        return A_ANY_ERROR;
    } else {
      if (unit3->supply[m] <= 0)
        return A_ANY_ERROR;
      if (um_storage_x(unit2->type, m) == 0)
        return A_ANY_ERROR;
    }
    if (!has_enough_acp(unit, 1))
      return A_ANY_NO_ACP;
    return A_ANY_OK;
}

/* Move supply from one unit to another.  Don't move more than is
   possible; check both from and to amounts and capacities.  This
   routine will also transfer to the side's treasury if necessary to
   handle overflow. */

int
transfer_supply(Unit *from, Unit *to, int m, int amount)
{
    int amt2, origfrom = from->supply[m], origto = to->supply[m];

    /* Constrain the amount of supply that may be unloaded. */
    amount = min(amount, origfrom);
    if (um_unload_max(from->type, m) >= 0) {
      amount = min(amount, um_unload_max(from->type, m));
    }
    if (um_load_max(to->type, m) >= 0) {
      amount = min(amount, um_load_max(to->type, m));
    }
    amt2 = min(amount, um_storage_x(to->type, m) - origto);

    /* Transfer overflow to side's treasury if it exists. */
    if (side_has_treasury(to->side, m)
        && um_gives_to_treasury(to->type, m)) {
      to->side->treasury[m] += (amount - amt2);
    } else {
      amount = amt2;
    }
    from->supply[m] -= amount;
    to->supply[m] += amt2;
    /* Make sure any displays of supply levels see the transfer. */
    update_unit_display(from->side, from, TRUE);
    update_unit_display(to->side, to, TRUE);
    if (amount != amt2) {
      update_side_display(from->side, from->side, TRUE);
      update_side_display(to->side, to->side, TRUE);
    }
    Dprintf("%s (had %d) transfers %d %s to %s (had %d) (%d to side)\n",
          unit_desig(from), origfrom, amount, m_type_name(m),
          unit_desig(to), origto, (amount - amt2));
    return amount;
}

/* Develop action. */

/* If a side's tech level is under its max, develop can increase it. */

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

int
do_develop_action(Unit *unit, Unit *unit2, int u3)
{
    int u2 = unit2->type, oldtech, lim;
    Side *side = unit2->side;

    oldtech = side->tech[u3];
    side->tech[u3] += prob_fraction(uu_tech_per_develop(u2, u3));
    /* Silently apply the per-side-per-turn limit on tech gains. */
    lim =  side->inittech[u3] + u_tech_per_turn_max(u3);
    if (side->tech[u3] > lim)
      side->tech[u3] = lim;
    if (side->tech[u3] != oldtech) {
      /* (should do generic side display update?) */
      notify_tech(side, u3, oldtech, side->tech[u3]);
      /* Update info for side_can_build. */
      update_canbuild_vector(side);
    }
    /* Adjust the tech levels of any related unit types. */
    adjust_tech_crossover(side, u3);
    use_up_acp(unit, uu_acp_to_develop(u2, u3));
    return A_ANY_DONE;
}

int
check_develop_action(Unit *unit, Unit *unit2, int u3)
{
    int u, u2, m;
    Side *side;

    if (!in_play(unit))
      return A_ANY_ERROR;
    if (!in_play(unit2))
      return A_ANY_ERROR;
    if (!is_unit_type(u3))
      return A_ANY_ERROR;
    u = unit->type;  u2 = unit2->type;
    side = unit->side;
    /* Independent units might not do develop. */
    if (side == indepside && g_indepside_can_develop() == FALSE)
      return A_ANY_ERROR;
    /* This unit must be of a type that can develop the given type. */
    if (uu_acp_to_develop(u2, u3) < 1)
      return A_ANY_CANNOT_DO;
    if (!can_have_enough_acp(unit, uu_acp_to_develop(u2, u3)))
      return A_ANY_CANNOT_DO;
    /* Max tech level means there's nothing more to learn. */
    if (side->tech[u3] >= u_tech_max(u3))
      return A_ANY_ERROR;
    if (!has_enough_acp(unit, uu_acp_to_develop(u2, u3)))
      return A_ANY_NO_ACP;
    /* Check that the unit has any required supplies. */
    for_all_material_types(m) {
      if (unit2->supply[m] < um_to_develop(u2, m))
        return A_ANY_NO_MATERIAL;
    }
    return A_ANY_OK;
}

/* For all unit types, bring their tech level up to match the crossovers
   from the given unit type. */

void
adjust_tech_crossover(Side *side, int u)
{
    int u2, oldtech, crosstech;

    for_all_unit_types(u2) {
      if (u2 != u) {
          oldtech = side->tech[u2];
          /* Compute the crossover as a ratio of max tech levels for
               each type. */
          crosstech =
            ((side->tech[u] * uu_tech_crossover(u, u2) * u_tech_max(u2)) /
             (u_tech_max(u) * 100));
          if (crosstech > side->tech[u2])
            side->tech[u2] = crosstech;
          if (side->tech[u2] != oldtech) {
            /* (should do generic side display update?) */
            notify_tech(side, u2, oldtech, side->tech[u2]);
            /* Update info for side_can_build. */
            update_canbuild_vector(side);
          }
      }
    }
}

/* Notify the given side of any notable changes in technological
   ability. */
/* (to nlang.c?) */
void
notify_tech(Side *side, int u, int oldtech, int newtech)
{
    if (oldtech < u_tech_to_see(u)
      && newtech >= u_tech_to_see(u)) {
      notify(side, "You now have the technology to see %s units",
             u_type_name(u));
    }
    if (oldtech < u_tech_to_own(u)
      && newtech >= u_tech_to_own(u)) {
      notify(side, "You now have the technology to own %s units",
             u_type_name(u));
    }
    if (oldtech < u_tech_to_use(u)
      && newtech >= u_tech_to_use(u)) {
      notify(side, "You now have the technology to use %s units",
             u_type_name(u));
    }
    if (oldtech < u_tech_to_build(u)
      && newtech >= u_tech_to_build(u)) {
      notify(side, "You now have the technology to build new %s units",
             u_type_name(u));
    }
}

/* Toolup action. */

/* Before a unit can build another, it may need to take some time to
   prepare by "tooling up". */

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

int
do_toolup_action(Unit *unit, Unit *unit2, int u3)
{
    int oldtp, tp;

    if (unit2->tooling == NULL)
      init_unit_tooling(unit2);
    /* Increase the tooling, clipping to its max. */
    oldtp = tp = unit2->tooling[u3];
    tp += uu_tp_per_toolup(unit2->type, u3);
    tp = min(tp, uu_tp_max(unit2->type, u3));
    unit2->tooling[u3] = tp;
    if (unit2->tooling[u3] != oldtp) {
      notify_tp(unit->side, unit2, u3, oldtp, unit2->tooling[u3]);
    }
    /* Adjust any related toolings. */
    adjust_tooling_crossover(unit2, u3);
    update_unit_display(unit->side, unit2, TRUE);
    /* Consume acp. */
    use_up_acp(unit, uu_acp_to_toolup(unit2->type, u3));
    return A_ANY_DONE;
}

int
check_toolup_action(Unit *unit, Unit *unit2, int u3)
{
    int acp, tp;

    if (!in_play(unit))
      return A_ANY_ERROR;
    if (!in_play(unit2))
      return A_ANY_ERROR;
    if (!is_unit_type(u3))
      return A_ANY_ERROR;
    acp = uu_acp_to_toolup(unit2->type, u3);
    if (acp < 1)
      return A_ANY_CANNOT_DO;
    if (!can_have_enough_acp(unit, acp))
      return A_ANY_CANNOT_DO;
    tp = (unit2->tooling ? unit2->tooling[u3] : 0);
    /* Check if tooling is already at its max. */
    if (tp >= uu_tp_max(unit2->type, u3))
      return A_ANY_ERROR;
    if (!has_enough_acp(unit, acp))
      return A_ANY_NO_ACP;
    return A_ANY_OK;
}

/* For all unit types, bring their tooling levels up to match the crossovers
   from the given unit type. */

void
adjust_tooling_crossover(Unit *unit, int u2)
{
    int u3, uucross, cross, oldtp3;

    /* Perhaps nothing to cross over with. */
    if (unit->tooling == NULL)
      return;
    for_all_unit_types(u3) {
      if (u3 != u2) {
          uucross = uu_tp_crossover(u2, u3);
          if (uucross > 0) {
            oldtp3 = unit->tooling[u3];
            /* Calculate the crossover as a ratio of max levels. */
            cross = (unit->tooling[u2] * uucross * uu_tp_max(u2, u3)) /
                  (uu_tp_max(u2, u3) * 100);
            if (cross > oldtp3)
              unit->tooling[u3] = cross;
            if (unit->tooling[u3] != oldtp3) {
                notify_tp(unit->side, unit, u2, oldtp3, unit->tooling[u3]);
            }
          }
      }
    }
}

/* Notify the given side/unit of any notable changes in tooling. */
/* (to nlang.c?) */
void
notify_tp(Side *side, Unit *unit, int u2, int oldtp, int newtp)
{
    if (oldtp < uu_tp_to_build(unit->type, u2)
      && newtp >= uu_tp_to_build(unit->type, u2)) {
      notify(side, "%s is now tooled to build %s units",
             unit_handle(side, unit), u_type_name(u2));
    }
}

/* Create-in action. */

/* This action creates the (incomplete) unit. */

int
prep_create_in_action(Unit *unit, Unit *unit2, int u3, Unit *dest)
{
    if (unit == NULL || unit->act == NULL || unit2 == NULL || dest == NULL)
      return FALSE;
    unit->act->nextaction.type = ACTION_CREATE_IN;
    unit->act->nextaction.args[0] = u3;
    unit->act->nextaction.args[1] = dest->id;
    unit->act->nextaction.actee = unit2->id;
    return TRUE;
}

int
do_create_in_action(Unit *unit, Unit *unit2, int u3, Unit *dest)
{
    int u2 = unit2->type, m, consum, consum2;
    Unit *newunit;

    if (u_advanced(u3)) {
      Unit *oldunit = unit_at(unit2->x, unit2->y);
      if (oldunit->type == u3) {
          /* (should test that this kind of joining is allowed) */
          oldunit->size += 1;
          update_unit_display(oldunit->side, oldunit, TRUE);
          garrison_unit(unit2, oldunit);
          use_up_acp(unit, uu_acp_to_create(u2, u3));
          return A_ANY_DONE;
      }
    }
    /* Make the new unit. */
    newunit = create_unit(u3, FALSE);
    if (newunit != NULL) {
      /* Fill in various properties. */
      set_created_unit_props(newunit, u2, unit->side, unit2->morale);
      /* Transfer the builders stash of cps to newunit if permitted. */
      if (uu_builder_can_reuse_cp(u2, u3)) {
          newunit->cp += min(unit2->cp_stash, u_cp(u3) - newunit->cp);
          unit2->cp_stash -= min(unit2->cp_stash, u_cp(u3) - newunit->cp);
      }
      /* Put the new unit inside the designated transport. */
      enter_transport(newunit, dest);
      /* Unit might have started out complete. */
      if (completed(newunit)) {
          garrison_unit(unit2, newunit);
          make_unit_complete(newunit);
      } else {
          record_event(H_UNIT_CREATED, add_side_to_set(unit2->side, NOSIDES),
                   side_number(unit2->side), newunit->id);
      }
      if (alive(unit2)) {
          count_gain(unit2->side, newunit->type, build_gain);
          /* Consume the creator's supplies as specified. */
          for_all_material_types(m) {
            consum = um_consumption_on_creation(u3, m);
            consum2 = 0;
            if (consum > unit2->supply[m]) {
                consum2 = consum - unit2->supply[m];
                consum = unit2->supply[m];
            }
            unit2->supply[m] -= consum;
            
            /* Use material from treasury if necessary. */
            if (consum2 > 0) {
                  if (side_has_treasury(unit2->side, m)
                      && um_takes_from_treasury(unit2->type, m)) {  
                            unit2->side->treasury[m] -= consum2;      
                  } else {
                        /* Should never happen. */
                        run_warning("Unit created even though material was insufficient"); 
                  }
               }
          }
      }
      use_up_acp(unit, uu_acp_to_create(u2, u3));
      return A_ANY_DONE;
    } else {
      /* We've hit a max number of units, nothing to be done. */
      return A_ANY_ERROR;
    }
}

int
check_create_in_action(Unit *unit, Unit *unit2, int u3, Unit *dest)
{
    int rslt;

    if (!in_play(dest))
      return A_ANY_ERROR;
    rslt = check_create_common(unit, unit2, u3, dest->x, dest->y);
    if (rslt != A_ANY_OK)
      return rslt;
    if (u_advanced(u3)) {
      Unit *oldunit = unit_at(unit2->x, unit2->y);
      if (oldunit->type == u3)
        return A_ANY_OK;
    }
    if (!type_can_occupy(u3, dest))
      return A_ANY_ERROR;
    return A_ANY_OK;
}

/* Test for the constraints common to create-in and create-at. */

static int
check_create_common(Unit *unit, Unit *unit2, int u3, int x, int y)
{
    int u, u2, m, tp, totsup;

    if (!in_play(unit))
      return A_ANY_ERROR;
    if (!in_play(unit2))
      return A_ANY_ERROR;
    if (!is_unit_type(u3))
      return A_ANY_ERROR;
    u = unit->type;  u2 = unit2->type;
    if (uu_acp_to_create(u2, u3) < 1)
      return A_ANY_CANNOT_DO;
    if (!can_have_enough_acp(unit, uu_acp_to_create(u2, u3)))
      return A_ANY_CANNOT_DO;
    /* Check if type is allowed and if needed tech & advances exist. */
    if (!side_can_build(unit->side, u3))
      return A_ANY_CANNOT_DO;
    /* Check if we hit a limit on the number of units. */
    if (!new_unit_allowed_on_side(u3, unit->side))
      return A_ANY_CANNOT_DO;
    /* Check the tooling. */
    tp = (unit2->tooling ? unit2->tooling[u3] : 0);
    if (tp < uu_tp_to_build(u2, u3))
      return A_ANY_ERROR;
    if (distance(unit2->x, unit2->y, x, y) > uu_create_range(u2, u3))
      return A_ANY_TOO_FAR;
    if (unit2->transport != NULL
        && !uu_occ_can_build(unit2->transport->type, u2))
      return A_ANY_ERROR;
    for_all_material_types(m) {
      totsup = unit2->supply[m];
      /* Add material in treasury if available to this unit. */
      if (side_has_treasury(unit2->side, m)
          && um_takes_from_treasury(unit2->type, m)) {
            totsup += unit2->side->treasury[m]; 
      }
      if (totsup < um_to_create(u3, m))
        return A_ANY_NO_MATERIAL;
      if (totsup < um_consumption_on_creation(u3, m))
        return A_ANY_NO_MATERIAL;
    }
    if (!has_enough_acp(unit, uu_acp_to_create(u2, u3)))
      return A_ANY_NO_ACP;
    /* (should check that overall unit count limits not hit yet) */
    return A_ANY_OK;
}

static void
set_created_unit_props(Unit *newunit, int u2, Side *side, int u2mor)
{
    int u3 = newunit->type, m, amt, mfrac, mor;

    newunit->hp = newunit->hp2 = 1;
    newunit->cp = uu_creation_cp(u2, u3);
    set_unit_side(newunit, side);
    set_unit_origside(newunit, side);
    /* Always number the unit when first created. */
    assign_unit_number(newunit);
    /* Set all supplies to their just-created levels. */
    for_all_material_types(m) {
      amt = newunit->supply[m];
      amt = max(amt, um_created_supply(u3, m));
      /* Clip to capacity. */
      amt = min(amt, um_storage_x(u3, m));
      newunit->supply[m] = amt;
    }
    /* Set the created unit's morale as a ratio to the creator's morale. */
    if (u_morale_max(u3) > 0) {
      if (u_morale_max(u2) > 0) {
          mfrac = (u2mor * 100) / u_morale_max(u2);
          mor = (uu_creation_morale(u2, u3) * mfrac) / 100;
          /* Scale to new unit's morale range. */
          newunit->morale = (mor * u_morale_max(u3)) / 100;
      } else {
          /* (should get from global side morale or some such?) */
          newunit->morale = u_morale_max(u3);
      }
    }
}

/* Create-at action. */

/* Create a new unit of a given type, out in the open at a given location. */

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

int
do_create_at_action(Unit *unit, Unit *unit2, int u3, int x, int y, int z)
{
    int u2 = unit2->type, m, consum, consum2;
    Unit *newunit;

    /* Make the new unit. */
    newunit = create_unit(u3, FALSE);
    if (newunit != NULL) {
      /* Fill in various properties. */
      set_created_unit_props(newunit, u2, unit->side, unit2->morale);
      /* Transfer the builders stash of cps to newunit if permitted. */
      if (uu_builder_can_reuse_cp(u2, u3)) {
          newunit->cp += min(unit2->cp_stash, u_cp(u3) - newunit->cp);
          unit2->cp_stash -= min(unit2->cp_stash, u_cp(u3) - newunit->cp);
      }
      /* Put it at a correct location. */
      if (can_occupy_cell(newunit, x, y)) {
          enter_cell(newunit, x, y);
      } else if (can_occupy_cell_without(newunit, x, y, unit2)
            && can_occupy(unit2, newunit)) {
          /* Let the builder occupy its incomplete work. */
          leave_cell(unit2);
          enter_cell(newunit, x, y);
          enter_transport(unit2, newunit);
      } else {
          /* This should never happen.  If it does, then the preflighting
             in check_create_at_action has made a mistake. */
          run_error("construction/occupation complications");
      }
      /* (should) set its altitude? */
      /* Unit might be complete right away. */
      if (completed(newunit)) {
          garrison_unit(unit2, newunit);
          make_unit_complete(newunit);
      } else {
          record_event(H_UNIT_CREATED, add_side_to_set(unit2->side, NOSIDES),
                   side_number(unit2->side), newunit->id);
      }
      if (alive(unit2)) {
          count_gain(unit2->side, newunit->type, build_gain);
          /* Consume the creator's supplies as specified. */
          for_all_material_types(m) {
            consum = um_consumption_on_creation(u3, m);
            consum2 = 0;
            if (consum > unit2->supply[m]) {
                consum2 = consum - unit2->supply[m];
                consum = unit2->supply[m];
            }
            /* Use material from treasury if necessary. */
            if (consum2 > 0) {
                  if (side_has_treasury(unit2->side, m)
                      && um_takes_from_treasury(unit2->type, m)) {  
                            unit2->side->treasury[m] -= consum2;      
                  } else {
                        /* Should never happen. */
                        run_warning("Unit created even though material was insufficient"); 
                  }
               }
          }
      }
      use_up_acp(unit, uu_acp_to_create(u2, u3));
      return A_ANY_DONE;
    } else {
      /* We've hit a max number of units, nothing to be done. */
      return A_ANY_ERROR;
    }
}

int
check_create_at_action(Unit *unit, Unit *unit2, int u3, int x, int y, int z)
{
    int rslt, t;

    if (!inside_area(x, y))
      return A_ANY_ERROR;
    rslt = check_create_common(unit, unit2, u3, x, y);
    if (rslt != A_ANY_OK)
      return rslt;
    /* Check that the desired cell has room for the unit, either by
       stacking, or by having the builder occupy its construction work. */
    if (!(type_can_occupy_cell(u3, x, y)
       || (can_occupy_type(unit2, u3)
              && type_can_occupy_cell_without(u3, x, y, unit2))))
      return A_ANY_ERROR;
    t = terrain_at(x, y);
    /* Can't build a unit on hostile terrain, unless a connection
       available for it to sit on. */
    if ((ut_vanishes_on(u3, t) || ut_wrecks_on(u3, t))
      && !type_can_sit_on_conn(u3, x, y))
      return A_ANY_ERROR;
    return A_ANY_OK;
}

/* Build action. */

/* This action makes progress on a construction effort, possibly completing
   the new unit and making it available. */

int build_step_consumption(int u2, int u3, int m, int cp);

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

int
do_build_action(Unit *unit, Unit *unit2, Unit *newunit)
{
    int u, u2, u3, m;

    u = unit->type;  u2 = unit2->type;  u3 = newunit->type;
    for_all_material_types(m) {
      unit2->supply[m] -= build_step_consumption(u2, u3, m, newunit->cp);
    }
    newunit->cp += uu_cp_per_build(u2, u3);
    if (completed(newunit)) {
      garrison_unit(unit2, newunit);
      make_unit_complete(newunit);
      update_unit_display(newunit->side, newunit, TRUE);
      use_up_acp(unit, uu_acp_to_build(u2, u3));
      return A_BUILD_COMPLETED;
    }
    update_unit_display(newunit->side, newunit, TRUE);
    use_up_acp(unit, uu_acp_to_build(u2, u3));
    return A_ANY_DONE;
}

/* Consumption per build is in terms of total material to complete,
   this routine computes the consumption for a particular build action.
   This is basically division of the total by the number of completion
   points, but is careful about not missing any needed consumption
   if the division rounds down. */

int
build_step_consumption(int u2, int u3, int m, int cp)
{
    int consumtot = um_consumption_per_build(u3, m);
    int cpinc = uu_cp_per_build(u2, u3);
    int consum, rslt;

    consum = (cpinc * consumtot * 100);
    /* Bug fix: if consumption really is 0, we return 0 here so that
    "correction" below does not make it non-zero. */ 
    if (consum == 0)
        return 0;
    consum /= (u_cp(u3));
    rslt = consum / 100;
    if (((cp + cpinc) * 100) / (u_cp(u3)) <= consum % 100)
      ++rslt;
    return rslt;
}

void
garrison_unit(Unit *unit, Unit *unit2)
{
    int u = unit->type, u2 = unit2->type, x = unit->x, y = unit->y;
    Unit *transport = NULL, *occ, *nextocc;

    /* Maybe get rid of the building unit if it is to be the garrison. */
    if (uu_hp_to_garrison(u, u2) >= unit->hp) {
      /* But first get the about-to-be-killed garrisoning unit
         disconnected from everything. */
      leave_cell(unit);
      /* Put new unit in place of the garrisoning one, if it was an
           occupant. */
      if (unit2->transport == unit) {
          leave_transport(unit2);
          if (transport != NULL) { /* some other unit that could be transport? */
            enter_transport(unit2, transport);
          } else {
            enter_cell(unit2, x, y);
          }
      }

      if (unit2->transport != NULL && unit2->transport != unit)
        transport = unit2->transport;
      /* for_all_occupants will not work here, 
         since leave_transport changes occ->nexthere */
      for (occ = unit->occupant; occ != NULL; occ = nextocc) {
          nextocc = occ->nexthere;
          /* Move the other occupants anywhere we can find. */
          if (can_occupy(occ, unit2)) {
            /* leave_cell won't work here, since "unit" already left cell */
            leave_transport(occ);
            update_unit_display(unit->side, unit, TRUE);
            enter_transport(occ, unit2);
          } else if (transport != NULL && can_occupy(occ, transport)) {
            leave_transport(occ);
            update_unit_display(unit->side, unit, TRUE);
            enter_transport(occ, transport);
          } else if (can_occupy_cell(occ, x, y)) {
            leave_transport(occ);
            update_unit_display(unit->side, unit, TRUE);
            enter_cell(occ, x, y);
          }
          /* Otherwise the occupant has to die along with the garrison. */
          /* (should also do something with sub-occs of doomed occs?) */
      }
      /* Now we can get rid of the garrisoning unit without scrambling
         anything else. */
      tmphevtdata1 = unit2->id;
      kill_unit(unit, H_UNIT_GARRISONED);
    } else {
      /* Note that if this all happens before damage is reckoned,
         hp and hp2 might be different. */
      unit->hp -= uu_hp_to_garrison(u, u2);
      unit->hp2 -= uu_hp_to_garrison(u, u2);
      /* (should record loss of hp as garrison event?) */
    }
}

int
check_build_action(Unit *unit, Unit *unit2, Unit *newunit)
{
    int u, u2, u3, acpcost, m, tp;

    if (!in_play(unit))
      return A_ANY_ERROR;
    if (!in_play(unit2))
      return A_ANY_ERROR;
    if (!in_play(newunit))
      return A_ANY_ERROR;
    u = unit->type;  u2 = unit2->type;  u3 = newunit->type;
    acpcost = uu_acp_to_build(u2, u3);
    if (acpcost < 1)
      return A_ANY_CANNOT_DO;
    if (!can_have_enough_acp(unit, acpcost))
      return A_ANY_CANNOT_DO;
    if (uu_cp_per_build(u2, u3) <= 0)
      return A_ANY_ERROR;

/* This causes games that use obsoleting advances to hang. We now assume
that if we were allowed to create the unit (which checks side_can_build)
we should also be allowed to continue building on it. */

#if 0

    if (!side_can_build(unit->side, u3))
      return A_ANY_ERROR;

#endif

    /* Check the tooling. */
    tp = (unit2->tooling ? unit2->tooling[u3] : 0);
    if (tp < uu_tp_to_build(u2, u3))
      return A_ANY_ERROR;
    /* A mistake to be working on a complete unit. */
    if (fullsized(newunit))
      return A_ANY_ERROR;
    /* Check the distance to the unit being worked on. */
    if (distance(unit->x, unit->y, newunit->x, newunit->y)
      > uu_build_range(u, u3))
      return A_ANY_ERROR;
    /* Note that we should be able to build when inside the incomplete
       unit we're building. */
    if (unit2->transport != NULL
      && completed(unit2->transport)
        && !uu_occ_can_build(unit2->transport->type, u2))
      return A_ANY_ERROR;
    if (!has_enough_acp(unit, acpcost))
      return A_ANY_NO_ACP;
    for_all_material_types(m) {
      if (unit2->supply[m] < um_to_build(u2, m))
        return A_ANY_NO_MATERIAL;
      if (unit2->supply[m] < build_step_consumption(u2, u3, m, newunit->cp))
        return A_ANY_NO_MATERIAL;
    }
    return A_ANY_OK;
}

/* Do all the little things to make a fully operational unit. */

void
make_unit_complete(Unit *unit)
{
    int u = unit->type, m;
    SideMask observers;
    Side *side2;

    /* Make this a "complete" but not a "fullsized" unit. */
    unit->cp = max(unit->cp, u_cp(u) / u_parts(u));
    unit->hp = unit->hp2 = u_hp(u) / u_parts(u);
    /* Christen our new unit. Its serial number (if it is a type that has
       one) was assigned just after its creation. */
    make_up_unit_name(unit);
    /* It also starts viewing its surroundings. */
    cover_area(unit->side, unit, unit->transport, -1, -1, unit->x, unit->y);
    /* Kick out enemy users if we control this cell. */
    kick_out_enemy_users(unit->side, unit->x, unit->y);
    /* Set all the supplies up to their unit-just-completed levels. */
    for_all_material_types(m) {
      unit->supply[m] = max(unit->supply[m], um_completed_supply(u, m));
      unit->supply[m] = min(unit->supply[m], um_storage_x(u, m));
    }
    /* Also see if anybody here is willing to share to make up any
       deficiencies before the end of the turn. */
    for_all_material_types(m) {
      if (unit->transport)
        try_sharing(unit->transport, unit, m);
    }
    init_unit_actorstate(unit, FALSE);
    init_unit_plan(unit);
    /* Put this unit into action immediately, at full acp. */
    if (unit->act) {
      compute_acp(unit);
      if (unit->act->initacp > 0) {
         /* Make_unit_complete may be called by advanced units at
            turn 0 before compose_actionvectors has been called! */
         if (unit->side->actionvector == NULL) {
            unit->side->actionvector = make_unit_vector(max(numunits, 100));
            clear_unit_vector(unit->side->actionvector);
         }
         unit->side->actionvector = add_unit_to_vector(unit->side->actionvector, unit, 0);
      }
    }
    /* Inform all sides that should know about the completion. */
    observers = NOSIDES;
    for_all_sides(side2) {
      if (side2 == unit->side
          || trusted_side(unit->side, side2)
          /* (or add all sides if g_see_all?) */
          ) {
          observers = add_side_to_set(unit->side, observers);
      }
    }
    record_event(H_UNIT_COMPLETED, observers, side_number(unit->side),
             unit->id);
    /* Make sure the image of the newly completed unit (and its name) is drawn. */
    all_see_cell(unit->x, unit->y);
    /* (should add to any per-side tallies) */
    Dprintf("%s is completed\n", unit_desig(unit));
}

/* Repair action. */

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

int
do_repair_action(unit, unit2, unit3)
Unit *unit, *unit2, *unit3;
{
    int u, u2, u3, rep, m;

    u = unit->type;  u2 = unit2->type;  u3 = unit3->type;
    rep = uu_repair(u2, u3);
    /* Add to the repairee's hit points. */
    add_to_unit_hp(unit3, prob_fraction(rep));
    /* Eat supplies used up by repair. */
    for_all_material_types(m) {
      unit2->supply[m] -= um_consumption_per_repair(u3, m);
    }
    use_up_acp(unit, uu_acp_to_repair(u2, u3));
    return A_ANY_DONE;
}

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

    if (!in_play(unit))
      return A_ANY_ERROR;
    if (!in_play(unit2))
      return A_ANY_ERROR;
    if (!in_play(unit3))
      return A_ANY_ERROR;
    u = unit->type;  u2 = unit2->type;  u3 = unit3->type;
    acp = uu_acp_to_repair(u2, u3);
    if (acp < 1)
      return A_ANY_CANNOT_DO;
    if (!can_have_enough_acp(unit, acp))
      return A_ANY_CANNOT_DO;
    if (uu_repair(u2, u3) <= 0)
      return A_ANY_ERROR;
    if (unit3->hp >= u_hp(u3))
      return A_ANY_ERROR;
    if (unit2->hp < uu_hp_to_repair(u2, u3))
      return A_ANY_ERROR;
    for_all_material_types(m) {
      if (unit2->supply[m] < um_to_repair(u3, m))
        return A_ANY_NO_MATERIAL;
      if (unit2->supply[m] < um_consumption_per_repair(u3, m))
        return A_ANY_NO_MATERIAL;
    }
    if (!has_enough_acp(unit, acp))
      return A_ANY_NO_ACP;
    return A_ANY_OK;
}

/* Disband action. */

/* The disband action destroys a unit in an "orderly" fashion, and can be
   undertaken voluntarily. */

int
prep_disband_action(unit, unit2)
Unit *unit, *unit2;
{
    if (unit == NULL || unit->act == NULL || unit2 == NULL)
      return FALSE;
    unit->act->nextaction.type = ACTION_DISBAND;
    unit->act->nextaction.actee = unit2->id;
    return TRUE;
}

int
do_disband_action(unit, unit2)
Unit *unit, *unit2;
{
    int u2, m, amt, disb;

    u2 = unit2->type;
    /* Recover some percentage of the unit's supply. */
    for_all_material_types(m) {
      if (um_supply_per_disband(u2, m) > 0 && unit2->supply[m] > 0) {
          amt = (unit2->supply[m] * um_supply_per_disband(u2, m)) / 100;
          /* Unit always loses the amount, whether or not distributed. */
          unit2->supply[m] -= amt;
          distribute_material(unit2, m, amt);
      }
    }
    /* Remove hit points or kill the unit directly. */
    disb = u_hp_per_disband(u2);
    if (disb < unit2->hp) {
      unit2->hp -= disb;
      unit2->hp2 = unit2->hp;
    } else {
      /* Pass around whatever we can get out of the unit itself. */
      for_all_material_types(m) {
          if (um_recycleable(u2, m) > 0) {
            distribute_material(unit2, m, um_recycleable(u2, m));
          }
      }
      kill_unit(unit2, H_UNIT_DISBANDED);
    }
    use_up_acp(unit, u_acp_to_disband(u2));
    return A_ANY_DONE;
}

/* Given a unit and a quantity of material, pass it out to nearby units. */

void
distribute_material(unit, m, amt)
Unit *unit;
int m, amt;
{
    int dir, x1, y1;
    Unit *unit2;

    /* Distribute to transport first. */
    if (amt > 0 && unit->transport != NULL) {
      amt = give_away(unit->transport, m, amt);
      if (amt > 0 && unit->transport->transport != NULL)
        amt = give_away(unit->transport->transport, m, amt);
    }
    /* Then to any unit in the cell. */
    if (amt > 0) {
      for_all_stack(unit->x, unit->y, unit2) {
          if (unit2 != unit && unit_trusts_unit(unit, unit2)) {
            amt = give_away(unit2, m, amt);
          }
      }
    }
    /* Then to any unit in an adjacent cell. */
    if (amt > 0) {
      for_all_directions(dir) {
          if (interior_point_in_dir(unit->x, unit->y, dir, &x1, &y1)) {
            for_all_stack(x1, y1, unit2) {
                if (unit_trusts_unit(unit, unit2)) {
                  amt = give_away(unit2, m, amt);
                }
            }
          }
          if (amt == 0)
            break;
      }
    }
}

/* Give as much as possible of the given material to the unit,
   return the amount left to give away. */

static int
give_away(unit, m, amt)
Unit *unit;
int m, amt;
{
    int space, add;

    space = um_storage_x(unit->type, m) - unit->supply[m];
    add = (amt < space ? amt : space);
    unit->supply[m] += add;
    amt -= add;
    if (add > 0)
      update_unit_display(unit->side, unit, TRUE);
    return amt;
}

int
check_disband_action(unit, unit2)
Unit *unit, *unit2;
{
    int u, u2, acp;

    if (!in_play(unit))
      return A_ANY_ERROR;
    if (!in_play(unit2))
      return A_ANY_ERROR;
    u = unit->type;  u2 = unit->type;
    acp = u_acp_to_disband(u2);
    if (acp < 1)
      return A_ANY_CANNOT_DO;
    if (!can_have_enough_acp(unit, acp))
      return A_ANY_CANNOT_DO;
    if (u_hp_per_disband(unit2->type) <= 0)
      return A_ANY_CANNOT_DO;
    if (!has_enough_acp(unit, acp))
      return A_ANY_NO_ACP;
    return A_ANY_OK;
}

/* Transfer-part action. */

/* Create a new unit that is similar to the original one, and give it
   some of the parts of the original unit. */
/* (New unit in same cell if possible or else in random adjacent cell.) */

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

int
do_transfer_part_action(unit, unit2, parts, unit3)
Unit *unit, *unit2, *unit3;
int parts;
{
    int u2 = unit2->type, acp, part_hp;

    part_hp = u_hp(u2) / u_parts(u2);
    if (unit3 == NULL) {
      /* Create a new unit with parts from unit2. */
      unit3 = create_unit(u2, TRUE);
      if (unit3 != NULL) {
          unit3->hp = parts * part_hp;
          /* (Cap the hp now - occupancy calcs below might use unit parts
              to determine available capacity) */
          unit3->hp = min(unit3->hp, u_hp(unit3->type));
          unit3->hp2 = unit3->hp;
          set_unit_side(unit3, unit->side);
          set_unit_origside(unit3, unit->origside);
          /* Always number the unit when first created. */
          assign_unit_number(unit3);
          /* (should fill in more slots of new unit, such as supply?) */
          if (can_occupy_cell(unit3, unit2->x, unit2->y)) {
            enter_cell(unit3, unit2->x, unit2->y);
          } else {
            /* (should add code to enter something else here) */
            /* This should never happen. */
            run_warning("transfer_part complications, leaving unit offworld");
          }
      } else {
          /* We have a problem. */
          return A_ANY_ERROR;
      }
    } else {
      /* Increase the unit3's hp by what's in this unit, and cap it. */
      add_to_unit_hp(unit3, parts * part_hp);
    }
    /* Tweak parts in unit2 also. */
    if (parts * part_hp >= unit2->hp) {
      /* (should transfer occs, supply, etc to unit3) */
      kill_unit(unit2, H_UNIT_MERGED);
    } else {
      unit2->hp -= parts * part_hp;
      unit2->hp2 = unit2->hp;
    }
    if (alive(unit2))
      update_unit_display(unit2->side, unit2, TRUE);
    update_unit_display(unit3->side, unit3, TRUE);
    acp = u_acp_to_transfer_part(u2);
    use_up_acp(unit, acp);
    return A_ANY_DONE;
}

int
check_transfer_part_action(unit, unit2, parts, unit3)
Unit *unit, *unit2, *unit3;
int parts;
{
    int u2, acp;

    if (!in_play(unit))
      return A_ANY_ERROR;
    if (!in_play(unit2))
      return A_ANY_ERROR;
    if (parts <= 0)
      return A_ANY_ERROR;
    /* unit3 can be null. */
    u2 = unit2->type;
    acp = u_acp_to_transfer_part(u2);
    if (acp < 1)
      return A_ANY_CANNOT_DO;
    /* Check if type is allowed and if needed tech & advances exist. */
    if (!side_can_build(unit->side, u2))
      return A_ANY_CANNOT_DO;
    /* Check if we hit a limit on the number of units. */
    if (!new_unit_allowed_on_side(u2, unit->side))
      return A_ANY_CANNOT_DO;
    if (!can_have_enough_acp(unit, acp))
      return A_ANY_CANNOT_DO;
    if (u_parts(u2) <= 1)
      return A_ANY_ERROR;
    if (!has_enough_acp(unit, acp))
      return A_ANY_NO_ACP;
    return A_ANY_OK;
}

/* Change-type action. */

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

/* Actually change the type of a unit. */

int
do_change_type_action(unit, unit2, u3)
Unit *unit, *unit2;
int u3;
{
    int u, u2;

    u = unit->type;  u2 = unit2->type;
    change_unit_type(unit2, u3, H_UNIT_TYPE_CHANGED);
    update_unit_display(unit2->side, unit2, TRUE);
    use_up_acp(unit, uu_acp_to_change_type(u2, u3));
    return A_ANY_DONE;
}

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

    if (!in_play(unit))
      return A_ANY_ERROR;
    if (!in_play(unit2))
      return A_ANY_ERROR;
    if (!is_unit_type(u3))
      return A_ANY_ERROR;
    u = unit->type;  u2 = unit2->type;
    acp = uu_acp_to_change_type(u2, u3);
    if (acp < 1)
      return A_ANY_CANNOT_DO;
    if (!can_have_enough_acp(unit, acp))
      return A_ANY_CANNOT_DO;
    if (!side_can_build(unit2->side, u3))
      return A_ANY_CANNOT_DO;
    if (!has_enough_acp(unit, acp))
      return A_ANY_NO_ACP;
    /* Check that the unit has any required supplies. */
    for_all_material_types(m) {
      if (unit2->supply[m] < um_to_change_type(u2, m))
        return A_ANY_NO_MATERIAL;
    }
    return A_ANY_OK;
}

/* Change-side action. */

/* Tell a unit to change to a given side. */

/* (what about occs, garrisons, plans?) */

int
prep_change_side_action(unit, unit2, side)
Unit *unit, *unit2;
Side *side;
{
    if (unit == NULL || unit->act == NULL || unit2 == NULL)
      return FALSE;
    unit->act->nextaction.type = ACTION_CHANGE_SIDE;
    unit->act->nextaction.args[0] = side_number(side);
    unit->act->nextaction.actee = unit2->id;
    return TRUE;
}

int
do_change_side_action(unit, unit2, side)
Unit *unit, *unit2;
Side *side;
{
    int rslt;

    if (side_controls_unit(unit->side, unit2)) {
      /* If we own it, we can just change it. */
      change_unit_side(unit2, side, H_UNIT_ACQUIRED, NULL);
      rslt = A_ANY_DONE;
    } else {
      rslt = A_ANY_ERROR;
    }
    use_up_acp(unit, u_acp_to_change_side(unit2->type));
    return rslt;
}

int
check_change_side_action(unit, unit2, side)
Unit *unit, *unit2;
Side *side;
{
    int u, u2, acp;

    if (!in_play(unit))
      return A_ANY_ERROR;
    if (!in_play(unit2))
      return A_ANY_ERROR;
    if (!side_in_play(side))
      return A_ANY_ERROR;
    if (unit2->side == side)
      return A_ANY_ERROR;
    if (!unit_allowed_on_side(unit2, side))
      return A_ANY_ERROR;
    u = unit->type;  u2 = unit2->type;
    acp = u_acp_to_change_side(u2);
    if (acp < 1)
      return A_ANY_CANNOT_DO;
    if (!can_have_enough_acp(unit, acp))
      return A_ANY_CANNOT_DO;
    if (!has_enough_acp(unit, acp))
      return A_ANY_NO_ACP;
    return A_ANY_OK;
}

/* Alter-terrain action. */

/* Change the terrain in the cell to something else. */

/* We don't need to ensure that the unit can exist on the new terrain
   type, because the designer is presumed to have set things up sensibly,
   because the unit might be in an appropriate transport, or because
   there is some actual use in such a bizarre shtick. */

/* What if engineers dig hole underneath enemy unit?  Should this be
   possible, or should there be a "can-dig-under-enemy" parm?  */

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

int
do_alter_cell_action(unit, unit2, x, y, t)
Unit *unit, *unit2;
int x, y, t;
{
    int u, u2, oldt, acpr, acpa, m, amt, excess;

    u = unit->type;  u2 = unit2->type;
    oldt = terrain_at(x, y);
    /* Change the terrain to the new type. */
    change_terrain_type(x, y, t);
    for_all_material_types(m) {
      amt = tm_material_per_remove_terrain(oldt, m);
      unit2->supply[m] += amt;
      amt = tm_consumption_per_add_terrain(t, m);
      unit2->supply[m] -= amt;
      excess = unit2->supply[m] - um_storage_x(unit2->type, m);
      if (excess > 0) {
          unit2->supply[m] -= excess;
          distribute_material(unit2, m, excess);
      }
    }
    /* Note that we still charge acp even if terrain type doesn't change. */
    acpr = ut_acp_to_remove_terrain(u2, oldt);
    acpa = ut_acp_to_add_terrain(u2, t);
    use_up_acp(unit, acpr + acpa);
    return A_ANY_DONE;
}

int
check_alter_cell_action(unit, unit2, x, y, t)
Unit *unit, *unit2;
int x, y, t;
{
    int u, u2, m, oldt, acpr, acpa;

    if (!in_play(unit))
      return A_ANY_ERROR;
    if (!in_play(unit2))
      return A_ANY_ERROR;
    if (!in_area(x, y))
      return A_ANY_ERROR;
    if (!is_terrain_type(t))
      return A_ANY_ERROR;
    if (!t_is_cell(t))
      return A_ANY_ERROR;
    u = unit->type;  u2 = unit2->type;
    oldt = terrain_at(x, y);
    acpr = ut_acp_to_remove_terrain(u2, oldt);
    acpa = ut_acp_to_add_terrain(u2, t);
    if (acpr < 1 || acpa < 1)
      return A_ANY_CANNOT_DO;
    if (!can_have_enough_acp(unit, acpr + acpa))
      return A_ANY_CANNOT_DO;
    if (distance(unit2->x, unit2->y, x, y) > ut_alter_range(u2, t))
      return A_ANY_ERROR;
    if (!has_enough_acp(unit, acpr + acpa))
      return A_ANY_NO_ACP;
    /* We have to have a minimum level of supply to be able to do the action. */
    for_all_material_types(m) {
      if (unit2->supply[m] < um_to_remove_terrain(u2, m))
        return A_ANY_NO_MATERIAL;
      if (unit2->supply[m] < um_to_add_terrain(u2, m))
        return A_ANY_NO_MATERIAL;
      if (unit2->supply[m] < (tm_consumption_per_add_terrain(t, m) - tm_material_per_remove_terrain(oldt, m)))
        return A_ANY_NO_MATERIAL;
    }
    return A_ANY_OK;
}

/* Add-terrain action. */

/* Add terrain; border, connection, or coating. */

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

int
do_add_terrain_action(unit, unit2, x, y, dir, t)
Unit *unit, *unit2;
int x, y, dir, t;
{
    int u = unit->type, oldval, newval, m, amt;
    Side *side;

    switch (t_subtype(t)) {
      case cellsubtype:
            /* Will never happen. */
            break;
      case bordersubtype:
            oldval = border_at(x, y, dir, t);
            newval = TRUE;
      set_border_at(x, y, dir, t, newval);
            break;
      case connectionsubtype:
            oldval = connection_at(x, y, dir, t);
            newval = TRUE;
      set_connection_at(x, y, dir, t, newval);
            break;
      case coatingsubtype:
      oldval = aux_terrain_at(x, y, t);
            /* Interpret "dir" as depth of coating to add. */
      newval = min(oldval + dir, tt_coat_max(terrain_at(x, y), t));
      set_aux_terrain_at(x, y, t, newval);
            break;
    }
    /* Consume any material necessary to the action. */
    for_all_material_types(m) {
      amt = tm_consumption_per_add_terrain(t, m);
      unit2->supply[m] -= amt;
    }
    /* Let everybody see what has happened. */
    for_all_sides(side) {
      if (active_display(side)) {
          if (terrain_visible(side, x, y))
            update_cell_display(side, x, y, UPDATE_ALWAYS | UPDATE_ADJ);
      }
    }
    use_up_acp(unit, (newval != oldval ? ut_acp_to_add_terrain(u, t) : 1));
    return A_ANY_DONE;
}

int
check_add_terrain_action(unit, unit2, x, y, dir, t)
Unit *unit, *unit2;
int x, y, dir, t;
{
    int u, u2, m, 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;
    if (!between(0, dir, NUMDIRS - 1))
      return A_ANY_ERROR;
    if (!is_terrain_type(t))
      return A_ANY_ERROR;
    if (t_is_cell(t))
      return A_ANY_ERROR;
    u = unit->type;  u2 = unit2->type;
    acp = ut_acp_to_add_terrain(u2, t);
    if (acp < 1)
      return A_ANY_CANNOT_DO;
     if (!can_have_enough_acp(unit, acp))
       return A_ANY_CANNOT_DO;
   if (distance(unit->x, unit->y, x, y) > ut_alter_range(u2, t))
      return A_ANY_ERROR;
    if (!has_enough_acp(unit, acp))
      return A_ANY_NO_ACP;
    /* We have to have certain amounts supply to be able to do the action. */
    for_all_material_types(m) {
      if (unit2->supply[m] < um_to_add_terrain(u2, m))
        return A_ANY_NO_MATERIAL;
      if (unit2->supply[m] < tm_consumption_per_add_terrain(t, m))
        return A_ANY_NO_MATERIAL;
    }
    return A_ANY_OK;
}

/* Remove-terrain action. */

/* Remove a border, connection, or coating. */

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

int
do_remove_terrain_action(unit, unit2, x, y, dir, t)
Unit *unit, *unit2;
int x, y, dir, t;
{
    int u = unit->type, oldval, newval, m, amt, excess;
    Side *side;

    switch (t_subtype(t)) {
      case cellsubtype:
            /* Will never happen. */
            break;
      case bordersubtype:
      oldval = border_at(x, y, dir, t);
      newval = FALSE;
      set_border_at(x, y, dir, t, newval);
            break;
      case connectionsubtype:
      oldval = connection_at(x, y, dir, t);
      newval = FALSE;
      set_connection_at(x, y, dir, t, newval);
            break;
      case coatingsubtype:
      oldval = aux_terrain_at(x, y, t);
            /* Interpret "dir" as depth of coating to remove. */
      newval = max(oldval - dir, 0);
      /* If newval drops below the min coating depth, coating will vanish. */
      if (newval < tt_coat_min(terrain_at(x, y), t))
        newval = 0;
      set_aux_terrain_at(x, y, t, newval);
            break;
    }
    for_all_material_types(m) {
      amt = tm_material_per_remove_terrain(t, m);
      unit2->supply[m] += amt;
      /* Clip to available storage and move leftovers around. */
      excess = unit2->supply[m] - um_storage_x(unit2->type, m);
      if (excess > 0) {
          unit2->supply[m] -= excess;
          distribute_material(unit2, m, excess);
      }
    }
    /* Let everybody see what has happened. */
    for_all_sides(side) {
      if (active_display(side)) {
          if (terrain_visible(side, x, y))
            update_cell_display(side, x, y, UPDATE_ALWAYS | UPDATE_ADJ);
      }
    }
    use_up_acp(unit, (newval != oldval ? ut_acp_to_remove_terrain(u, t) : 1));
    return A_ANY_DONE;
}

int
check_remove_terrain_action(unit, unit2, x, y, dir, t)
Unit *unit, *unit2;
int x, y, dir, t;
{
    int u, u2, m, 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;
    if (!between(0, dir, NUMDIRS - 1))
      return A_ANY_ERROR;
    if (!is_terrain_type(t))
      return A_ANY_ERROR;
    if (t_is_cell(t))
      return A_ANY_ERROR;
    u = unit->type;  u2 = unit2->type;
    acp = ut_acp_to_remove_terrain(u2, t);
    if (acp < 1)
      return A_ANY_CANNOT_DO;
    if (!can_have_enough_acp(unit, acp))
      return A_ANY_CANNOT_DO;
    if (distance(unit->x, unit->y, x, y) > ut_alter_range(u2, t))
      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 do the action. */
    for_all_material_types(m) {
      if (unit2->supply[m] < um_to_remove_terrain(u2, m))
        return A_ANY_NO_MATERIAL;
    }
    return A_ANY_OK;
}

/* Execute a given action on a given unit. */

/* (assumes unit can act in the first place - valid?) */

int
execute_action(Unit *unit, Action *action)
{
    char *argtypestr;
    int u = unit->type, rslt = A_ANY_ERROR, n, i, a[4];
    Unit *unit2, *aunits[4];
    Side *asides[4];
    extern int numsoundplays;

    Dprintf("%s doing %s with %d acp left\n",
          unit_desig(unit), action_desig(action), unit->act->acp);

    if (!alive(unit) || !unit->act || unit->act->acp < u_acp_min(u))
      return A_ANY_ERROR;

    argtypestr = actiondefns[(int) action->type].argtypes;
    n = strlen(argtypestr);
    for (i = 0; i < n; ++i) {
      switch (argtypestr[i]) {
        case 'n':
        case 'u':
        case 'm':
        case 't':
        case 'x':
        case 'y':
        case 'z':
        case 'd':
          a[i] = action->args[i];
          break;
        case 'U':
          aunits[i] = find_unit(action->args[i]);
          /* It may be that the argunit cannot be found - perhaps the
             unit died before the action could be applied to it.  In any
             case, let the per-action code handle the case. */
          break;
        case 'S':
          asides[i] = side_n(action->args[i]);
          if (asides[i] == NULL)
            asides[i] = indepside;
          break;
        default:
          case_panic("action argument type", argtypestr[i]);
          break;
      }
    }
    if (action->actee == 0) {
      unit2 = unit;
    } else {
      unit2 = find_unit(action->actee);
    }
    if (unit2 == NULL) {
      return A_ANY_ERROR;
    }
    switch (actiondefns[(int) action->type].typecode) {
      case ACTION_NONE:
      rslt = check_none_action(unit, unit2);
      break;
      case ACTION_MOVE:
      rslt = check_move_action(unit, unit2, a[0], a[1], a[2]);
      break;
      case ACTION_ENTER:
      rslt = check_enter_action(unit, unit2, aunits[0]);
      break;
      case ACTION_ATTACK:
      rslt = check_attack_action(unit, unit2, aunits[0], a[1]);
      break;
      case ACTION_OVERRUN:
      rslt = check_overrun_action(unit, unit2, a[0], a[1], a[2], a[3]);
      break;
      case ACTION_FIRE_AT:
      rslt = check_fire_at_action(unit, unit2, aunits[0], a[1]);
      break;
      case ACTION_FIRE_INTO:
      rslt = check_fire_into_action(unit, unit2, a[0], a[1], a[2], a[3]);
      break;
      case ACTION_CAPTURE:
      rslt = check_capture_action(unit, unit2, aunits[0]);
      break;
      case ACTION_DETONATE:
      rslt = check_detonate_action(unit, unit2, a[0], a[1], a[2]);
      break;
      case ACTION_PRODUCE:
      rslt = check_produce_action(unit, unit2, a[0], a[1]);
      break;
      case ACTION_EXTRACT:
      rslt = check_extract_action(unit, unit2, a[0], a[1], a[2], a[3]);
      break;
      case ACTION_TRANSFER:
      rslt = check_transfer_action(unit, unit2, a[0], a[1], aunits[2]);
      break;
      case ACTION_DEVELOP:
      rslt = check_develop_action(unit, unit2, a[0]);
      break;
      case ACTION_TOOL_UP:
      rslt = check_toolup_action(unit, unit2, a[0]);
      break;
      case ACTION_CREATE_IN:
      rslt = check_create_in_action(unit, unit2, a[0], aunits[1]);
      break;
      case ACTION_CREATE_AT:
      rslt = check_create_at_action(unit, unit2, a[0], a[1], a[2], a[3]);
      break;
      case ACTION_BUILD:
      rslt = check_build_action(unit, unit2, aunits[0]);
      break;
      case ACTION_REPAIR:
      rslt = check_repair_action(unit, unit2, aunits[0]);
      break;
      case ACTION_DISBAND:
      rslt = check_disband_action(unit, unit2);
      break;
      case ACTION_TRANSFER_PART:
      rslt = check_transfer_part_action(unit, unit2, a[0], aunits[1]);
      break;
      case ACTION_CHANGE_TYPE:
      rslt = check_change_type_action(unit, unit2, a[0]);
      break;
      case ACTION_CHANGE_SIDE:
      rslt = check_change_side_action(unit, unit2, asides[0]);
      break;
      case ACTION_ALTER_TERRAIN:
      rslt = check_alter_cell_action(unit, unit2, a[0], a[1], a[2]);
      break;
      case ACTION_ADD_TERRAIN:
      rslt = check_add_terrain_action(unit, unit2, a[0], a[1], a[2], a[3]);
      break;
      case ACTION_REMOVE_TERRAIN:
      rslt = check_remove_terrain_action(unit, unit2, a[0], a[1], a[2], a[3]);
      break;
      case NUMACTIONTYPES:
      /* should be error */
      break;
    }
    numsoundplays = 0; /* kind of a hack */
    if (!valid(rslt)) {
      if (unit->plan) {
          unit->plan->lastaction = *action;
          unit->plan->lastresult = rslt;
      }
      Dprintf("%s action %s can't be done, result is %s\n",
            unit_desig(unit), action_desig(action), hevtdefns[rslt].name);
      return rslt;
    }
    if (g_action_notices() != lispnil)
      notify_action(unit, action);
    if (g_action_movies() != lispnil)
      play_action_movies(unit, action);
    switch (actiondefns[(int) action->type].typecode) {
      case ACTION_NONE:
      rslt = do_none_action(unit, unit2);
      break;
      case ACTION_MOVE:
      rslt = do_move_action(unit, unit2, a[0], a[1], a[2]);
      break;
      case ACTION_ENTER:
      rslt = do_enter_action(unit, unit2, aunits[0]);
      break;
      case ACTION_ATTACK:
      rslt = do_attack_action(unit, unit2, aunits[0], a[1]);
      break;
      case ACTION_OVERRUN:
      rslt = do_overrun_action(unit, unit2, a[0], a[1], a[2], a[3]);
      break;
      case ACTION_FIRE_AT:
      rslt = do_fire_at_action(unit, unit2, aunits[0], a[1]);
      break;
      case ACTION_FIRE_INTO:
      rslt = do_fire_into_action(unit, unit2, a[0], a[1], a[2], a[3]);
      break;
      case ACTION_CAPTURE:
      rslt = do_capture_action(unit, unit2, aunits[0]);
      break;
      case ACTION_DETONATE:
      rslt = do_detonate_action(unit, unit2, a[0], a[1], a[2]);
      break;
      case ACTION_PRODUCE:
      rslt = do_produce_action(unit, unit2, a[0], a[1]);
      break;
      case ACTION_EXTRACT:
      rslt = do_extract_action(unit, unit2, a[0], a[1], a[2], a[3]);
      break;
      case ACTION_TRANSFER:
      rslt = do_transfer_action(unit, unit2, a[0], a[1], aunits[2]);
      break;
      case ACTION_DEVELOP:
      rslt = do_develop_action(unit, unit2, a[0]);
      break;
      case ACTION_TOOL_UP:
      rslt = do_toolup_action(unit, unit2, a[0]);
      break;
      case ACTION_CREATE_IN:
      rslt = do_create_in_action(unit, unit2, a[0], aunits[1]);
      break;
      case ACTION_CREATE_AT:
      rslt = do_create_at_action(unit, unit2, a[0], a[1], a[2], a[3]);
      break;
      case ACTION_BUILD:
      rslt = do_build_action(unit, unit2, aunits[0]);
      break;
      case ACTION_REPAIR:
      rslt = do_repair_action(unit, unit2, aunits[0]);
      break;
      case ACTION_DISBAND:
      rslt = do_disband_action(unit, unit2);
      break;
      case ACTION_TRANSFER_PART:
      rslt = do_transfer_part_action(unit, unit2, a[0], aunits[1]);
      break;
      case ACTION_CHANGE_TYPE:
      rslt = do_change_type_action(unit, unit2, a[0]);
      break;
      case ACTION_CHANGE_SIDE:
      rslt = do_change_side_action(unit, unit2, asides[0]);
      break;
      case ACTION_ALTER_TERRAIN:
      rslt = do_alter_cell_action(unit, unit2, a[0], a[1], a[2]);
      break;
      case ACTION_ADD_TERRAIN:
      rslt = do_add_terrain_action(unit, unit2, a[0], a[1], a[2], a[3]);
      break;
      case ACTION_REMOVE_TERRAIN:
      rslt = do_remove_terrain_action(unit, unit2, a[0], a[1], a[2], a[3]);
      break;
      case NUMACTIONTYPES:
      /* should be error */
      break;
    }
    Dprintf("%s action %s result is %s, %d acp left\n",
          unit_desig(unit), action_desig(action), hevtdefns[rslt].name,
          (unit->act ? unit->act->acp : -9999));
    if (unit->plan) {
      unit->plan->lastaction = *action;
      unit->plan->lastresult = rslt;
    }
    /* Report the result to the unit's owner. */
    if (unit->side && side_has_display(unit->side)) {
      update_action_result_display(unit->side, unit, rslt, TRUE);
    }
    /* Show other sides that some action has occurred. */
    update_side_display_all_sides(unit->side, TRUE);
#if 0 /* doesn't do anything yet */
    /* If of a type that might be spotted if it does anything, check
       each side to see if they notice.  Note that the type is from
       *before* the action, not after (some actions may cause type
       changes). */
    if (u_spot_action(u)
      && !g_see_all()
      && !u_see_always(u)
      && in_area(unit->x, unit->y)) {
      Side *side2;
      for_all_sides(side2) {
          if (cover(side2, unit->x, unit->y) > 0) {
            /* (should call some sort of "glimpsing" routine) */
          }
      }
    }
#endif
    /* Check on any scorekeepers that run after each action.  Note
       that one or more sides may be out of the game after this
       returns. */
    if (any_post_action_scores)
      check_post_action_scores();
    /* Return success/failure so caller can use. */
    return rslt;
}

static int pattern_matches_action(Obj *pattern, Unit *unit, Action *action);
static void action_desc_from_list(Side *side, Obj *lis, Unit *unit, Action *action, char *buf);

static void
notify_action(Unit *unit, Action *action)
{
    int found = FALSE;
    char *atypename, abuf[BUFSIZE];
    Obj *rest, *head, *pat, *msgdesc;

    atypename = actiondefns[(int) action->type].name;
    for_all_list(g_action_notices(), rest) {
      head = car(rest);
      if (!consp(head)) {
          run_warning("Non-list in action-notices");
          continue;
      }
      pat = car(head);
      if (symbolp(pat) && strcmp(c_string(pat), atypename) == 0) {
          found = TRUE;
          break;
      }
      if (consp(pat)
          && symbolp(car(pat))
          && strcmp(c_string(car(pat)), atypename) == 0
          && pattern_matches_action(cdr(pat), unit, action)) {
          found = TRUE;
          break;
      }
    }
    /* If we have a match, do something with it. */
    if (found) {
      msgdesc = cadr(head);
      if (stringp(msgdesc)) {
          strcpy(abuf, c_string(msgdesc));
      } else {
          action_desc_from_list(unit->side, msgdesc, unit, action, abuf);
      }
      notify(unit->side, "%s", abuf);
    }
}

static int
pattern_matches_action(Obj *parms, Unit *unit, Action *action)
{
    Obj *head;
    Unit *actee;

    head = car(parms);
    if (!(symbolp(head) && strcmp(c_string(head), u_type_name(unit->type)) == 0)
       || match_keyword(head, K_USTAR)
       || (symbolp(head)
           && boundp(head)
           && ((symbolp(symbol_value(head))
                && strcmp(c_string(symbol_value(head)), u_type_name(unit->type)) == 0)
               || (numberp(symbol_value(head))
                   && c_number(symbol_value(head)) == unit->type))))
      return FALSE;
    parms = cdr(parms);
    head = car(parms);
    if (action->actee == unit->id)
      actee = unit;
    else
      actee = find_unit(action->actee);
    if (!(symbolp(head) && strcmp(c_string(head), u_type_name(actee->type)) == 0)
       || match_keyword(head, K_USTAR)
       || (symbolp(head)
           && boundp(head)
           && ((symbolp(symbol_value(head))
                && strcmp(c_string(symbol_value(head)), u_type_name(actee->type)) == 0)
               || (numberp(symbol_value(head))
                   && c_number(symbol_value(head)) == actee->type))))
      return FALSE;
    /* (should test result also) */
    return TRUE;
}

static void
action_desc_from_list(Side *side, Obj *lis, Unit *unit, Action *action,
                  char *buf)
{
    int n;
    char *symname;
    Obj *rest, *item;
    Unit *actee;

    buf[0] = '\0';
    for_all_list(lis, rest) {
      item = car(rest);
      if (stringp(item)) {
          strcat(buf, c_string(item));
      } else if (symbolp(item)) {
          symname = c_string(item);
          if (strcmp(symname, "actor") == 0) {
            sprintf(buf+strlen(buf), "%s", unit_handle(side, unit));
          } else if (strcmp(symname, "actee") == 0) {
            if (action->actee == unit->id)
              actee = unit;
            else
              actee = find_unit(action->actee);
            sprintf(buf+strlen(buf), "%s", unit_handle(side, actee));
          } else {
            sprintf(buf+strlen(buf), " ??%s?? ", symname);
          }
      } else if (numberp(item)) {
          n = c_number(item);
          if (0 /* special processing */) {
          } else {
            sprintf(buf+strlen(buf), "%d", n);
          }
      } else {
          strcat(buf, " ????? ");
      }
    }
}

static void
play_action_movies(Unit *unit, Action *action)
{
    int found = FALSE;
    char *soundname;
    Obj *rest, *head, *parms, *msgdesc;

    for_all_list(g_action_movies(), rest) {
      head = car(rest);
      if (consp(head)
          && symbolp(car(head))
          && strcmp(c_string(car(head)),
                  actiondefns[(int) action->type].name) == 0) {
          found = TRUE;
          break;
      }
      if (consp(head)
          && consp(car(head))
          && symbolp(car(car(head)))
          && strcmp(c_string(car(car(head))),
                  actiondefns[(int) action->type].name) == 0) {
          parms = cdr(car(head));
          if (parms == lispnil) {
            found = TRUE;
            break;
          }
          if (((symbolp(car(parms))
               && strcmp(c_string(car(parms)),
                       u_type_name(unit->type)) == 0)
              || match_keyword(car(parms), K_USTAR)
              || (symbolp(car(parms))
                  && boundp(car(parms))
                  && ((symbolp(symbol_value(car(parms)))
                      && strcmp(c_string(symbol_value(car(parms))),
                            u_type_name(unit->type)) == 0)
                      || (numberp(symbol_value(car(parms)))
                          && c_number(symbol_value(car(parms)))
                        == unit->type)))
              )) {
            found = TRUE;
            break;
          }
          /* (should be able to match on particular action parms also) */
      }
    }
    /* If we have a match, do something with it. */
    if (found) {
      msgdesc = cadr(head);
      if (stringp(msgdesc)) {
          notify(unit->side, "%s", c_string(msgdesc));
      } else if (consp(msgdesc)
               && symbolp(car(msgdesc))
               && strcmp(c_string(car(msgdesc)), "sound") == 0
               && stringp(cadr(msgdesc))) {
          soundname = c_string(cadr(msgdesc));
          /* (should not be passing ptrs to schedule_movie) */
          schedule_movie(unit->side, "sound", soundname);
          play_movies(add_side_to_set(unit->side, NOSIDES));
      } else {
      }
    }
}

/* Basic check that unit has sufficient acp to do an action. */

int
can_have_enough_acp(Unit *unit, int acp)
{
    int u = unit->type, maxacp, minacp;

    /* Acp-independent units always have enough acp. */
    if (acp_indep(unit))
      return TRUE;

    maxacp = u_acp(u);
    if (u_acp_turn_max(u) >= 0)
      maxacp = min(maxacp, u_acp_turn_max(u));
    maxacp = (u_acp_max(u) < 0 ? maxacp : u_acp_max(u));
    minacp = u_acp_min(u);
    return (maxacp - acp >= minacp);
}

int
has_enough_acp(Unit *unit, int acp)
{
    /* Acp-independent units always have enough acp. */
    if (acp_indep(unit))
      return TRUE;

    if (unit->act == NULL)
      return FALSE;
    return ((unit->act->acp - acp) >= u_acp_min(unit->type));
}

/* This is true iff the unit has enough of each sort of supply to act. */

int
has_supply_to_act(Unit *unit)
{
    int m;

    for_all_material_types(m) {
      if (unit->supply[m] < um_to_act(unit->type, m))
        return FALSE;
    }
    return TRUE;
}

/* Make the consumed acp disappear, but not go below the minimum possible. */

void
use_up_acp(Unit *unit, int acp)
{
    int oldacp, newacp, acpmin;

    /* This can sometimes be called on dead or non-acting units,
       so check first. */
    if (alive(unit) && unit->act && acp > 0) {
      oldacp = unit->act->acp;
      newacp = oldacp - acp;
      acpmin = u_acp_min(unit->type);
      unit->act->acp = max(newacp, acpmin);
      /* Maybe modify the unit's display. */
      if (oldacp != unit->act->acp) {
            update_unit_acp_display(unit->side, unit, TRUE);
      }
    }
}

/* The following is generic code. */

int
construction_possible(int u2)
{
    int u;

    for_all_unit_types(u) {
      if (uu_acp_to_create(u, u2) > 0
          && uu_tp_max(u, u2) >= uu_tp_to_build(u, u2))
        return TRUE;
    }
    return FALSE;
}

int
any_construction_possible(void)
{
    int u, u2;
    static int any_construction = -1;

    if (any_construction < 0) {
      any_construction = FALSE;
      for_all_unit_types(u) {
          for_all_unit_types(u2) {
            if (uu_acp_to_create(u, u2) > 0) {
                any_construction = TRUE;
                return any_construction;
            }
          }
      }
    }
    return any_construction;
}

/* Test if the given mtype ever is stored in individual units. */

int
storage_possible(int m)
{
      int   u;
      Side  *side;
      
      /* Test if any utype can store the mtype AND does not
      always give it to a treasury. */
      for_all_unit_types(u) {
            /* Skip utypes that cannot store m. */
            if (!um_storage_x(u, m))
                continue;
            /* This utype will give m to a treasury if it exists. */
            if (um_gives_to_treasury(u, m)) {
                  /* Test if all sides that can have this unit also
                  have a treasury that will recieve m. */
                  for_all_sides(side) {
                        if (side_has_treasury(side, m))
                            continue;
                        if (!type_allowed_on_side(u, side))
                            continue;
                        /* We found a side that can have this utype
                        and lacks a treasury for m. */
                        return TRUE;
                  }
            /* We found a utype that can store m and does not give it
            to a treasury even if it exists. */
            } else return TRUE;
      }
      return FALSE;
}

/* Test if any mtype is ever stored in individual units. */

int
any_storage_possible(void)
{
      int m;
      
      for_all_material_types(m) {
            if (storage_possible(m)) {
                  return TRUE;
            }
      }
      return FALSE;
}

/* Compose a legible description of a given action. */

char *
action_desig(Action *act)
{
    int i, slen;
    char ch, *str;

    if (act == NULL)
      return "?null action?";
    if (act->type == ACTION_NONE)
      return "[]";
    if (actiondesigbuf == NULL)
      actiondesigbuf = xmalloc(BUFSIZE);
    str = actiondesigbuf;
    sprintf(str, "[%s", actiondefns[act->type].name);
    slen = strlen(actiondefns[act->type].argtypes);
    for (i = 0; i < slen; ++i) {
      ch = (actiondefns[act->type].argtypes)[i];
      switch (ch) {
        case 'U':
          tprintf(str, " \"%s\"",
                unit_desig(find_unit(act->args[i])));
          break;
        default:
          tprintf(str, " %d", act->args[i]);
      }
    }
    if (act->actee != 0) {
      tprintf(str, " (#%d)", act->actee);
    }
    strcat(str, "]");
    return actiondesigbuf;
}

Generated by  Doxygen 1.6.0   Back to index