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

ui.c

/* Graphics support not specific to any Xconq interface.
   Copyright (C) 1992-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.  */

/* This file includes some very general graphics-related functionality
   that interfaces can (but are not required to) use.  For instance,
   the size and shapes of hex cells have been precalculated to provide
   a reasonable appearance at several magnifications.  Note that some
   of the algorithms in this file are abstracted from code that has been
   tuned and tweaked over many years, so it is strongly recommended that
   all new graphical interfaces use these. */

#include "conq.h"
#include "kpublic.h"
#include "imf.h"
#include "ui.h"

static void compute_q(void);
static int module_name_compare(CONST void *a1, CONST void *a2);
static int special_strcmp(char *str1, char *str2);
static void calc_view_misc(VP *vp);
static int blocking_utype(int u, int block);
static ImageFamily *add_default_terrain_image(ImageFamily *imf, int t);
static ImageFamily *add_default_material_image(ImageFamily *imf, int m);
static ImageFamily *add_default_unit_image(ImageFamily *imf, int u);
static ImageFamily *add_default_emblem_image(ImageFamily *imf, int s2);
static UnitView *find_unit_or_occ_view(Side *side, VP *vp, UnitView *uview,
                               int usx, int usy, int usw, int ush,
                               int sx, int sy);
static UnitView *find_unit_view_at(Side *side, VP *vp, int x, int y,
                           int sx, int sy);

/* The two games that should be always be available. */

char *first_game_name = INTRO_GAME;

char *second_game_name = STANDARD_GAME;

char *second_game_title;

/* The following magical arrays set all the sizes at each magnification. */

/* This is the basic cell size. */

short mags[NUMPOWERS] = { 1, 2, 4, 8, 16, 32, 64, 128 };

/* These give the total dimensions of each hex cell, plus the vertical
   distance center-to-center.  This is all carefully calculated to make
   the cells overlap perfectly at each different magnification, assuming
   that the icons have the right shape and size. */

short hws[NUMPOWERS] = { 1, 2, 4, 12, 24, 44, 88, 174 };
short hhs[NUMPOWERS] = { 1, 2, 4, 13, 26, 48, 96, 192 };
short hcs[NUMPOWERS] = { 1, 2, 4, 10, 20, 37, 74, 148 };

/* These numbers are half the length of a vertical side. */

short halfsides[NUMPOWERS] = { 1, 1, 2, 5, 7, 13, 26, 52 };

/* The sizes of the unit subcells.  This is available drawing area, exact
   unit icon sizes depends on what's available. */

short uhs[NUMPOWERS] = { 1, 1, 3, 8, 16, 32, 64, 128 };
short uws[NUMPOWERS] = { 1, 1, 3, 8, 16, 32, 64, 128 };

/* Widths of borders and connections (0 implies don't draw at all). */

/* Full border width. */

short bwid[NUMPOWERS] = { 0, 0, 1, 2, 3, 5, 7, 9 };

/* Half-width, for narrower inset borders. */

short bwid2[NUMPOWERS] = { 0, 0, 1, 1, 2, 3, 4, 5 };

/* Full connection width. */

short cwid[NUMPOWERS] = { 0, 0, 1, 2, 3, 5, 7, 9 };

/* Coordinates of the hex borders. */
/* Note that array has extra column so don't need to wrap index. */

short bsx[NUMPOWERS][7] = {
    { 0 },
    { 0 },
    {  2,   4,   4,  2,  0,  0,  2 },
    {  6,  12,  12,  6,  0,  0,  6 },
    { 12,  24,  24, 12,  0,  0, 12 },
    { 22,  44,  44, 22,  0,  0, 22 },
    { 44,  88,  88, 44,  0,  0, 44 },
    { 87, 174, 174, 87,  0,  0, 87 }
};
short bsy[NUMPOWERS][7] = {
    { 0 },
    { 0 },
    {  0,   0,   4,   4,   4,  0,  0 },
    {  0,   3,  10,  13,  10,  3,  0 },
    {  0,   6,  20,  26,  20,  6,  0 },
    {  0,  11,  37,  48,  37, 11,  0 },
    {  0,  21,  75,  96,  75, 21,  0 },
    {  0,  44, 148, 192, 148, 44,  0 }
};

short ibsx[NUMPOWERS][7] = {
    { 0 },
    { 0 },
    {  1,   3,   4,  3,  1,  0,  1 },
    {  3,  10,  13, 10,  3,  0,  3 },
    {  6,  20,  26, 20,  6,  0,  6 },
    { 11,  37,  48, 37, 11,  0, 11 },
    { 22,  74,  96, 74, 22,  0, 22 },
    { 0 }
};
short ibsy[NUMPOWERS][7] = {
    { 0 },
    { 0 },
    {  0,   0,   1,   2,   2,  1,  0 },
    {  0,   0,   3,   6,   6,  3,  0 },
    {  0,   0,   6,  12,  12,  6,  0 },
    {  0,   0,  11,  22,  22, 11,  0 },
    {  0,   0,  22,  44,  44, 22,  0 },
    { 0 }
};

/* Coords of middles of each hex border (half a connection, basically). */
 
short lsx[NUMPOWERS][6] = {
    { 0 },
    { 0 },
    {  1,  2,   1,  -1,  -2,  -1 },
    {  3,  6,   3,  -3,  -6,  -3 },
    {  6, 12,   6,  -6, -12,  -6 },
    { 11, 22,  11, -11, -22, -11 },
    { 22, 44,  22, -22, -44, -22 },
    { 44, 87,  44, -44, -87, -44 }
};
short lsy[NUMPOWERS][6] = {
    { 0 },
    { 0 },
    {  -2,  0,   2,   2,   0,  -2 },
    {  -5,  0,   5,   5,   0,  -5 },
    {  -9,  0,   9,   9,   0,  -9 },
    { -18,  0,  18,  18,   0, -18 },
    { -36,  0,  36,  36,   0, -36 },
    { -74,  0,  74,  74,   0, -74 }
};

/* Isometric versions. */

short ilsx[NUMPOWERS][6] = {
    { 0 },
    { 0 },
    {  0,  2,  2,  0,  -2,  -2 },
    {  0,  5,  5,  0,  -5,  -5 },
    {  0, 10, 10,  0, -10, -10 },
    {  0, 19, 19,  0, -19, -19 },
    {  0, 38, 38,  0, -38, -38 },
    { 0 }
};
short ilsy[NUMPOWERS][6] = {
    { 0 },
    { 0 },
    {  -1,  -1,  1,  1,  1,  -1 },
    {  -3,  -2,  1,  3,  1,  -2 },
    {  -6,  -3,  3,  6,  3,  -3 },
    { -11,  -5,  5, 11,  5,  -5 },
    { -22, -10, 10, 22, 10, -10 },
    { 0 }
};

short qx[NUMPOWERS][7], qy[NUMPOWERS][7];

static int q_computed;

int extracells = 3;

/* The traditional direction characters. */

char *dirchars = "ulnbhy";

/* The image family for regions that are not yet discovered. */

ImageFamily *unseen_image = NULL;

char *unitchars = NULL;
char *terrchars = NULL;
char unseen_char_1;
char unseen_char_2;

ImageFamily **recorded_imfs;

int num_recorded_imfs;

int max_recorded_imfs;

void (*imf_describe_hook)(Side *side, Image *img);

/* Machinery to find and list all the games that should be listed as
   choices for the user. We don't actually scan library folders
   looking for all possible game designs therein, that would pick up
   experimental games and modules that are only for other modules'
   use. */

Module **possible_games = NULL;

int numgames = 0;

/* The comparison function for the game list puts un-formally-named
   modules at the end, plus the default sorting puts initial-lowercased
   names after uppercased ones. */

static int
module_name_compare(CONST void *a1, CONST void *a2)
{
    char *title1, *title2, *basetitle1, *basetitle2;
    Module *mp1, *mp2, *base1, *base2;
    int rslt;

    mp1 = *((Module **) a1);
    mp2 = *((Module **) a2);
    title1 = (!empty_string(mp1->title) ? mp1->title : mp1->name);
    title2 = (!empty_string(mp2->title) ? mp2->title : mp2->name);
    basetitle1 = title1;
    if ((base1 = find_game_module(mp1->basemodulename)) != NULL)
      basetitle1 = (!empty_string(base1->title) ? base1->title : base1->name);
    basetitle2 = title2;
    if ((base2 = find_game_module(mp2->basemodulename)) != NULL)
      basetitle2 = (!empty_string(base2->title) ? base2->title : base2->name);
    rslt = special_strcmp(basetitle1, basetitle2);
    if (rslt != 0)
      return rslt;
    if (mp1 == base2)
      return (-1);
    if (mp2 == base1)
      return 1;
    return special_strcmp(title1, title2);
}

static int
special_strcmp(char *str1, char *str2)
{
    if (strcmp(str1, second_game_title) == 0) {
      if (strcmp(str2, second_game_title) == 0)
        return 0;
      else
        return (-1);
    } else {
      if (strcmp(str2, second_game_title) == 0)
        return 1;
      else
        return strcmp(str1, str2);
    }
}

static int max_possible_games;

void
collect_possible_games(void)
{
    int len;
    char *modulename = NULL, *modulecontents = NULL;
    Obj *lis;
    Module *module, *basemodule;
    FILE *fp;
    int startline = 0, endline = 0;

    if (numgames == 0 && numutypes == 0 /* !game_already_loaded() */) {
      len = 0;
      lis = lispnil;
      fp = open_library_file("game.dir");
      if (fp != NULL) {
          lis = read_form(fp, &startline, &endline);
          if (consp(lis)) {
            len = length(lis);
          } else {
            init_warning("Game directory has bad format, no games found");
          }
          fclose(fp);
      }
      max_possible_games = 2 + len * 2;
      /* Make enough room to record all the possible games. */
      possible_games =
        (Module **) xmalloc(max_possible_games * sizeof(Module *));
      /* Collect the intro and standard game modules and put at head
         of list. */
      module = get_game_module(first_game_name);
      add_to_possible_games(module);
      module = get_game_module(second_game_name);
      add_to_possible_games(module);
      second_game_title =
        (!empty_string(module->title) ? module->title : module->name);
      for (; lis != lispnil; lis = cdr(lis)) {
          if (!(symbolp(car(lis)) || stringp(car(lis)))) {
            init_warning("Bad name in game dir list, ignoring");
            continue;
          }
          modulename = c_string(car(lis));
          if (modulename != NULL) {
            module = get_game_module(modulename);
            module->contents = modulecontents;
            add_to_possible_games(module);
            if (module->basemodulename != NULL) {
                basemodule = get_game_module(module->basemodulename);
                add_to_possible_games(basemodule);
            }
          }
      }
      if (numgames > 1) {
          /* Sort all but the first game into alphabetical order
             by displayed name. */
          qsort(&(possible_games[1]), numgames - 1, sizeof(Module *),
              module_name_compare);
      }
    }
}

/* Load a game's description and add it to the list of games. */

void
add_to_possible_games(Module *module)
{
    int i;

    if (module != NULL) {
      if (load_game_description(module)) {
          /* It might be that the module description supplies the real name,
             and that the module already exists. (work on this) */
          /* Don't add duplicate modules. */
          for (i = 0; i < numgames; ++i) {
            if (possible_games[i] == module)
              return;
          }
          if (numgames < max_possible_games) {
            possible_games[numgames++] = module;
          }
      }
    }
}

/* Choose and return a reasonable location for map displays to start out
   centered on. */

void
pick_a_focus(Side *side, int *xp, int *yp)
{
    int tmpx, tmpy, dist, closest = area.maxdim;
    Unit *unit, *closestunit = NULL;

    /* Explicit setting overrides any guesses. */
    if (in_area(side->init_center_x, side->init_center_y)) {
      *xp = side->init_center_x;  *yp = side->init_center_y;
      return;
    }
    if (side->startx < 0 || side->starty < 0)
      calc_start_xy(side);
    if (side->startx < 0 || side->starty < 0) {
      *xp = area.width / 2 - area.height / 4;  *yp = area.halfheight;
    } else {
      tmpx = side->startx;  tmpy = side->starty;
      /* Rescan the units to find a closest one. */
      for_all_side_units(side, unit) {
          if (in_play(unit)) {
            /* If already got one right there, just return. */
            if (unit->x == tmpx && unit->y == tmpy) {
                *xp = tmpx;  *yp = tmpy;
                return;
            } else {
                dist = distance(unit->x, unit->y, tmpx, tmpy);
                if (dist < closest) {
                  closest = dist;
                  closestunit = unit;
                }
            }
          }
      }
      if (closestunit != NULL) {
          /* Return the position of the unit closest to the avg position. */
          *xp = closestunit->x;  *yp = closestunit->y;
      } else {
          *xp = tmpx;  *yp = tmpy;
      }
    }
}

int
num_active_displays(void)
{
    int n = 0;
    Side *side;

    for_all_sides(side) {
      if (active_display(side))
        ++n;
    }
    return n;
}

/* Compute positions at each hex corner, slightly inset. */ 

static void
compute_q(void)
{
    int d, p, w;

    for (p = 0; p < NUMPOWERS; ++p) {
      if (p < 2)
        continue;
      w = bwid[p] + 1;
      for_all_directions(d) {
          qx[p][d] = bsx[p][d] + ((hws[p] - 2 * bsx[p][d]) * w) / (2 * mags[p]);
          qy[p][d] = bsy[p][d] + ((hhs[p] - 2 * bsy[p][d]) * w) / (2 * mags[p]);
      }
      qx[p][NUMDIRS] = qx[p][0];
      qy[p][NUMDIRS] = qy[p][0];
    }
}

/* Viewport handling. */

VP *
new_vp(void)
{
    int t, thickest;
    VP *vp;

    if (!q_computed) {
      compute_q();
      q_computed = TRUE;
    }
    vp = (VP *) xmalloc(sizeof(VP));
    vp->draw_aux_terrain = (short *) xmalloc(numttypes * sizeof(short));
    /* View at a 90 degree angle by default. */
    vp->angle = 90;
    /* No vertical exaggeration by default. */
    vp->vertscale = 1;
    /* For isometric views, start out looking northeast. */
    vp->isodir = NORTHEAST;
    vp->cellwidth = area.cellwidth;
    /* If the cellwidth is not reasonable for drawing elevations, use
       an approximation based on the range of elevations and terrain
       thicknesses. */
    if (vp->cellwidth <= 1) {
      thickest = 0;
      for_all_terrain_types(t) {
          if (t_thickness(t) > thickest)
            thickest = t_thickness(t);
      }
      vp->cellwidth = ((area.maxelev + thickest) - area.avgelev) / 2;
    }
    if (vp->cellwidth < 1)
      vp->cellwidth = 1;
    set_contour_interval(vp, 0);
    return vp;
}

/* xform_cell has been separated into two functions, a core that does
   the xform calculations and a shell that adds on the vertical
   offset. This makes it possible to bypass the vertical offset by
   calling xform_cell_flat directly. */

/* Given a viewport and a cell, figure out where its UL corner will be. */

void
xform_cell(VP *vp, int x, int y, int *sxp, int *syp)
{
    int xw, elev, offset;

    /* First call the core function. */
    xform_cell_flat(vp, x, y, sxp, syp);

    /* Then add vertical offset if drawing at an angle. */
    if (vp->angle != 90 || vp->isometric) {
      xw = wrapx(x);
      elev = (elevations_defined() ? elev_at(xw, y) : 0) - area.avgelev;
      /* Exaggerate the vertical scale if requested. */
      elev *= vp->vertscale;
      offset = (elev * vp->hh) / vp->cellwidth;
      *syp -= offset;
    }
}

void
xform_cell_top(VP *vp, int x, int y, int *sxp, int *syp)
{
    int xw, elev, offset;

    /* First call the core function. */
    xform_cell_flat(vp, x, y, sxp, syp);

    /* Then add vertical offset if drawing at an angle. */
    if (vp->angle != 90 || vp->isometric) {
      xw = wrapx(x);
      elev = (elevations_defined() ? elev_at(xw, y) : 0) - area.avgelev;
      /* We see the top of the terrain in the cell, not the bottom. */
      elev += t_thickness(terrain_at(xw, y));
      /* Exaggerate the vertical scale if requested. */
      elev *= vp->vertscale;
      offset = (elev * vp->hh) / vp->cellwidth;
      *syp -= offset;
    }
}

/* Core calculation for xform_cell without vertical offset. */ 

void
xform_cell_flat(VP *vp, int x, int y, int *sxp, int *syp)
{
    int xnw;

#if 0
    /* Mac background redrawing uses out-of-area cell positions up to
       8 cells east of the area edge! */
    if (!(in_area(x, y) || (in_area(x - 8, y) && vp->wide_viewport))) {
      /* Always die on this, indicates bugs that must be fixed. */
      run_error("attempting to xform %d,%d", x, y);
      return;
    }
#endif
    if (vp->isometric) {
      /* The isometric case is funky in that each view direction
         seems to need a different formula.  There may be some
         unifying form that works for all directions, but I don't
         know what it might look like. */
      switch (vp->isodir) {
        case NORTHEAST:
          *sxp = x * vp->hch + (vp->hh - vp->hch);
          *syp = (((area.height - 1 - y) * vp->hw) / 2
                + ((area.width - 1 - x) * vp->hw) / 4
                - (area.halfheight * vp->hw) / 4);
          break;
        case EAST:
          *sxp = (area.height - 1 - y) * vp->hch;
          *syp = (((area.halfheight + area.width) * vp->hw) / 2
                - ((x * vp->hw) / 2 + ((y + area.halfheight) * vp->hw) / 4)
                - vp->hw / 2);
          break;
        case SOUTHEAST:
          *sxp = ((area.width - 1 - x + area.height - 1 - y) * vp->hch
                + (vp->hh - vp->hch));
          *syp = ((area.width - 1 - x + y) * vp->hw) / 4;
          break;
        case SOUTHWEST:
          *sxp = (area.width - 1 - x) * vp->hch + (vp->hh - vp->hch);
          *syp = (y * vp->hw) / 2 + (x * vp->hw) / 4;
          break;
        case WEST:
          *sxp = y * vp->hch;
          *syp = (x * vp->hw) / 2 + ((y - area.halfheight) * vp->hw) / 4;
          if (area.xwrap)
            *syp += (area.halfheight * vp->hw) / 4;
          break;
        case NORTHWEST:
          *sxp = (x + y) * vp->hch + (vp->hh - vp->hch);
          *syp = ((area.height - 1 - y + x) * vp->hw) / 4;
          break;
        default:
          case_panic("view direction", vp->isodir);
          break;
      }
      /* Make viewport-relative, works the same for every view direction. */
      *sxp -= vp->sx;  *syp -= vp->sy;
      return;
    }
    /* Traditional overhead transformation. */
    if (vp->wide_viewport) {
      /* This version "unwraps" x, currently appropriate for Mac
         interface. */
      xnw = x;
      if (area.xwrap
          && between(area.width - y / 2, xnw, area.width - 1)
          && vp->sx < (vp->totsh - vp->sy) / 2
          )
        xnw -= area.width;

      *sxp = xnw * vp->hw + (y * vp->hw) / 2 - vp->sx;
    } else {
      /* This calculation is currently appropriate for the Unix
         interface. */
      /* Compute the scaled x. */
      *sxp = x * vp->hw + (y * vp->hw) / 2 - vp->sx;
      /* If the world is cylindrical, then we want to come up with a
         transformed point that is in the viewport if possible;
         either add or subtract the scaled width of the world if
         that will help. */
      if (area.xwrap) {
          if (*sxp < - vp->hw
            && between(0, *sxp + (area.width * vp->hw), vp->pxw))
            *sxp += area.width * vp->hw;
          if (*sxp > vp->pxw
            && between(0, *sxp - (area.width * vp->hw), vp->pxw))
            *sxp -= area.width * vp->hw;
      }
    }
    /* Compute the scaled y.  Screen coords for y run downwards, so we
       subtract. */
    *syp = (vp->totsh - (vp->hh + y * vp->hch)) - vp->sy;
}

/* Similarly, but allowing 1/1000ths of cells as input.  Note that
   since .001 is at the "bottom" of the cell as it appears on the
   screen, we must subtract the fraction from 1000, similarly to how
   we do the main y value. */

void
xform_cell_fractional(VP *vp, int x, int y, int xf, int yf, int *sxp, int *syp)
{
    xform_cell(vp, x, y, sxp, syp);
    *sxp += (xf * vp->hw) / 1000;  *syp += ((1000 - yf) * vp->hch) / 1000;
}

/* Same as xform_cell_fractional but bypasses vertical offset. */

void
xform_cell_fractional_flat(VP *vp, int x, int y, int xf, int yf,
                     int *sxp, int *syp)
{
    xform_cell_flat(vp, x, y, sxp, syp);
    *sxp += (xf * vp->hw) / 1000;  *syp += ((1000 - yf) * vp->hch) / 1000;
}

void
xform_unit(VP *vp, Unit *unit, int *sxp, int *syp, int *swp, int *shp)
{
    int num = 0, n = -1, sq, sx, sy, sx1, sy1, sw1, sh1;
    int x = unit->x, y = unit->y;
    Unit *unit2;

    if (unit->transport == NULL) {
      xform_cell(vp, x, y, &sx, &sy);
      /* Adjust to the unit box within the cell. */
      sx += (vp->hw - vp->uw) / 2;
      if (vp->isometric)
        sy += (vp->hw / 2 - vp->uh);
      else
        sy += (vp->hh - vp->uh) / 2;
      /* Figure out our position in this cell's stack. */
      for_all_stack(x, y, unit2) {
          /* (should only count units visible to a given side) */
          if (unit == unit2)
            n = num;
          ++num;
      }
      if (n < 0) {
          run_warning("xform_unit weirdness with %s", unit_desig(unit));
          *sxp = *syp = 0;
          *swp = *shp = 1;
          return;
      }
      if (num <= 1) {
          sq = 1;
      } else if (num <= 4) {
          sq = 2;
      } else if (num <= 16) {
          sq = 4;
      } else if (num <= 256) {
          sq = 8;
      } else {
          /* This is room for 65,536 units in a stack. */
          sq = 16;
      }
      /* Tighten up positions if using big icons. */
      if (g_bigicons()) {
            *swp = vp->hw / sq;  
            *shp = vp->hw / sq;
            sx -= (vp->hw - vp->uw) / 2;
            sy -= (vp->hw - vp->uw) / (2 * sq);
            *sxp = sx + *swp * (n / sq) * 3 / 4;
            *syp = sy + *shp * (n % sq) * 3 / 4;
      } else {
            *swp = vp->uw / sq;  
            *shp = vp->uh / sq;
            *sxp = sx + *swp * (n / sq);  
            *syp = sy + *shp * (n % sq);
      }
    } else {
      /* Go up the transport chain to get the bounds for this unit. */
      xform_unit(vp, unit->transport, &sx1, &sy1, &sw1, &sh1);
      xform_occupant(vp, unit->transport, unit, sx1, sy1, sw1, sh1, sxp, syp, swp, shp);
    }
}

void
xform_unit_view(Side *side, VP *vp, UnitView *uview,
            int *sxp, int *syp, int *swp, int *shp)
{
    int num = 0, n = -1, sq, sx, sy, x, y;
    Unit *unit;
    UnitView *uview2;

    x = uview->x;  y = uview->y;
    xform_cell(vp, x, y, &sx, &sy);
    /* Adjust to the unit box within the cell. */
    sx += (vp->hw - vp->uw) / 2;
    if (vp->isometric)
      sy += (vp->hw / 2 - vp->uh);
    else
      sy += (vp->hh - vp->uh) / 2;
    /* Figure out our position in this cell's stack. */
    for_all_view_stack(side, x, y, uview2) {
      unit = view_unit(uview2);
      /* Only count views of top-level units (occupants may have
         view objects at this location also). */
      if (unit == NULL || unit->transport == NULL) {
          if (uview2 == uview)
            n = num;
          ++num;
      }
    }
    if (n < 0) {
      run_warning("xform_unit_view weirdness with");
      *sxp = *syp = 0;
      *swp = *shp = 1;
      return;
    }
    if (num <= 1) {
      sq = 1;
    } else if (num <= 4) {
      sq = 2;
    } else if (num <= 16) {
      sq = 4;
    } else if (num <= 256) {
      sq = 8;
    } else {
      /* This is room for 65,536 unit views in a stack. */
      sq = 16;
    }
    /* Tighten up positions if using big icons. */
    if (g_bigicons()) {
      *swp = vp->hw / sq;  
      *shp = vp->hw / sq;
      sx -= (vp->hw - vp->uw) / 2;
      sy -= (vp->hw - vp->uw) / (2 * sq);
      *sxp = sx + *swp * (n / sq) * 3 / 4;
      *syp = sy + *shp * (n % sq) * 3 / 4;
    } else {
      *swp = vp->uw / sq;  
      *shp = vp->uh / sq;
      *sxp = sx + *swp * (n / sq);  
      *syp = sy + *shp * (n % sq);
    }
}

void
xform_unit_self(VP *vp, Unit *unit, int *sxp, int *syp, int *swp, int *shp)
{
    int sx1, sy1, sw1, sh1;

    if (unit->transport == NULL) {
      if (unit->occupant == NULL) {
          xform_unit(vp, unit, sxp, syp, swp, shp);
      } else {
          xform_unit(vp, unit, &sx1, &sy1, &sw1, &sh1);
          xform_occupant(vp, unit, unit, sx1, sy1, sw1, sh1, sxp, syp, swp, shp);
      }
    } else {
      xform_unit(vp, unit->transport, &sx1, &sy1, &sw1, &sh1);
      xform_occupant(vp, unit->transport, unit, sx1, sy1, sw1, sh1, sxp, syp, swp, shp);
    }
}

/* Given a unit, compute its actual bounding box (as opposed to the
   box that includes both its and its occupants). */

int
xform_unit_self_view(Side *side, VP *vp, Unit *unit,
                 int *sxp, int *syp, int *swp, int *shp)
{
    int sx1, sy1, sw1, sh1;
    UnitView *uview = NULL;

    if (unit->transport == NULL) {
      for_all_view_stack(side, unit->x, unit->y, uview) {
          if (view_unit(uview) == unit)
            break;
      }
      if (uview == NULL || view_unit(uview) != unit)
        return FALSE;
      if (unit->occupant == NULL) {
          xform_unit_view(side, vp, uview, sxp, syp, swp, shp);
      } else {
          xform_unit_view(side, vp, uview, &sx1, &sy1, &sw1, &sh1);
          xform_occupant(vp, unit, unit, sx1, sy1, sw1, sh1,
                     sxp, syp, swp, shp);
      }
    } else {
      for_all_view_stack(side, unit->transport->x, unit->transport->y,
                     uview) {
          if (view_unit(uview) == unit->transport)
            break;
      }
      if (uview == NULL || view_unit(uview) != unit->transport)
        return FALSE;
      xform_unit_view(side, vp, uview, &sx1, &sy1, &sw1, &sh1);
      xform_occupant(vp, unit->transport, unit, sx1, sy1, sw1, sh1,
                   sxp, syp, swp, shp);
    }
    return TRUE;
}

void
xform_occupant(VP *vp, Unit *transport, Unit *unit,
             int sx, int sy, int sw, int sh,
             int *sxp, int *syp, int *swp, int *shp)
{
    int num = 0, n = -1, nmx, nmy;
    Unit *unit2;

#ifdef MAC        /* Until the tcltk version has closeups. */

    /* Skip transformation if using big icons. */
    if (g_bigicons()) {
      *sxp = sx;
      *syp = sy;
      *swp = sw;
      *shp = sh;
      return;
    }
    
#endif

    /* Figure out the position of this unit amongst all the occupants. */
    for_all_occupants(transport, unit2) {
      if (unit2 == unit)
        n = num;
      ++num;
    }
    if (unit == transport) {
      if (num > 0) {
          /* Transport image shrinks by half in each dimension. */
          *swp = sw / 2;  *shp = sh / 2;
      }
      /* Transport is always in the UL corner. */
      *sxp = sx;  *syp = sy;
    } else {
      if (n < 0)
        run_error("xform_occupant weirdness");
      /* Compute how the half-box will be subdivided.  Only use
         powers of two, so image scaling works better. */
      if (num <= 2) {
          nmx = 2;
      } else if (num <= 8) {
          nmx = 4;
      } else if (num <= 128) {
          nmx = 8;
      } else {
          /* This is room for 32,768 units in a stack. */
          nmx = 16;
      }
      nmy = nmx / 2;
      *swp = sw / nmx;  *shp = (sh / 2) / nmy;
      *sxp = sx + *swp * (n / nmy);  *syp = sy + sh / 2 + *shp * (n % nmy);
    }
}

/* Scale one viewport box to its position in another. */

void
scale_vp(VP *vp, VP *vp2, int *sxp, int *syp, int *swp, int *shp)
{
    *sxp = (vp2->sx * vp->hw) / vp2->hw - vp->sx;
    *syp = (vp2->sy * vp->hch) / vp2->hch - vp->sy;
    *swp = (vp2->pxw * vp->hw) / vp2->hw;
    *shp = (vp2->pxh * vp->hch) / vp2->hch;
}

#if 0
void
scale_point(VP *vp, VP *vp2, int sx, int sy, int *sx2p, int *sy2p)
{
    *sx2p = ((sx + vp2->sx) * vp->hw) / vp2->hw - vp->sx;
    *sy2p = ((sy + vp2->sy) * vp->hch) / vp2->hch - vp->sy;
}
#endif

int
nearest_cell(VP *vp, int sx, int sy, int *xp, int *yp, int *xfp, int *yfp)
{
    int sxadj, syadj, sxfrac, syflipped, syfrac, tmp1;

    if (vp->isometric) {
      /* Convert to absolute scaled coordinates. */
      sx += vp->sx;  sy += vp->sy;
      switch (vp->isodir) {
        case NORTHEAST:
          sy += (area.halfheight * vp->hw) / 4;
          *xp = (sx - (vp->hh - vp->hch)) / vp->hch;
          syadj = sy - ((area.width - 1 - *xp) * vp->hw) / 4;
          *yp = (area.height - 1) - (2 * syadj) / vp->hw;
          break;
        case EAST:
          sy += vp->hw / 2;
          *yp = (area.height - 1) - sx / vp->hch;
          tmp1 = ((area.halfheight + area.width) * vp->hw) / 2;
          *xp = ((tmp1 - sy - ((*yp + area.halfheight) * vp->hw) / 4) * 2) / vp->hw;
          break;
        case SOUTHEAST:
          *xp = area.width - 1 - (((sx - (vp->hh - vp->hch)) / vp->hch)
                            + ((sy * 4) / vp->hw)
                            - (area.height - 1)) / 2;
          *yp = ((sy * 4) / vp->hw) - (area.width - 1 - *xp);
          break;
        case SOUTHWEST:
          *xp = (area.width - 1) - (sx - (vp->hh - vp->hch)) / vp->hch;
          *yp = ((sy - (*xp * vp->hw) / 4) * 2) / vp->hw;
          break;
        case WEST:
          if (area.xwrap)
            sy -= (area.halfheight * vp->hw) / 4;
          *yp = sx / vp->hch;
          *xp = ((sy - ((*yp - area.halfheight) * vp->hw) / 4) * 2) / vp->hw;
          break;
        case NORTHWEST:
          *xp = ((sx + (vp->hh - vp->hch)) / vp->hch
               - (area.height - 1)
               + ((sy * 4) / vp->hw)) / 2;
          *yp = *xp - ((sy * 4) / vp->hw) + (area.height - 1);
          break;
        default:
          case_panic("view direction", vp->isodir);
          break;
      }
      return (in_area(*xp, *yp));
    }
    /* Flip the raw y and then scale to hex coords. */
    syflipped = vp->totsh - (vp->sy + sy);
    *yp = syflipped / vp->hch;
    if (yfp) {
      syfrac = syflipped - (*yp * vp->hch) - (vp->hh - vp->hch);
      *yfp = (syfrac * 1000) / vp->hch;
    }
    /* Adjust scaled x. */
    sxadj = (sx + vp->sx - (*yp * vp->hw) / 2);
    /* The division by hw below might round towards 0, so wrap
       negative numbers around to positive values.  This should only
       ever happens for cylinder areas, but doesn't hurt to just
       adjust all negative values. */

    /* Unfortunately, it does hurt. The mac-specific meridian drawing
       code must be allowed to use negative values for non-cylindrical
       worlds! */
    if (area.xwrap && sxadj < 0)
      sxadj += (2 * area.width * vp->hw);

    *xp = sxadj / vp->hw;
    if (xfp) {
      sxfrac = sxadj - (*xp * vp->hw);
      *xfp = (sxfrac * 1000) / vp->hw;
    }
    /* If the magnification of the map is large enough that the top
       and bottom edges of a hex are visibly sloping, then we have to
       take those edges into account, and accurately. */
    if ((vp->hh - vp->hch) / 2 > 1) {
      /* (should adjust according to hex boundaries correctly here) */
    }
    /* Wrap coords as usual. */
    if (area.xwrap)
      *xp = wrapx(*xp);
    DGprintf("Pixel %d,%d -> hex %d.%03d,%d.%03d\n",
           sx, sy, *xp, (xfp ? *xfp : 0), *yp, (yfp ? *yfp : 0));
    return (in_area(*xp, *yp));
}

int
nearest_boundary(VP *vp, int sx, int sy, int *xp, int *yp, int *dirp)
{
    int sx2, sy2, ydelta, hexslope;

    /* Get the nearest cell... */
    if (nearest_cell(vp, sx, sy, xp, yp, NULL, NULL)) {
      /* ... and xform it back to get the pixel coords. */ 
      xform_cell(vp, *xp, *yp, &sx2, &sy2);
      ydelta = sy - sy2;
      hexslope = (vp->hh - vp->hch) / 2;
      if (sx - sx2 > vp->hw / 2) {
          *dirp = ((ydelta < hexslope) ? NORTHEAST : (ydelta > vp->hch ? SOUTHEAST : EAST));
      } else {
          *dirp = ((ydelta < hexslope) ? NORTHWEST : (ydelta > vp->hch ? SOUTHWEST : WEST));
      }
      DGprintf("Pixel %d,%d -> hex %d,%d dir %d\n", sx, sy, *xp, *yp, *dirp);
      return TRUE;
    } else {
      return FALSE;
    }
}

Unit *
find_unit_or_occ(Side *side, VP *vp, Unit *unit, int usx, int usy,
             int usw, int ush, int sx, int sy)
{
    int usx1, usy1, usw1, ush1;
    Unit *occ, *rslt;

    /* See if the point might be over an occupant. */
    if (unit->occupant != NULL

#ifdef MAC

      && g_bigicons() != TRUE

#endif

      && (side_controls_unit(side, unit)
          /* If side can examine unit in detail it can also see its occs. */
          || side_sees_unit(side, unit)
          || vp->show_all
          || u_see_occupants(unit->type)
          || side_owns_occupant(side, unit))) {
      for_all_occupants(unit, occ) {
          xform_unit(vp, occ, &usx1, &usy1, &usw1, &ush1);
          rslt = find_unit_or_occ(side, vp, occ, usx1, usy1, usw1, ush1,
                            sx, sy);
          if (rslt)
            return rslt;
      }
    }
    /* Otherwise see if it could be the unit itself.  This has the effect of
       "giving" the transport everything in its box that is not in an occ. */
    xform_unit(vp, unit, &usx1, &usy1, &usw1, &ush1);
    if (between(usx1, sx, usx1 + usw1) && between(usy1, sy, usy1 + ush1))
      return unit;
    return NULL;
}

Unit *
find_unit_at(Side *side, VP *vp, int x, int y, int sx, int sy)
{
    int usx, usy, usw, ush;
    Unit *unit, *rslt;
    
    for_all_stack(x, y, unit) {
      xform_unit(vp, unit, &usx, &usy, &usw, &ush);
      rslt = find_unit_or_occ(side, vp, unit, usx, usy, usw, ush, sx, sy);
      if (rslt)
        return rslt;
    }
    return NULL;
}

int
nearest_unit(Side *side, VP *vp, int sx, int sy, Unit **unitp)
{
    int x, y;
    
    if (!nearest_cell(vp, sx, sy, &x, &y, NULL, NULL)) {
      *unitp = NULL;
      DGprintf("Pixel %d,%d -> outside area\n", sx, sy);
      return FALSE;
    }
    if (vp->power > 4) {
      *unitp = find_unit_at(side, vp, x, y, sx, sy);
    } else {
      *unitp = unit_at(x, y);
    }
    DGprintf("Pixel %d,%d -> unit %s\n", sx, sy, unit_desig(*unitp));
    return TRUE;
}

UnitView *
find_unit_or_occ_view(Side *side, VP *vp, UnitView *uview, int usx, int usy,
                  int usw, int ush, int sx, int sy)
{
    int usx1, usy1, usw1, ush1;

#if 0 /* until we figure this out */
    Unit *unit, *occ, *rslt;
    /* See if the point might be over an occupant. */
    if (unit->occupant != NULL

#ifdef MAC

      && g_bigicons() != TRUE

#endif

      && (side_controls_unit(side, unit)
          /* If side can examine unit in detail it can also see its occs. */
          || side_sees_unit(side, unit)
          || vp->show_all
          || u_see_occupants(unit->type)
          || side_owns_occupant(side, unit))) {
      for_all_occupants(unit, occ) {
          xform_unit(vp, occ, &usx1, &usy1, &usw1, &ush1);
          rslt = find_unit_or_occ(side, vp, occ, usx1, usy1, usw1, ush1,
                            sx, sy);
          if (rslt)
            return rslt;
      }
    }
#endif
    /* Otherwise see if it could be the unit itself.  This has the effect of
       "giving" the transport everything in its box that is not in an occ. */
    xform_unit_view(side, vp, uview, &usx1, &usy1, &usw1, &ush1);
    if (between(usx1, sx, usx1 + usw1) && between(usy1, sy, usy1 + ush1))
      return uview;
    return NULL;
}

UnitView *
find_unit_view_at(Side *side, VP *vp, int x, int y, int sx, int sy)
{
    int usx, usy, usw, ush;
    UnitView *uview, *rslt;

    x = wrapx(x);
    for_all_view_stack(side, x, y, uview) {
      xform_unit_view(side, vp, uview, &usx, &usy, &usw, &ush);
      rslt = find_unit_or_occ_view(side, vp, uview, usx, usy, usw, ush,
                             sx, sy);
      if (rslt)
        return rslt;
    }
    return NULL;
}

int
nearest_unit_view(Side *side, VP *vp, int sx, int sy, UnitView **uviewp)
{
    int x, y;
    
    if (!nearest_cell(vp, sx, sy, &x, &y, NULL, NULL)) {
      *uviewp = NULL;
      DGprintf("Pixel %d,%d -> outside area\n", sx, sy);
      return FALSE;
    }
    if (vp->power > 4) {
      *uviewp = find_unit_view_at(side, vp, wrapx(x), y, sx, sy);
    } else {
      *uviewp = unit_view_at(side, wrapx(x), y);
    }
    DGprintf("Pixel %d,%d -> ", sx, sy);
    if (DebugG) {
      if (*uviewp != NULL) {
          DGprintf("uview %d,%d,%d\n",
                 (*uviewp)->id, (*uviewp)->type, (*uviewp)->side_id);
      } else {
          DGprintf("no uview\n");
      }
    }
    return TRUE;
}

/* Return true if the cell is visible in the viewport. */

int
cell_is_visible(VP *vp, int x, int y)
{
    int sx, sy;
 
    if (!in_area(x, y))
      return FALSE;   
    xform_cell(vp, x, y, &sx, &sy);
    if (area.xwrap && sx > vp->totsw)
      sx -= vp->totsw;
    if (sx + vp->hw < 0)
      return FALSE;
    if (sx > vp->pxw) 
      return FALSE;
    if (sy + vp->hh < 0)
      return FALSE;
    if (sy > vp->pxh)
      return FALSE;
    return TRUE;
}

/* Decide whether given location is away from the edge of the map's
   window. */

int
cell_is_in_middle(VP *vp, int x, int y)
{
    int sx, sy, insetx1, insety1, insetx2, insety2;
    
    if (!in_area(x, y))
      return FALSE;   
    xform_cell(vp, x, y, &sx, &sy);
    /* Adjust to be the center of the cell, more reasonable if large. */
    sx += vp->hw / 2;  sy += vp->hh / 2;
    insetx1 = min(vp->pxw / 4, 1 * vp->hw);
    insety1 = min(vp->pxh / 4, 1 * vp->hch);
    insetx2 = min(vp->pxw / 4, 2 * vp->hw);
    insety2 = min(vp->pxh / 4, 2 * vp->hch);
    if (sx < insetx2)
      return FALSE;
    if (sx > vp->pxw - insetx2)
      return FALSE;
    if (sy < (between(2, y, area.height-3) ? insety2 : insety1))
      return FALSE;
    if (sy > vp->pxh - (between(2, y, area.height-3) ? insety2 : insety1))
      return FALSE;
    return TRUE;
}

/* Set vcx/vcy to point to the center of the view. */

void
focus_on_center(VP *vp)
{
    vp->vcy = (vp->totsh - (vp->sy + vp->pxh / 2)) / vp->hch;
    vp->vcx = vp->sx / vp->hw - (vp->vcy / 2) + (vp->pxw / vp->hch) / 2;
    /* Restrict the focus to be *inside* the area. */
    vp->vcy = limitn(1, vp->vcy, area.height - 2);
    if (area.xwrap) {
      vp->vcx = wrapx(vp->vcx);
    } else {
      vp->vcx = limitn(1, vp->vcx, area.width - 2);
      if (vp->vcx + vp->vcy < area.halfheight + 1)
        vp->vcx = area.halfheight + 1;
      if (vp->vcx + vp->vcy > area.width + area.halfheight - 1)
        vp->vcx = area.width + area.halfheight - 1;
    }
}

void
center_on_focus(VP *vp)
{
    int sx, sy, xnw = vp->vcx;

    if (vp->isometric) {
      xform_cell(vp, vp->vcx, vp->vcy, &sx, &sy);
      sx += vp->sx - vp->pxw / 2;
      sy += vp->sy - vp->pxh / 2;
    } else {
      if (area.xwrap && xnw >= (area.width - vp->vcy / 2))
        xnw -= area.width;
      /* Scale, add hex offset adjustment, translate to get left edge. */
      sx = xnw * vp->hw + (vp->vcy * vp->hw) / 2 - vp->pxw / 2 + vp->hw / 2;
      /* Scale, translate to top edge, flip. */
      sy = vp->totsh - (vp->vcy * vp->hch + vp->pxh / 2 + vp->hh / 2);
    }
    set_view_position(vp, sx, sy);
    DGprintf("Viewport 0x%x at %d,%d, focused at %d,%d\n",
           vp, vp->sx, vp->sy, vp->vcx, vp->vcy);
}

int
set_view_size(VP *vp, int w, int h)
{
    if (w < 1 || h < 1)
      run_error("Bad viewport size %dx%d", w, h);
    vp->pxw = w;  vp->pxh = h;
    calc_view_misc(vp);
    return TRUE;
}

int
set_view_position(VP *vp, int sx, int sy)
{
    vp->sx = sx;  vp->sy = sy;
    /* Clip to rational limits. */
    vp->sx = limitn(vp->sxmin, vp->sx, vp->sxmax);
    vp->sy = limitn(vp->symin, vp->sy, vp->symax);
    return TRUE;
}

/* Given a magnification power, calculate and cache the sizes within a cell,
   and the scaled size in pixels of the entire world. */

int
set_view_power(VP *vp, int power)
{
    vp->power = power;
    vp->mag = mags[power]; /* is this used?? */
    vp->hw = hws[power];  vp->hh = hhs[power];
    vp->hch = hcs[power];
    vp->uw = uws[power];  vp->uh = uhs[power];
    if (vp->angle == 30) {
      vp->hh /= 2;
      vp->hch /= 2;
    } else if (vp->angle == 15) {
      vp->hh /= 4;
      vp->hch /= 4;
    }
    calc_view_misc(vp);
    DGprintf("Viewport 0x%x power is now %d, total scaled area is %d x %d\n",
           vp, vp->power, vp->totsw, vp->totsh);
    return TRUE;
}

int
set_view_focus(VP *vp, int x, int y)
{
    if (!in_area(x, y))
      run_error("View focus of %d,%d not in area", x, y);
    vp->vcx = x;  vp->vcy = y;
    return TRUE;
}

int
set_view_angle(VP *vp, int angle)
{
    if (!(angle == 90 || angle == 30 || angle == 15)) {
      run_warning("Bad angle %d, setting to 90", angle);
      angle = 90;
    }
    vp->angle = angle;
    vp->hh = hhs[vp->power];
    vp->hch = hcs[vp->power];
    vp->uh = uhs[vp->power];
    if (vp->angle == 30) {
      vp->hh /= 2;
      vp->hch /= 2;
      vp->uh /= 2;
    } else if (vp->angle == 15) {
      vp->hh /= 4;
      vp->hch /= 4;
      vp->uh /= 4;
    }
    calc_view_misc(vp);
    DGprintf("Angle is now %d, total scaled area is %d x %d\n",
           vp->angle, vp->totsw, vp->totsh);
    return TRUE;
}

int
set_view_isometric(VP *vp, int flag, int scale)
{
    vp->isometric = flag;
    vp->vertscale = scale;
    calc_view_misc(vp);
    return TRUE;
}

int
set_view_direction(VP *vp, int dir)
{
    vp->isodir = dir;
    calc_view_misc(vp);
    return TRUE;
}

static void
calc_view_misc(VP *vp)
{
    if (vp->isometric) {
      switch (vp->isodir) {
        case NORTHEAST:
        case SOUTHEAST:
        case SOUTHWEST:
        case NORTHWEST:
          vp->totsw = area.width * vp->hch + (vp->hh - vp->hch);
          vp->totsh = (area.width * vp->hw) / 4;
          if (area.xwrap)
            vp->totsh += (area.height * vp->hw) / 2;
          else
            vp->totsh += ((area.halfheight * vp->hw) / 4) * 2;
          break;
        case EAST:
        case WEST:
          vp->totsw = area.height * vp->hch + (vp->hh - vp->hch);
          vp->totsh = (area.width * vp->hw) / 2;
          break;
        default:
          case_panic("view direction", vp->isodir);
          break;
      }
      /* (should add any adjust due to elevated cells) */
      vp->sxmin = vp->symin = 0;
    } else {
      /* Calculate and cache the width in pixels of the whole area, adding an
         an adjustment to account for the "bulge" of hexagon-shaped areas. */
      /* (but it seems the adjustment is wrong for sizing - should figure
         out why this seems necessary) */
      vp->totsw = area.width * vp->hw + hexagon_adjust(vp);
    
      /* I don't understand why this adjustment is done. It has no apparent 
         effect on either mac, xtconq or tcltk drawing. HR. */
#if 0    
      if (area.xwrap && (vp->totsw > (vp->pxw - vp->hw)))
        vp->totsw += (vp->pxw - vp->hw);
#endif

      /* Total scaled height is based on center-to-center height, plus
         an adjustment to include the bottom parts of the bottom row. */
      vp->totsh = area.height * vp->hch + (vp->hh - vp->hch);
      vp->sxmin = hexagon_adjust(vp);  vp->symin = 0;
    }
    /* Special-case wide viewports (a la Mac) for cylindrical worlds. */
    if (vp->wide_viewport && area.xwrap)
      vp->sxmax = area.width * vp->hw;
    else
      vp->sxmax = max(vp->sxmin, vp->totsw - vp->pxw);  
    vp->symax = max(vp->symin, vp->totsh - vp->pxh);
    /* Limiting sx to totsw instead of sxmax fixes the misaligned updates 
    bug at 8x8 resultion. */
    vp->sx = limitn(vp->sxmin, vp->sx, (vp->isometric ? vp->sxmax : vp->totsw));
    vp->sy = limitn(vp->symin, vp->sy, vp->symax);
}

void
free_vp(VP *vp)
{
    free(vp);
}

int
any_borders_in_dir(int x, int y, int dir)
{
    int b;

    if (!any_aux_terrain_defined())
      return FALSE;
    for_all_border_types(b) {
      /* Note that border_at tests for layer existence. */
      if (border_at(x, y, dir, b))
        return TRUE;
    }
    return FALSE;
}

/* Compute the transition between two cells with different terrain,
   returning the positioning of the transition and which of four
   subimages to use. */

int
compute_transition(Side *side, VP *vp, int x, int y, int dir,
               int *sxp, int *syp, int *swp, int *shp, int *offsetp)
{
    int t, x1, y1, t1, x2, y2, sx, sy, sw, sh, sx2, sy2;
    int trite, tleft, overrite, overleft, offset;
    int w = vp->hw, h = vp->hh, hch = vp->hch;

    if (!point_in_dir(x, y, dir, &x1, &y1))
      return FALSE;
    if (!(vp->show_all || terrain_visible(side, x1, y1)))
      return FALSE;
    t = terrain_at(x, y);
    t1 = terrain_at(x1, y1);
    /* We want to overlap the adjacent terrain into this cell if the
       terrain type is lower-numbered (meaning that sea usually
       overlaps land for instance), but not if there is a border,
       because the border itself provides the transition. */
    if (t1 >= t || any_borders_in_dir(x, y, dir))
      return FALSE;
    if (point_in_dir(x, y, right_dir(dir), &x2, &y2))
      trite = terrain_at(x2, y2);
    else
      trite = t;
    /* Overlap on the right-hand corner if the third cell's terrain is
       the same as the adjacent cell's terrain. */
    overrite = (trite == t1);
    /* Border on one side suppresses overlap. */
    if (any_borders_in_dir(x, y, right_dir(dir)))
      overrite = FALSE;
    if (point_in_dir(x, y, left_dir(dir), &x2, &y2))
      tleft = terrain_at(x2, y2);
    else
      tleft = t;
    /* Overlap on the left-hand corner if the third cell's terrain is
       the same as the adjacent cell's terrain. */
    overleft = (tleft == t1);
    /* Border on one side suppresses overlap. */
    if (any_borders_in_dir(x, y, left_dir(dir)))
      overleft = FALSE;
    xform_cell(vp, x, y, &sx, &sy);
    /* Now, given the overlaps on each side, choose the right piece of
       transition bitmap to use. */
    switch (dir) {
      case NORTHEAST:
      sx2 = sx + w / 2;  sy2 = sy;
      sw = w / 2;  sh = h - hch + 1;
      if (overrite)
        offset = 1;
      else if (overleft)
        offset = 2;
      break;
      case EAST:
      sx2 = sx + w / 2;  sy2 = sy + (h - hch) + 1;
      sw = w / 2;  sh = h - 2 * (h - hch) - 2;
      if (overrite)
        offset = 2;
      else if (overleft)
        offset = 1;
      break;
      case SOUTHEAST:
      sx2 = sx + w / 2;  sy2 = sy + hch - 1;
      sw = w / 2;  sh = h - hch + 1;
      if (overrite)
        offset = 1;
      else if (overleft)
        offset = 2;
      break;
      case SOUTHWEST:
      sx2 = sx;  sy2 = sy + hch - 1;
      sw = w / 2;  sh = h - hch + 1;
      if (overrite)
        offset = 2;
      else if (overleft)
        offset = 1;
      break;
      case WEST:
      sx2 = sx;  sy2 = sy + (h - hch) + 1;
      sw = w / 2;  sh = h - 2 * (h - hch) - 2;
      if (overrite)
        offset = 1;
      else if (overleft)
        offset = 2;
      break;
      case NORTHWEST:
      sx2 = sx;  sy2 = sy;
      sw = w / 2;  sh = h - hch + 1;
      if (overrite)
        offset = 2;
      else if (overleft)
        offset = 1;
      break;
    }
    if (overrite && overleft)
      offset = 0;
    else if (!overrite && !overleft)
      offset = 3;
    *sxp = sx2;  *syp = sy2;
    *swp = sw;  *shp = sh;
    *offsetp = offset;
    return TRUE;
}

void
compute_fire_line_segment(int sx1, int sy1, int sx2, int sy2, int i, int n,
                    int *xx, int *yy, int *dx, int *dy)
{
    /* Position one segment of a line between the locations. */
    *dx = (sx2 - sx1) / n;  *dy = (sy2 - sy1) / n;
    *xx = sx1 + ((i / 2) % n) * *dx;  *yy = sy1 + ((i / 2) % n) * *dy;
}

/* This routine can be used by the interface to place legends */

/* orient==0 :  E (horizontal) only;
   orient==1 :  E, SE, NE;
   orient==2 :  E, SE, NE, ESE, ENE, N; */

/* block==0  :  write over any unit;
   block==1  :  don't write over "city-like" units;
   block==2  :  don't write over visible units. */

void
place_feature_legends(Legend *legend, int nf, Side *side, int orient,
                  int block)
{
    int x, y, x1, y1, dx, dy, f, i, i3, j, d, ndirstotry, d1, dc;
    double dist;
    static int numdirstotry[] = { 1, 3, 6 };
    static int dirstotry[] =
    { EAST, SOUTHEAST, NORTHEAST, NORTHEAST, SOUTHEAST, EAST };
    static int da[] = { 0, -60, 60, 90, -30, 30 };
    unsigned char *auxf_layer, dmask;
    
    if (!features_defined())
      return;
    
    orient = min(orient, 2);
    ndirstotry = numdirstotry[orient];
    
    for (f = 1; f <= nf; f++) {
      legend[f-1].ox = 0;  legend[f-1].oy = 0;
      legend[f-1].dx = 0;  legend[f-1].dy = 0;
      legend[f-1].angle = 0;
      legend[f-1].dist  = -1;
    }
    
    /* Speedup: in auxf_layer we keep this information:
       the cell is unseen or hosts a blocking unit (bit 7);
       the cell has already been reached from direction id (bit id)
       [this avoids repeating the same path over and over;
      note that directions 3,4,5 zig-zag with step 3,
      so this bit is set/checked only every 3 steps.] */
    
    auxf_layer = (unsigned char *)
      malloc(area.height * area.width * sizeof(unsigned char));
    
    if (auxf_layer == NULL)
      return;
    
    for_all_cells(x, y) {
      if (terrain_seen_at(side, x, y) == NONTTYPE ||
          blocking_utype(utype_seen_at(side, x, y), block)) {
          aset(auxf_layer, x, y, '\200');
      } else {
          aset(auxf_layer, x, y, '\0');
      }
    }
    
    for_all_cells(x, y) {
      f = raw_feature_at(x, y);
      if (f < 1 || f > nf)
        continue;

      for (j = 0; j < ndirstotry; j++) {
          dmask = '\001' << j;
          d = dirstotry[j];
          d1 = ((j < 3) ? d : left_dir(left_dir(d)));
          x1 = x;  y1 = y;
          dx = dy = 0;
          i3 = i = 0;
          dist = 0;
          while (raw_feature_at(x1, y1) == f &&
               !(aref(auxf_layer, x1, y1) &
                 ((j < 3 || !i3) ? ('\200' | dmask) : '\200'))) {
            if (dist > legend[f-1].dist && (j < 3 || !i3)) {
                legend[f-1].ox = x;  legend[f-1].oy = y;
                legend[f-1].dx = dx;  legend[f-1].dy = dy;
                legend[f-1].angle = da[j];
                legend[f-1].dist  = dist;
            }
            if (j < 3 || !i3) {
                auxf_layer[area.width * y1 + x1] |= dmask;
            }
            dc = ((i3 == 1) ? d1 : d);
            dx += dirx[dc];
            x1 = wrapx(x1 + dirx[dc]);
            dy += diry[dc];
            y1 += diry[dc];
            dist += ((j < 3) ? 1.0 : (i3 ? 0.5 * 1.73205080756888 : 0.0));
            ++i;
            i3 = i % 3;
          }
      }
    }
    
    free(auxf_layer);
}

static int
blocking_utype(int u, int block)
{
    if (u == NONUTYPE || block == 0)
      return 0;
    if (block > 1)
      return 1;
    /* block==1:  only visible see-always unmovable units */
    return ((u_already_seen(u) > 99 || u_see_always(u)) && !mobile(u));
}

/* Set the spacing of the meridians of latitude and longitude. */

void
set_meridian_interval(VP *vp, int interval)
{
    vp->meridian_interval = interval;
    /* Don't want to compute the lat/lon label spacing yet, we don't
       necessarily have the window's size recorded in the viewport. */
    vp->lat_label_lon_interval = vp->lon_label_lat_interval = 0;
}

/* Compute the location of meridians desired for the given viewport
   and render the lines and labels using the two callback
   functions. */

/* (should attempt to draw the lines all the way to the edge of
   the area, but formulas more complicated) */

void vp_latlong(VP *vp, int *lat1p, int *lon1p, int *lat2p, int *lon2p);

void
plot_meridians(VP *vp,
             void (*line_callback)(int x1, int y1, int x1f, int y1f,
                             int x2, int y2, int x2f, int y2f),
             void (*text_callback)(int x1, int y1, int x1f, int y1f,
                             char *str))
{
    int lat1, lon1, lat2, lon2, latmin, latmax, lonmin, lonmax, incr, lat, lon;
    int latmid, lonmid, xmid, ymid, xmidf, ymidf;
    int x1, y1, x2, y2, x1f, y1f, x2f, y2f;
    int sx1, sy1, sx2, sy2;
    int latdeg, latminu, londeg, lonminu;
    char minbuf[10];

    incr = vp->meridian_interval;
    /* Draw only if the interval is not too small. */
    xy_to_latlong(area.halfwidth, area.height - 2, 0, 0, &lat, &lon);
    latlong_to_xy(lat, lon, &x1, &y1, &x1f, &y1f);
    latlong_to_xy(lat - incr, lon, &x2, &y2, &x2f, &y2f);
    /* If a single interval down from the top middle of the area is
       off the map, then there won't be any lines to plot anyway, so
       OK to escape. */
    if (!in_area(x2, y2))
      return;
    xform_cell_fractional(vp, x1, y1, x1f, y1f, &sx1, &sy1);
    xform_cell_fractional(vp, x2, y2, x2f, y2f, &sx2, &sy2);
    /* Don't draw if lines would be really closely spaced. */
    if (sy2 - sy1 < 20)
      return;
    /* Find the closest meridians that will be visible in the
       viewport. */
    vp_latlong(vp, &lat1, &lon1, &lat2, &lon2);
    /* Round to even multiples of the meridian interval. */
    latmin = (lat1 / incr) * incr;
    latmax = (lat2 / incr) * incr + incr;
    lonmin = (lon1 / incr) * incr;
    lonmax = (lon2 / incr) * incr + incr;
    if (lonmax <= lonmin)
      lonmax += 21600;
    vp_latlong(vp, &lat1, &lon1, &lat2, &lon2);
    /* Determine the spacing of the text labels.  They should be far
       enough apart so that only one set of each is visible at any
       time, although it's more OK for two sets to appear than for
       none to be visible. */
    /* This algorithm assumes that we first do a draw of the whole
       viewport before using smaller viewports for incremental
       updates. */
    if (vp->lon_label_lat_interval == 0) {
      vp->lon_label_lat_interval = (abs(lat2 - lat1) / incr) * incr;
      if (vp->lon_label_lat_interval == 0)
        vp->lon_label_lat_interval = 1;
      vp->lat_label_lon_interval = (abs(lon2 - lon1) / incr) * incr;
      if (vp->lat_label_lon_interval == 0)
        vp->lat_label_lon_interval = 1;
    }
    /* Draw each line as an individual segment, because meridians may
       not be straight lines. */
    for (lat = latmax; lat >= latmin; lat -= incr) {
      for (lon = lonmin; lon <= lonmax; lon += incr) {
          latlong_to_xy(lat, lon, &x1, &y1, &x1f, &y1f);
          if (in_area(x1, y1)) {
            latlong_to_xy(lat - incr, lon, &x2, &y2, &x2f, &y2f);
            if (line_callback) {
                if (in_area(x2, y2)) {
                  (*line_callback)(x1, y1, x1f, y1f, x2, y2, x2f, y2f);
                }
                latlong_to_xy(lat, lon + incr, &x2, &y2, &x2f, &y2f);
                if (in_area(x2, y2)) {
                  (*line_callback)(x1, y1, x1f, y1f, x2, y2, x2f, y2f);
                }
            }
            if (text_callback) {
                if (lon % vp->lat_label_lon_interval == 0) {
                  lonmid = lon + incr / 2;
                  latlong_to_xy(lat, lonmid,
                              &xmid, &ymid, &xmidf, &ymidf);
                  /* If adding the incr goes off the world,
                     subtract instead. */
                  if (!in_area(xmid, ymid)) {
                      lonmid = lon - incr / 2;
                      latlong_to_xy(lat, lonmid,
                                &xmid, &ymid, &xmidf, &ymidf);
                  }
                  /* Give up if this did not work. */
                  if (in_area(xmid, ymid)) {
                      latdeg = abs(lat) / 60;
                      latminu = abs(lat) % 60;
                      minbuf[0] = '\0';
                      if (latminu != 0)
                        sprintf(minbuf, "%dm", latminu);
                      sprintf(tmpbuf, "%dd%s%c",
                            latdeg, minbuf, (lat >= 0 ? 'N' : 'S'));
                      (*text_callback)(xmid, ymid, xmidf, ymidf, tmpbuf);
                  }
                }
                if (lat % vp->lon_label_lat_interval == 0) {
                  latmid = lat + incr / 2;
                  latlong_to_xy(latmid, lon,
                              &xmid, &ymid, &xmidf, &ymidf);
                  /* If adding the incr goes off the world,
                     subtract instead. */
                  if (!in_area(xmid, ymid)) {
                      latmid = lat - incr / 2;
                      latlong_to_xy(latmid, lon,
                                &xmid, &ymid, &xmidf, &ymidf);
                  }
                  /* If this position is valid, draw the
                           longitude. */
                  if (in_area(xmid, ymid)) {
                      londeg = abs(lon) / 60;
                      lonminu = abs(lon) % 60;
                      minbuf[0] = '\0';
                      if (lonminu != 0)
                        sprintf(minbuf, "%dm", lonminu);
                      sprintf(tmpbuf, "%dd%s%c",
                            londeg, minbuf, (lon >= 0 ? 'E' : 'W'));
                      (*text_callback)(xmid, ymid, xmidf, ymidf, tmpbuf);
                  }
                }
            }
#if 0 /* use to debug meridian plotting */
            sprintf(tmpbuf, "%d %d", lat, lon);
            (*text_callback)(x1, y1, x1f, y1f, tmpbuf);
            latlong_desc(tmpbuf, x1, y1, x1f, y1f, 3);
            (*text_callback)(x1, y1, x1f, y1f, tmpbuf);
#endif
          }
      }
    }
}

void
vp_latlong(VP *vp, int *lat1p, int *lon1p, int *lat2p, int *lon2p)
{
    int xmin, ymin, xmax, ymax, xminf, yminf, xmaxf, ymaxf;

    /* The implementation of nearest_cell still does the correct
       calculation, even if the pixel position is not in the area. */
    nearest_cell(vp, 0, vp->pxh, &xmin, &ymin, &xminf, &yminf);
    nearest_cell(vp, vp->pxw, 0, &xmax, &ymax, &xmaxf, &ymaxf);
    xy_to_latlong(xmin, ymin, xminf, yminf, lat1p, lon1p);
    xy_to_latlong(xmax, ymax, xmaxf, ymaxf, lat2p, lon2p);
}

/* Use this to set the contour interval, or, by setting to 0, let it
   float to a plausible value based on the actual range of
   elevations. */

void
set_contour_interval(VP *vp, int n)
{
    int ncontours;

    if (n > 0) {
      /* Set contour interval, compute num contours from it. */
      vp->contour_interval = n;
      ncontours = (area.maxelev - area.minelev) / vp->contour_interval;
      vp->contour_interval_fixed = TRUE;
    } else {
      /* Set num contours, compute contour interval. */
      ncontours = max(1, min(15, area.maxelev - area.minelev));
      vp->contour_interval = (area.maxelev - area.minelev) / ncontours;
      vp->contour_interval_fixed = FALSE;
    }
    if (ncontours != vp->num_contours) {
      vp->num_contours = ncontours;
      if (vp->linebuf != NULL) {
          free(vp->linebuf);
          vp->linebuf = NULL;
      }
    }
}

/* The theory of contour lines is that each hex can be considered as
   six triangles, each of which has a vertex at the center and two
   on adjacent corners of the hex.  The elevation of the center vertex
   is the overall elevation of the cell, the elevations of the corners
   are averages with the adjacent cells.  If a particular contour
   elevation is between any pair of vertex elevations, then the contour
   line must cross that side of the triangle - and one of the other two
   sides.  We decide which of the two it is, interpolate to get the
   actual positions of each endpoint of the line segment, then draw it. */

void
contour_lines_at(VP *vp, int x, int y, int sx, int sy, LineSegment **lines,
             int *numlinesp)
{
    int el, el2, dir, x1, y1, sum, n, lowest, liq0, liq, ecor[NUMDIRS], ec;
    int maxel;
    int sxcor[NUMDIRS], sycor[NUMDIRS], sxc, syc;
    int power = vp->power;
    int ecorr, ecorl, sxcorr, sycorr, sxcorl, sycorl;
    int sx1, sy1, sx2, sy2;

    *numlinesp = 0;
    if (vp->contour_interval < 1)
      return;
    if (vp->linebuf == NULL)
      vp->linebuf = (LineSegment *) xmalloc ((vp->num_contours + 2) * 2 * NUMDIRS * sizeof(LineSegment));
    *lines = vp->linebuf;
    /* It's possible that the contour intervals have not yet been
       adjusted to account for all elevations in the world, so be
       sure to clip. */ 
    maxel = area.minelev + vp->num_contours * vp->contour_interval;
    el = elev_at(x, y);
    if (el > maxel)
      el = maxel;
    sxc = sx + vp->hw / 2;  syc = sy + vp->hh / 2;
    /* Compute the elevation at each corner of the cell. */
    liq0 = t_liquid(terrain_at(x, y));
    for_all_directions(dir) {
      sum = el;
      n = 1;
      lowest = el;
      liq = liq0;
      if (point_in_dir(x, y, dir, &x1, &y1)) {
          el2 = elev_at(x1, y1);
          if (el2 > maxel)
            el2 = maxel;
          sum += el2;
          ++n;
          lowest = min(lowest, el2);
          if (t_liquid(terrain_at(x1, y1)))
            liq = TRUE;
      }
      if (point_in_dir(x, y, left_dir(dir), &x1, &y1)) {
          el2 = elev_at(x1, y1);
          if (el2 > maxel)
            el2 = maxel;
          sum += el2;
          ++n;
          lowest = min(lowest, el2);
          if (t_liquid(terrain_at(x1, y1)))
            liq = TRUE;
      }
      /* Pick lowest, in the case of liquids, or average. */
      if (liq)
        ecor[dir] = lowest;
      else
        ecor[dir] = sum / n;
      sxcor[dir] = sx + bsx[power][dir];  sycor[dir] = sy + bsy[power][dir];
    }
    /* Iterate over all the possible elevations for contour lines. */
    for (ec = area.minelev + vp->contour_interval; ec < area.maxelev; ec += vp->contour_interval) {
      for_all_directions(dir) {
          ecorr = ecor[dir];
          ecorl = ecor[left_dir(dir)];
          sxcorr = sxcor[dir];  sycorr = sycor[dir];
          sxcorl = sxcor[left_dir(dir)];  sycorl = sycor[left_dir(dir)];
          if (el != ecorr && between(min(el, ecorr), ec, max(el, ecorr))) {
            if (el != ecorl
                && between(min(el, ecorl), ec, max(el, ecorl))) {
                sx1 = sxc + ((sxcorr - sxc) * (ec - el)) / (ecorr - el);
                sy1 = syc + ((sycorr - syc) * (ec - el)) / (ecorr - el);
                sx2 = sxc + ((sxcorl - sxc) * (ec - el)) / (ecorl - el);
                sy2 = syc + ((sycorl - syc) * (ec - el)) / (ecorl - el);
                if (sx1 != sx2 || sy1 != sy2) {
                  vp->linebuf[*numlinesp].sx1 = sx1;
                  vp->linebuf[*numlinesp].sy1 = sy1;
                  vp->linebuf[*numlinesp].sx2 = sx2;
                  vp->linebuf[*numlinesp].sy2 = sy2;
                  ++(*numlinesp);
                }
            } else if (ecorl != ecorr) {
                sx1 = sxc + ((sxcorr - sxc) * (ec - el)) / (ecorr - el);
                sy1 = syc + ((sycorr - syc) * (ec - el)) / (ecorr - el);
                /* By inverting odd directions before the
                   calculation we ensure that the endpoints are
                   calculated in exactly the same way for the two
                   line segments from adjacent cells that should
                   end on the same point. This eliminates the
                   small jumps in contour lines as they cross from
                   one cell to another. */
                /* Line ends on outer edge. */
                if (dir == NORTHEAST || dir == SOUTHEAST || dir == WEST) {
                  sx2 = sxcorr + ((sxcorl - sxcorr) * (ec - ecorr)) / (ecorl - ecorr);
                  sy2 = sycorr + ((sycorl - sycorr) * (ec - ecorr)) / (ecorl - ecorr);
                } else { 
                  sx2 = sxcorl + ((sxcorl - sxcorr) * (ec - ecorl)) / (ecorl - ecorr);
                  sy2 = sycorl + ((sycorl - sycorr) * (ec - ecorl)) / (ecorl - ecorr);
                  }
                if (sx1 != sx2 || sy1 != sy2) {
                  vp->linebuf[*numlinesp].sx1 = sx1;
                  vp->linebuf[*numlinesp].sy1 = sy1;
                  vp->linebuf[*numlinesp].sx2 = sx2;
                  vp->linebuf[*numlinesp].sy2 = sy2;
                  ++(*numlinesp);
                }
            }
          }
          if (el != ecorl && between(min(el, ecorl), ec, max(el, ecorl))) {
            if (ecorl != ecorr
                && between(min(ecorr, ecorl), ec, max(ecorr, ecorl))) {
                sx1 = sxc + ((sxcorl - sxc) * (ec - el)) / (ecorl - el);
                sy1 = syc + ((sycorl - syc) * (ec - el)) / (ecorl - el);
                /* Line ends on outer edge */
                if (dir == NORTHEAST || dir == SOUTHEAST || dir == WEST) {
                  sx2 = sxcorr + ((sxcorl - sxcorr) * (ec - ecorr)) / (ecorl - ecorr);
                  sy2 = sycorr + ((sycorl - sycorr) * (ec - ecorr)) / (ecorl - ecorr);
                } else { 
                  sx2 = sxcorl + ((sxcorl - sxcorr) * (ec - ecorl)) / (ecorl - ecorr);
                  sy2 = sycorl + ((sycorl - sycorr) * (ec - ecorl)) / (ecorl - ecorr);
                }
                if (sx1 != sx2 || sy1 != sy2) {
                  vp->linebuf[*numlinesp].sx1 = sx1;
                  vp->linebuf[*numlinesp].sy1 = sy1;
                  vp->linebuf[*numlinesp].sx2 = sx2;
                  vp->linebuf[*numlinesp].sy2 = sy2;
                  ++(*numlinesp);
                }
            }
          }
      }
    }
}

int
unit_visible(Side *side, VP *vp, Unit *unit)
{
    if (vp->show_all)
      return TRUE;
    /* Designer needs to see all units, even when show_all has been
       turned off - otherwise cells with units look empty. */
    if (is_designer(side))
      return TRUE;
    return side_sees_unit(side, unit);
}

/* Test whether the occupants of the given unit are visible to the
   given side. */

int
occupants_visible(Side *side, VP *vp, Unit *unit)
{
    if (unit->occupant == NULL)
      return FALSE;
    if (vp->show_all)
      return TRUE;
    /* side_sees_unit means we may freely examine the unit since we
    control it, the game is over or side->see-all is true. Occs should 
    therefore always be visible then. */
    if (side_sees_unit(side, unit))
      return TRUE;
    if (u_see_occupants(unit->type))
      return TRUE;
    if (side_owns_occupant(side, unit))
      return TRUE;
    return FALSE;
}

/* Don't draw the temperature in every cell, only do ones with even
   coords or ones where the temperature in any adjacent cell is
   different. */

int
draw_temperature_here(Side *side, int x, int y)
{
    int dir, x1, y1, temphere = temperature_view(side, x, y);

    /* Designers should see temperature in every cell. */
    if (is_designer(side))
      return TRUE;
    for_all_directions(dir) {
      if (interior_point_in_dir(x, y, dir, &x1, &y1)) {
          if (temphere != temperature_view(side, x1, y1))
            return TRUE;
          /* Always show temperature around edge of known area. */
          if (terrain_view(side, x1, y1) == UNSEEN)
            return TRUE;
      }
    }
    return (x % 2 == 0 && y % 2 == 0);
}

/* Don't draw the winds in every cell, only do ones with odd coords or
   ones where the wind in any adjacent cell is different. */

int
draw_winds_here(Side *side, int x, int y)
{
    int dir, x1, y1, windhere = wind_view(side, x, y);

    /* Designers should see wind in every cell. */
    if (is_designer(side))
      return TRUE;
    /* Don't draw wind in unseen areas. */
    if (terrain_view(side, x, y) == UNSEEN)
      return FALSE;
    for_all_directions(dir) {
      if (interior_point_in_dir(x, y, dir, &x1, &y1)) {
          if (windhere != wind_view(side, x1, y1))
            return TRUE;
          /* Always show wind around edge of known area. */
          if (terrain_view(side, x1, y1) == UNSEEN)
            return TRUE;
      }
    }
    return (x % 2 == 1 && y % 2 == 1);
}

/* Return a textual description of what is at the given pixel in the
   given viewport. */

void
oneliner(Side *side, VP *vp, int sx, int sy)
{
    int x, y, xf, yf;
    Unit *unit, *unit2, *user;
    int t2, u, ps = NOBODY, cs = NOBODY, dep, sayin = FALSE, userid;
    char *peopdesc = NULL, *str;
    char descbuf[80], ctrlbuf[80], buf[BUFSIZE];
    Side *side2, *side3;
    Feature *feature;
    UnitView *uview;

    nearest_cell(vp, sx, sy, &x, &y, &xf, &yf);
    nearest_unit(side, vp, sx, sy, &unit);
    nearest_unit_view(side, vp, sx, sy, &uview);
    if (!in_area(x, y)) {
      strcpy(tmpbuf, "(nothing)");
      return;
    } else if (vp->show_all || terrain_visible(side, x, y)) {
      strcpy(tmpbuf, " ");
      /* Describe the side of the people here. */
      if (people_sides_defined()) {
          ps = people_side_at(x, y);
          if (ps != NOBODY) {
            side2 = side_n(ps);
            if (side2 == NULL) {
                peopdesc = "indep";
            } else if (side2 == side) {
                peopdesc = "your";
            } else {
                peopdesc = side_adjective(side2);
                if (peopdesc[0] == '\0') {
                  sprintf(descbuf, "s%d", side2->id);
                  peopdesc = descbuf;
                }
            }
          }
      }
      if (control_sides_defined()) {
          cs = control_side_at(x, y);
          if (cs != ps) {
            side3 = side_n(cs);
            if (side3 == NULL) {
                strcpy(ctrlbuf, "uncontrolled");
            } else if (side3 == side) {
                strcpy(ctrlbuf, "your");
            } else {
                strcpy(ctrlbuf, side_adjective(side3));
                if (ctrlbuf[0] == '\0') {
                  sprintf(ctrlbuf, "s%d", side3->id);
                }
                strcat(ctrlbuf, "-controlled");
            }
            if (peopdesc != NULL) {
                strcat(ctrlbuf, " ");
                strcat(ctrlbuf, peopdesc);
            }
            peopdesc = ctrlbuf;
          }
      }
      if (vp->show_all || (uview != NULL && view_side(uview) == side)) {
          if (unit != NULL) {
            if (unit->side != side) {
                if (ps != NOBODY && ps == side_number(unit->side)) {
                  peopdesc = "own";
                }
            }
            strcat(tmpbuf, unit_handle(side, unit));
            sayin = TRUE;
          }
      } else {
          if (uview != NULL) {
            u = view_type(uview);
            side2 = view_side(uview);
            if (ps != NOBODY && ps == side2->id) {
                peopdesc = "own";
            }
            /* Display the real name of a non-mobile unit,
               even if it's not in view coverage. */
            /* (should fix this to use view stack) */
            unit = NULL;
            for_all_stack(x, y, unit2) {
                if (unit2->type == u
                  && (!mobile(u) || u_acp(u) == 0)) {
                  unit = unit2;
                  break;
                }
            }
            if (unit != NULL) {
                strcat(tmpbuf,
                     apparent_unit_handle(side, unit, side2));
            } else {
                strcat(tmpbuf, side_adjective(side2));
                strcat(tmpbuf, " ");
                strcat(tmpbuf, u_type_name(u));
            }
            sayin = TRUE;
          }
      }
      if (sayin) {
          strcat(tmpbuf, " (in ");
      }
      if (peopdesc != NULL) {
          strcat(tmpbuf, peopdesc);
          strcat(tmpbuf, " ");
      }
      if (vp->show_all)
        t2 = terrain_at(x, y);
      else
        t2 = vterrain(terrain_view(side, x, y));
      strcat(tmpbuf, t_type_name(t2));
      if (sayin) {
          strcat(tmpbuf, ")");
      }
      /* (should be able to display views of these) */
      if (elevations_defined()) {
          tprintf(tmpbuf, " Elev %d", elev_at(x, y));
      }
      if (temperatures_defined()) {
          tprintf(tmpbuf, " T %ddeg", temperature_at(x, y));
      }
      if (numcoattypes > 0) {
          for_all_terrain_types(t2) {
            if (t_is_coating(t2)
                && aux_terrain_defined(t2)
                && ((dep = aux_terrain_view(side, x, y, t2)) > 0)) {
                tprintf(tmpbuf, " %s %d", t_type_name(t2), dep);
            }
          }
      }
    } else {
      sprintf(tmpbuf, "(unknown)");
    }
    strcat(tmpbuf, " @");
    if (1 /* drawxy */) {
      tprintf(tmpbuf, "%d,%d", x, y);
    } else if (vp->draw_meridians) {
      latlong_desc(descbuf, x, y, xf, yf, 3);
      strcat(tmpbuf, descbuf);
    }
    if (vp->show_all || terrain_visible(side, x, y)) {
      feature = feature_at(x, y);
      if (feature != NULL) {
          if (feature->size > 0) {
            str = feature_desc(feature, buf);
            if (str != NULL) {
                strcat(tmpbuf, " (");
                strcat(tmpbuf, str);
                strcat(tmpbuf, ")");
            }
          }
      }
    }
    if (1 /* drawxy */ && vp->draw_meridians) {
      latlong_desc(descbuf, x, y, xf, yf, 3);
      strcat(tmpbuf, " (");
      strcat(tmpbuf, descbuf);
      strcat(tmpbuf, ")");
    }
    if (user_defined()
      && ((userid = user_at(x, y)) != NOUSER)) {
      user = find_unit(userid);
      if (in_play(user)) {
          strcat(tmpbuf, " (used by ");
          if (user->name != NULL)
            strcat(tmpbuf, user->name);
          else
            sprintf(tmpbuf+strlen(tmpbuf), "%s u#%d",
                  u_type_name(user->type), user->id);
          strcat(tmpbuf, ")");
      }
    }
    if (vp->draw_ai && side_has_ai(side)) {
      str = ai_at_desig(side, x, y);
      if (str) {
          strcat(tmpbuf, " ");
          strcat(tmpbuf, str);
      }
    }
}

/* (needs a better home?) */

/* Given a side and a unit, calculate the correct "next unit".  Typically
   used by autonext options, thus the name. */

Unit *
autonext_unit(Side *side, Unit *unit)
{
    int i, uniti = -1, n;
    Unit *nextunit;

    if (!side->ingame
      || side->finishedturn
      || side->actionvector == NULL)
      return NULL;
    if (could_be_next_unit(unit) && side_controls_unit(side, unit))
      return unit;
    for (i = 0; i < side->actionvector->numunits; ++i) {
      nextunit = unit_in_vector(side->actionvector, i);
      if (in_play(nextunit) && side_controls_unit(side, nextunit)) {
          if (unit == NULL || unit == nextunit) {
            uniti = i;
            break;
          }
      }
    }
    if (uniti < 0)
      return NULL;
    /* (should scan for both a preferred and an alternate - preferred
       could be within a supplied bbox so as to avoid scrolling) */
    for (i = uniti; i < uniti + side->actionvector->numunits; ++i) {
      n = i % side->actionvector->numunits;
      nextunit = unit_in_vector(side->actionvector, n);
      if (could_be_next_unit(nextunit) && side_controls_unit(side, nextunit))
        return nextunit;
    }
    return NULL;
}

/*
 * This should really be called autonext_unit and the decision
 * whether to check inbox or not should depend on the bbox being
 * valid. i.e. could be called with -1,-1,-1,-1 to disable the bbox.
 */
Unit *
autonext_unit_inbox(Side *side, Unit *unit, VP *vp)
{
    int i, u, mx, my, val, prefval = -999, v = 10;
    Unit *nextunit = NULL, *prefunit = NULL;

    if (!side->ingame || side->finishedturn || side->actionvector == NULL)
      return NULL;

    /* degenerate case... this unit still has stuff to do. */
    if (could_be_next_unit(unit) && side_controls_unit(side, unit))
      return unit;

    if (unit == NULL) {
      u = 0;
      if (!nearest_cell(vp, vp->sx + vp->pxw / 2, vp->sy + vp->pxh / 2, &mx, &my, NULL, NULL)) {
          mx = area.width / 2;  my = area.halfheight;
      }
    } else {
      u = unit->type;
      mx = unit->x;  my = unit->y;
    }

    for (i = 0; i < side->actionvector->numunits; ++i) {
      nextunit = unit_in_vector(side->actionvector, i);
      if (side_controls_unit(side, nextunit) && could_be_next_unit(nextunit)) {
          val = v - distance(nextunit->x, nextunit->y, mx, my);
          if (cell_is_in_middle(vp, nextunit->x, nextunit->y))
            val += v;
          if (nextunit->type == u)
            val += 2;

          if (val > prefval) {
            prefval = val;
            prefunit = nextunit;
          }
      }
    }
    return prefunit;
}

int
could_be_next_unit(Unit *unit)
{
    return (unit != NULL
          && alive(unit)
          && inside_area(unit->x, unit->y)
          && has_acp_left(unit)
          && (unit->plan
            && !unit->plan->asleep
            && !unit->plan->reserve
            && !unit->plan->delayed
            && unit->plan->waitingfortasks));
}

/* Do a depth-first traversal of all the occupants of a unit. */

Unit *
find_next_occupant(Unit *unit)
{
    Unit *nextup;

    if (unit->occupant != NULL) {
      return unit->occupant;
    } else if (unit->nexthere != NULL) {
      return unit->nexthere;
    } else {
      nextup = unit->transport;
      if (nextup != NULL) {
          while (nextup->transport != NULL && nextup->nexthere == NULL) {
            nextup = nextup->transport;
          }
          if (nextup->nexthere != NULL)
            return nextup->nexthere;
          if (nextup->transport == NULL)
            return nextup;
      } else {
          /* This is a no-op if there is no stacking within a hex. */
          return unit_at(unit->x, unit->y);
      }
    }
    return unit;
}

int
find_units_matching(Side *side, char *name, Unit **unitp)
{
    int num = 0;
    Unit *unit;

    if (empty_string(name))
      return 0;
    for_all_units(unit) {
      if (!empty_string(unit->name)
          && strstr(unit->name, name)
          && side_sees_image(side, unit)) {
          *unitp = unit;
          ++num;
      }
    }
    return num;
}

Unit *
embarkation_unit(Unit *unit)
{
    Unit *transport, *occ;

    /* look for the first possible transport */
    for_all_stack(unit->x, unit->y, transport) {
      /* make sure its not the transport we're in and we can enter it */
      if (transport != unit->transport &&
          valid(check_enter_action(unit, unit, transport))) {
          return transport;
      }
      /* check the occupants too */
      for_all_occupants(transport, occ) {
          if (occ != unit->transport &&
            valid(check_enter_action(unit, unit, occ))) {
            return occ;
          }
      }
    }
    return NULL;
}

/* Given a character, compute the direction(s) that it represents.
   Return the number of directions. */

int
char_to_dir(int ch, int *dir1p, int *dir2p, int *modp)
{
    char basech, *rawdir;
    int ndirs = 0;

    if (isupper(ch)) {
      basech = tolower(ch);
      if (modp)
       *modp = 1;
    } else if (ch < ' ') {
      basech = ch + 0x60;
      if (modp)
        *modp = 2;
    } else {
      basech = ch;
      if (modp)
        *modp = 0;
    }
    rawdir = strchr(dirchars, basech);
    if (rawdir) {
      *dir1p = rawdir - dirchars;
      ndirs = 1;
    } else if (basech == 'k') {
      if (flip_coin()) {
          *dir1p = NORTHEAST;
          if (dir2p)
            *dir2p = NORTHWEST;
      } else {
          *dir1p = NORTHWEST;
          if (dir2p)
            *dir2p = NORTHEAST;
      }
      ndirs = 2;
    } else if (basech == 'j') {
      if (flip_coin()) {
          *dir1p = SOUTHEAST;
          if (dir2p)
            *dir2p = SOUTHWEST;
      } else {
          *dir1p = SOUTHWEST;
          if (dir2p)
            *dir2p = SOUTHEAST;
      }
      ndirs = 2;
    }
    return ndirs;
}

/* Given that the player desires to move the given unit into the given
   cell/other unit, prepare a "most appropriate" action. */
/* (should share diff cell and same cell interaction code) */

extern void toggle_user_at(Unit *unit, int x, int y);

int
advance_into_cell(Side *side, Unit *unit, int x, int y, Unit *other,
              HistEventType *reason)
{
    int z, m, rslt = H_UNDEFINED;

    /* Make sure we have some default value. */
    if (reason)
      *reason = rslt;
#ifdef DESIGNERS
    /* Designers use this function to push units around, bound only by the
       limits on occupancy. */
    if (is_designer(side))
      return net_designer_teleport(unit, x, y, other);
#endif /* DESIGNERS */
    z = unit->z;
    if (x != unit->x || y != unit->y) {
      /* Units that can't act/plan can't do anything to adjacent units. */
      if (unit->act == NULL || unit->plan == NULL)
        return FALSE;
      if (!mobile(unit->type)
          && u_advanced(unit->type)
          && distance(unit->x, unit->y, x, y) <= unit->reach) {
          toggle_user_at(unit, x, y);
          return TRUE;
      }
      if (distance(unit->x, unit->y, x, y) == 1) {
          /* Destination is adjacent to us. */
          /* First, handle the case where no unit was clicked on. */
          if (other == NULL) {
            rslt = check_move_action(unit, unit, x, y, z);
            if (valid(rslt)) {
                net_prep_move_action(unit, unit, x, y, z);
                return TRUE;
            }
            if (reason)
              *reason = rslt;
            if (can_extract_at(unit, x, y, &m)) {
                net_set_collect_task(unit, m, x, y);
                return TRUE;
            }
            return FALSE;
          }
          /* If a unit was clicked on, decide how to interact. */
          if (unit_trusts_unit(unit, other)
            || (other->side == indepside
                && sides_allow_entry(unit, other))) {
            /* A friend, maybe get on it. */
            if (can_occupy(unit, other)) {
                if (valid(check_enter_action(unit, unit, other))) {
                  net_prep_enter_action(unit, unit, other);
                } else {
                  /* (should schedule for next turn?) */
                }
            } else if (can_occupy(other, unit)) {
                if (u_acp(other->type) > 0) {
                  /* Have other unit do an enter action, then
                     move. */
                  /* (not quite right, move should happen after
                     other unit is actually inside, in case it
                     fills dest) */
                  net_prep_enter_action(other, other, unit);
                  net_set_move_to_task(unit, x, y, 0);
                } else {
                  net_prep_enter_action(unit, other, unit);
                  net_set_move_to_task(unit, x, y, 0);
                }
            } else if (other->transport != NULL
                     && can_occupy(unit, other->transport)) {
                if (valid(check_enter_action(unit, unit,
                                     other->transport))) {
                  net_prep_enter_action(unit, unit,
                                    other->transport);
                } else {
                  /* (should schedule for next turn?) */
                }
            } else if (other->transport != NULL
                     && other->transport->transport != NULL
                     && can_occupy(unit, other->transport->transport)) {
                /* two levels up should be sufficient */
                if (valid(check_enter_action(unit, unit, other->transport->transport))) {
                  net_prep_enter_action(unit, unit, other->transport->transport);
                } else {
                  /* (should schedule for next turn?) */
                }
            } else if (valid(check_transfer_part_action(unit, unit,
                                              unit->hp, other))) {
                net_prep_transfer_part_action(unit, unit,
                                      unit->hp, other);
            } else if (can_extract_at(unit, x, y, &m)
                     || can_load_at(unit, x, y, &m)) {
                /* (should test extraction from specific unit) */
                net_set_collect_task(unit, m, x, y);
            } else {
                rslt = check_move_action(unit, unit, x, y, z);
                if (valid(rslt)) {
                  net_prep_move_action(unit, unit, x, y, z);
                  return TRUE;
                }
                if (reason)
                  *reason = rslt;
                return FALSE;
            }
          } else {
            /* Somebody else's unit, try to victimize it in
               various ways, trying coexistence only as a last
               resort. */
            rslt = check_capture_action(unit, unit, other);
            if (valid(rslt)) {
                net_prep_capture_action(unit, unit, other);
                return TRUE;
            }
            rslt = check_overrun_action(unit, unit, x, y, z, 100);
            if (valid(rslt)) {
                net_prep_overrun_action(unit, unit, x, y, z, 100);
                return TRUE;
            }
            if (reason && rslt == A_ANY_NO_AMMO)
              *reason = rslt;
            rslt = check_attack_action(unit, unit, other, 100);
            if (valid(rslt)) {
                net_prep_attack_action(unit, unit, other, 100);
                return TRUE;
            }
            if (reason && rslt == A_ANY_NO_AMMO)
              *reason = rslt;
            rslt = check_fire_at_action(unit, unit, other, -1);
            if (valid(rslt)) {
                net_prep_fire_at_action(unit, unit, other, -1);
                return TRUE;
            }
            if (reason && rslt == A_ANY_NO_AMMO)
              *reason = rslt;
            rslt = check_detonate_action(unit, unit, x, y, z);
            if (valid(rslt)) {
                net_prep_detonate_action(unit, unit, x, y, z);
                return TRUE;
            }
            if (can_extract_at(unit, x, y, &m)
                || can_load_at(unit, x, y, &m)) {
                /* (should test extraction from specific unit) */
                net_set_collect_task(unit, m, x, y);
                return TRUE;
            }
            rslt = check_move_action(unit, unit, x, y, z);
            if (valid(rslt)) {
                net_prep_move_action(unit, unit, x, y, z);
                return TRUE;
            }
            /* None of the possible actions worked, so fail. */
            return FALSE;
          }
      } else {
          /* We're not adjacent to the destination; actions alone
             won't suffice. */
          if (can_extract_at(unit, x, y, &m)
            || can_load_at(unit, x, y, &m)) {
            net_set_collect_task(unit, m, x, y);
          } else if (mobile(unit->type)) {
            /* Although it's theoretically possible for a
               non-mobile unit to fulfill a move task by being
               carried all the way, in practice players are going
               to direct the transport to make the move, not its
               immobile occupants; so only allow mobile units and
               beep the player for trying to move a non-mobile
               unit. */
            net_set_move_to_task(unit, x, y, 0);
          } else {
            return FALSE;
          }
      }
    } else {
      /* Destination is in the unit's own cell. */
      if (other != NULL) {
          if (unit_trusts_unit(unit, other)) {
            if (valid(check_transfer_part_action(unit, unit, unit->hp, other))) {
                net_prep_transfer_part_action(unit, unit, unit->hp, other);
            } else if (valid(check_enter_action(unit, unit, other))) {
                net_prep_enter_action(unit, unit, other);
            } else {
                return FALSE;
            }
          } else {
            /* Somebody else's unit, try to victimize it in various ways,
               trying coexistence only as a last resort. */
            if (valid(check_capture_action(unit, unit, other))) {
                net_prep_capture_action(unit, unit, other);
            } else if (valid(check_attack_action(unit, unit, other, 100))) {
                net_prep_attack_action(unit, unit, other, 100);
            } else if (valid(check_fire_at_action(unit, unit, other, -1))) {
                net_prep_fire_at_action(unit, unit, other, -1);
            } else if (valid(check_detonate_action(unit, unit, x, y, z))) {
                net_prep_detonate_action(unit, unit, x, y, z);
            } else {
                return FALSE;
            }
          }
      } else if (unit->transport != NULL) {
          /* Unit is an occupant wanting to leave, but yet remain in
             the same cell as the transport. */
          rslt = check_move_action(unit, unit, x, y, z);
          if (valid(rslt)) {
            net_prep_move_action(unit, unit, x, y, z);
            return TRUE;
          }
          if (reason)
            *reason = rslt;
          return FALSE;
      } else {
          /* This is a no-op, don't do anything. */
      }
    }
    /* All the failures got filtered out by early returns, so anything
       that got here is a success. */
    return TRUE;
}

/* For an advanced unit, toggle its use of the given cell. */

void
toggle_user_at(Unit *unit, int x, int y)
{
    Unit *unit2;
    Side *side2;

    /* Return if landuse is undefined. */
    if (!user_defined())
      return;
    /* Return if the cell is not visible to this side. */
    if (!terrain_visible(unit->side, x, y))
      return;
    /* Return if the cell is used by another unit. */
    if (user_at(x, y) != NOUSER && user_at(x, y) != unit->id) {
      /* (could be more helpful) */
      notify(unit->side, "Cell at %d,%d is already in use", x, y);
      return;
    }
    /* Return if using maxcells and we are trying to add one more. */
    if (unit->usedcells >= unit->maxcells && user_at(x, y) != unit->id) {
      notify(unit->side, "Cannot use any more cells");
      return;
    }
    /* Return if independents or untrusted side has a unit in the cell. */
    if (unit_at(x, y) != NULL) {
      for_all_stack(x, y, unit2) {
          if (!trusted_side(unit->side, unit2->side))
            return;
      }
    }
    /* Toggle landuse by unit either on or off for the cell. */
    if (user_at(x, y) == NOUSER) {
      set_user_at(x, y, unit->id);
      unit->usedcells += 1;
    } else if (user_at(x, y) == unit->id) {
      set_user_at(x, y, NOUSER);
      unit->usedcells -= 1;
    }
    for_all_sides(side2) {
      if (side2->see_all
          || side_sees_unit(side2, unit) 
          || side_tracking_unit(side2, unit)) {
          update_cell_display(side2, x, y, UPDATE_ALWAYS);
      }
    }
}

/* Given a unit and amounts of supplies desired to transfer, move as
   much of them as possible into the unit's transport, or to a nearby
   units if there is no transport. */

static int give_supplies_to_one(Unit *unit, Unit *unit2, short *amts,
                        short *rslts);

Unit *
give_supplies(Unit *unit, short *amts, short *rslts)
{
    int dir, x1, y1, didsome;
    Unit *unit2;

    unit2 = unit->transport;
    if (unit2 != NULL) {
      didsome = give_supplies_to_one(unit, unit2, amts, rslts);
      if (didsome)
        return unit2;
    }
    for_all_occs_with_occs(unit, unit2) {
      /* Note that we might have enemy spies as occupants, so test
         relationship even here. */
      if (unit_trusts_unit(unit, unit2)) {
          didsome = give_supplies_to_one(unit, unit2, amts, rslts);
          if (didsome)
            return unit2;
      }
    }
    for_all_stack(unit->x, unit->y, unit2) {
      if (unit2 != unit && unit_trusts_unit(unit, unit2)) {
          didsome = give_supplies_to_one(unit, unit2, amts, rslts);
          if (didsome)
            return unit2;
      }
    }
    /* Look around for adjacent units to give to. */
    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)) {
                didsome = give_supplies_to_one(unit, unit2, amts, rslts);
                if (didsome)
                  return unit2;
            }
          }
      }
    }
    /* Now try again on adjacent occupants.  We do it this way so
       top-level adjacent units get tried before occupants, since
       for_all_stack_with_occs iterates depth-first. */
    for_all_directions(dir) {
      if (interior_point_in_dir(unit->x, unit->y, dir, &x1, &y1)) {
          for_all_stack_with_occs(x1, y1, unit2) {
            if (unit_trusts_unit(unit, unit2)) {
                didsome = give_supplies_to_one(unit, unit2, amts, rslts);
                if (didsome)
                  return unit2;
            }
          }
      }
    }
    return NULL;
}

/* Given a pair of units, have one give the desired amounts to the
   other. */

static int
give_supplies_to_one(Unit *unit, Unit *unit2, short *amts, short *rslts)
{
    int m, gift, maxgift, actual, didsome = FALSE;

    if (!(in_play(unit2) && completed(unit2)))
      return FALSE;
    for_all_material_types(m) {
      if (rslts)
        rslts[m] = 0;
      maxgift = min(unit->supply[m],
                  um_storage_x(unit2->type, m) - unit2->supply[m]);
      gift = ((amts == NULL || amts[m] == -1) ? (maxgift / 2) : amts[m]);
      if (gift > 0) {
          if (1 /* can do immed transfer */) {
            /* Be stingy if giver is low */
            if (2 * unit->supply[m] < um_storage_x(unit->type, m))
              gift = max(1, gift / 2);
            actual = transfer_supply(unit, unit2, m, gift);
            if (rslts)
              rslts[m] = actual;
            if (actual > 0)
              didsome = TRUE;
          }
      }
    }

    if (didsome) {
      /* Recompute the supply_is_low flags for both unit and unit2.  */
      if (unit->plan != NULL
          && !unit->plan->supply_is_low
          && past_halfway_point(unit)
          ) {
          unit->plan->supply_is_low = TRUE;
          update_unit_display(unit->side, unit, TRUE);
      }
      if (unit2->plan != NULL
          && unit2->plan->supply_is_low
          && !past_halfway_point(unit2)
          ) {
          unit2->plan->supply_is_low = FALSE;
          update_unit_display(unit2->side, unit2, TRUE);
      }
    }

    return didsome;
}

/* Attempt to transfer the given amounts of material from the unit's
   transport into the unit. */

int
take_supplies(Unit *unit, short *amts, short *rslts)
{
    int m, want, actual, neededsome;
    Unit *unit2;

    neededsome = FALSE;
    for_all_material_types(m) {
      if (rslts)
        rslts[m] = 0;
      want = ((amts == NULL || amts[m] == -1)
              ? (um_storage_x(unit->type, m) - unit->supply[m])
              : amts[m]);
      if (want > 0) {
          neededsome = TRUE;
          unit2 = unit->transport;
          if (in_play(unit2) && completed(unit2)) {
            /* Bug fix. Units inside a transport inside a base or
               city were previously unable to resupply directly
               from the base. Now the occ checks if the transport
               is inside a base and then resupplies directly from
               it. Moreover, the 'stingy' 50% left principle is
               applied only to units that need m themselves. Other
               units are free to give away all their supply. */
          
            Unit *base = unit2->transport;
            int request = want;                       

            /* First take as much supplies as possible from the
               transport's transport (the base) if it exists. */
            actual = 0;
            if (in_play(base) && completed(base)) {
                /* Be stingy if base is low (but only if it also
                       needs m). */
                if (2 * base->supply[m] < um_storage_x(base->type, m)
                  && (um_base_consumption(base->type, m) > 0 
                      || um_consumption_per_move(base->type, m) > 0)) {
                  request = max(1, want/2);
                }
                actual += transfer_supply(base, unit, m, request);
                want -= actual;
            }
            /* If still unsatisfied, also take supplies from
                   transport. */
            if (want) {
                request = want;
                /* Be stingy if transport is low (but only if it
                   also needs m). */
                if (2 * unit2->supply[m] < um_storage_x(unit2->type, m)
                  && (um_base_consumption(unit2->type, m) > 0 
                      || um_consumption_per_move(unit2->type, m) > 0)) {
                  request = max(1, want/2);
                }
                actual += transfer_supply(unit2, unit, m, want);
            }
            if (rslts)
              rslts[m] = actual;
          }
      }
    }

    if (neededsome) {
      /* Recompute the supply_is_low flags for both unit and unit2.  */
      if (unit2 != NULL
          && unit2->plan != NULL
          && !unit2->plan->supply_is_low
          && past_halfway_point(unit2)
          ) {
          unit2->plan->supply_is_low = TRUE;
          update_unit_display(unit2->side, unit2, TRUE);
      }
      if (unit->plan != NULL
          && unit->plan->supply_is_low
          && !past_halfway_point(unit)
          ) {
          unit->plan->supply_is_low = FALSE;
          update_unit_display(unit->side, unit, TRUE);
      }
    }

    return neededsome;
}

/* Return the type to build that dialogs should highlight initially. */

int
favored_type(Unit *unit)
{
    int u;

    if (unit == NULL)
      return NONUTYPE;
    if (unit->plan
      && unit->plan->tasks
      && unit->plan->tasks->type == TASK_BUILD)
      return unit->plan->tasks->args[0];
    for_all_unit_types(u) {
      if (uu_acp_to_create(unit->type, u) > 0
          && side_can_build(unit->side, u))
        return u;
    }
    return NONUTYPE;
}

ImageFamily *
get_unit_type_images(Side *side, int u)
{
    char *name;
    ImageFamily *imf;

    if (!empty_string(u_image_name(u)))
      name = u_image_name(u);
    else
      name = u_internal_name(u);
    imf = get_generic_images(name);
    if (imf != NULL && imf->numsizes == 0) {
      imf->ersatz = TRUE;
      imf = add_default_unit_image(imf, u);
    }
    record_imf_get(imf);
    return imf;
}

static ImageFamily *
add_default_unit_image(ImageFamily *imf, int u)
{
    int i, hi, lo;
    Image *img;

    img = get_img(imf, 16, 16);
    if (img == NULL)
      return imf;
    img->rawmonodata = xmalloc(32);
    img->rawmaskdata = xmalloc(32);
    hi = u >> 4;
    lo = (u & 0xf) << 4;
    for (i = 4; i < 28; i += 2) {
      (img->rawmonodata)[i] = hi;
      (img->rawmonodata)[i + 1] = lo;
      (img->rawmaskdata)[i] = 0x7f;
      (img->rawmaskdata)[i + 1] = 0xfe;
    }
    (img->rawmonodata)[14] = 0x1f;
    (img->rawmonodata)[15] = 0xf8;
    (img->rawmonodata)[16] = 0x1f;
    (img->rawmonodata)[17] = 0xf8;
    (img->rawmaskdata)[2] = 0x7f;
    (img->rawmaskdata)[3] = 0xfe;
    (img->rawmaskdata)[28] = 0x7f;
    (img->rawmaskdata)[29] = 0xfe;
    if (imf_interp_hook)
      imf = (*imf_interp_hook)(imf, NULL, FALSE);
    return imf;
}

/* Acquire all imagery relating to the given material type. */

ImageFamily *
get_material_type_images(Side *side, int m)
{
    char *name;
    ImageFamily *imf;

    if (!empty_string(m_image_name(m)))
      name = m_image_name(m);
    else
      name = m_type_name(m);
    imf = get_generic_images(name);
    if (imf != NULL && imf->numsizes == 0) {
      imf->ersatz = TRUE;
      imf = add_default_material_image(imf, m);
    }
    record_imf_get(imf);
    return imf;
}

/* The default material image is ugly but functional; basically a
   binary encoding of the material type number.  Also add a shade of
   gray, for use when solid colors are wanted. */

static ImageFamily *
add_default_material_image(ImageFamily *imf, int m)
{
    int gray;
    Image *img;

    img = get_img(imf, 8, 8);
    img->istile = TRUE;
    img->rawmonodata = xmalloc(8);
    (img->rawmonodata)[1] = (m << 2);
    (img->rawmonodata)[2] = (m << 2);
    (img->rawmonodata)[3] = 0x7e;
    (img->rawmonodata)[4] = (m << 2);
    (img->rawmonodata)[5] = (m << 2);
    /* Also add a shade of gray. */
    img = get_img(imf, 1, 1);
    img->istile = TRUE;
    /* Range from dark to light gray. */
    gray = 15000 + ((m * 45000) / nummtypes);
    img->palette = cons(cons(new_number(0),
                       cons(new_number(gray),
                          cons(new_number(gray),
                               cons(new_number(gray), lispnil)))),
                  lispnil);
    if (imf_interp_hook)
      imf = (*imf_interp_hook)(imf, NULL, FALSE);
    return imf;
}

/* Acquire all imagery relating to the given terrain type. */

ImageFamily *
get_terrain_type_images(Side *side, int t)
{
    char *name;
    ImageFamily *imf;

    if (!empty_string(t_image_name(t)))
      name = t_image_name(t);
    else
      name = t_type_name(t);
    imf = get_generic_images(name);
    if (imf != NULL && imf->numsizes == 0) {
      imf->ersatz = TRUE;
      imf = add_default_terrain_image(imf, t);
    }
    record_imf_get(imf);
    return imf;
}

/* The default terrain image is ugly but functional; basically a binary
   encoding of the terrain type number. */

static ImageFamily *
add_default_terrain_image(ImageFamily *imf, int t)
{
    Image *img;

    img = get_img(imf, 8, 8);
    img->istile = TRUE;
    img->rawmonodata = xmalloc(8);
    (img->rawmonodata)[1] = (t << 2);
    (img->rawmonodata)[2] = (t << 2);
    (img->rawmonodata)[3] = 0x7e;
    (img->rawmonodata)[4] = (t << 2);
    (img->rawmonodata)[5] = (t << 2);
    if (imf_interp_hook)
      imf = (*imf_interp_hook)(imf, NULL, FALSE);
    return imf;
}

ImageFamily *
get_unseen_images(Side *side)
{
    if (!empty_string(g_unseen_color())) {
      unseen_image = get_generic_images(g_unseen_color());
      if (unseen_image != NULL && unseen_image->numsizes == 0) {
          /* Appears to have failed - clear the unseen image then. */
          unseen_image = NULL;
          /* Note that we shouldn't try to free the imf, because it
             may be in use elsewhere. */
      }
    }
    record_imf_get(unseen_image);
    return unseen_image;
}

ImageFamily *
get_emblem_images(Side *side, Side *side2)
{
    char *s, *c, *name, tmpbuf[BUFSIZE];
    int s2 = side_number(side2);
    ImageFamily *imf;

    if (side2 == NULL) {
      name = "s0";
    } else if (!empty_string(side2->emblemname)) {
      name = side2->emblemname;
    } else if (!empty_string(side2->colorscheme)) {
      /* Take the first (main) color if there are multiple colors. */
      for (s = side2->colorscheme, c = tmpbuf;; ++s) {
            /* Test for end of name or end of scheme. */
            if (*s == ',' || *s == '\0') {
                  /* Terminate the name string. */
                  *c = '\0';
                  c = tmpbuf;
                  break;
            /* Add one more character. */
            } else *c++ = *s;
      }
      name = copy_string(tmpbuf);
      /* Make sure the color emblemname is set so that it is saved
         correctly. */
      net_set_side_emblemname(side, side2, name);
    } else {
      /* There is a set of default emblems named "s1", etc. */
      sprintf(tmpbuf, "s%d", s2);
      name = copy_string(tmpbuf);
      /* Make sure the default emblemname is set so that it is 
         saved correctly. */
      net_set_side_emblemname(side, side2, name);
    }
    imf = get_generic_images(name);
    /* If we must have an image, and none were acquired, invoke the default
       image getter. */
    if (imf != NULL && imf->numsizes == 0 && strcmp(name, "none") != 0) {
      imf->ersatz = TRUE;
      imf = add_default_emblem_image(imf, s2);
    }
    record_imf_get(imf);
    return imf;
}

/* For a substitute emblem, make a solid white square with stripes at
   top and bottom, with the encoding of the side number in the
   middle.  (In practice, the number emblems usually get used.) */

static ImageFamily *
add_default_emblem_image(ImageFamily *imf, int s2)
{
    int i;
    Image *img;

    img = get_img(imf, 8, 8);
    if (img == NULL)
      return imf;
    img->rawmonodata = xmalloc(8);
    img->rawmaskdata = xmalloc(8);
    for (i = 0; i < 8; ++i) {
      (img->rawmonodata)[i] = s2;
      (img->rawmaskdata)[i] = 0xff;
    }
    (img->rawmonodata)[0] = 0xff;
    (img->rawmonodata)[7] = 0xff;
    if (imf_interp_hook)
      imf = (*imf_interp_hook)(imf, NULL, FALSE);
    return imf;
}

/* Record that the given image family was used.  This is used when
   saving a game, for instance. */

void
record_imf_get(ImageFamily *imf)
{
    int i;
    ImageFamily **new_record;

    if (imf == NULL)
      return;
    /* Estimate and allocate the usual amount of space needed. */
    if (max_recorded_imfs == 0)
      max_recorded_imfs = numutypes + numttypes + (MAXSIDES + 1) + 1;
    if (recorded_imfs == NULL) {
      recorded_imfs =
        (ImageFamily **) xmalloc(max_recorded_imfs * sizeof(ImageFamily *));
    }
    /* Allocate more space if needed. */
    if (num_recorded_imfs >= max_recorded_imfs) {
      max_recorded_imfs += max_recorded_imfs / 2;
      new_record =
        (ImageFamily **) xmalloc(max_recorded_imfs * sizeof(ImageFamily *));
      for (i = 0; i < num_recorded_imfs; ++i) {
          new_record[i] = recorded_imfs[i];
      }
      recorded_imfs = new_record;
    }
    for (i = 0; i < num_recorded_imfs; ++i) {
      if (strcmp(imf->name, recorded_imfs[i]->name) == 0)
        return;
    }
    recorded_imfs[num_recorded_imfs++] = imf;
    /* Expand any interface-specific data into its all-interface form,
       so that saved games will include it.  This needs to be done
       now, because game saving may occur in a low-memory situation
       and there may not be enough memory available then. */
    make_generic_image_data(imf);
}

/* Output a general description of an image family. */

void
describe_imf(Side *side, char *classname, char *typename, ImageFamily *imf)
{
    Image *img;

    if (imf == NULL) {
      DGprintf("No image family for %s %s for %s",
             classname, typename, side_desig(side));
      return;
    }
    DGprintf("%s %s family for %s has %d images",
           classname, typename, side_desig(side), imf->numsizes);
    if (imf->location)
      DGprintf(" and is in %s", imf->location->name);
    DGprintf("\n");
    for_all_images(imf, img) {
      DGprintf("    %dx%d", img->w, img->h);
      if (img->istile)
        DGprintf(" tile");
      if (img->isconnection)
        DGprintf(" connection");
      if (img->isborder)
        DGprintf(" border");
      if (img->istransition)
        DGprintf(" transition");
      if (img->numsubimages > 0)
        DGprintf(" %d subimages", img->numsubimages);
      if (imf_describe_hook)
        (*imf_describe_hook)(side, img);
      DGprintf("\n");
    }
}

/* Compute and cache single-char representations for things. */

void
init_ui_chars(void)
{
    int u, t;
    char *str;

    if (unitchars == NULL) {
      unitchars = xmalloc(numutypes);
      for_all_unit_types(u) {
          str = u_uchar(u);
          unitchars[u] =
            (!empty_string(str) ? str[0] : utype_name_n(u, 1)[0]);
      }
    }
    if (terrchars == NULL) {
      terrchars = xmalloc(numttypes);
      for_all_terrain_types(t) {
          str = t_char(t);
          terrchars[t] = (!empty_string(str) ? str[0] : t_type_name(t)[0]);
      }
    }
    unseen_char_1 = unseen_char_2 = ' ';
    str = g_unseen_char();
    if (strlen(str) >= 1) {
      unseen_char_1 = unseen_char_2 = str[0];
      if (strlen(str) >= 2) {
          unseen_char_2 = str[1];
      }
    }
}

/* Write the side's view of the world, as ASCII. */

/* (should be intelligent enough to cut into pages, or else document
   how to do it) */
/* (maybe display names too somehow, perhaps as second layer?) */

#define VIEWFILE "view.ccq"

void
dump_text_view(Side *side, int use_both_chars)
{
    char ch1, ch2;
    int x, y, t, u, s, draw, i;
    Side *side2;
    Unit *unit;
    UnitView *uview;
    FILE *fp;

    fp = open_file(VIEWFILE, "w");
    if (fp != NULL) {
      for (y = area.height-1; y >= 0; --y) {
          for (i = 0; i < y; ++i)
            fputc(' ', fp);
          for (x = 0; x < area.width; ++x) {
            ch1 = ch2 = ' ';
            if (in_area(x, y) && terrain_visible(side, x, y)) {
                t = terrain_at(x, y);
                ch1 = terrchars[t];
                ch2 = (use_both_chars ? ch1 : ' ');
                draw = FALSE;
                if (side->see_all) {
                  unit = unit_at(x, y);
                  if (unit != NULL) {
                      u = unit->type;
                      s = side_number(unit->side);
                      draw = TRUE;
                  }
                } else {
                  uview = unit_view_at(side, wrapx(x), y);
                  if (uview != NULL) {
                      u = view_type(uview);
                      s = view_side(uview)->id;
                      draw = TRUE;
                  }
                }
                if (draw) {
                  ch1 = unitchars[u];
                  ch2 = ' ';
                  if (between(1, s, 9))
                    ch2 = s + '0';
                  else if (s >= 10)
                    /* This could get weird if s > 36, but not much
                       chance of that because MAXSIDES < 31 always. */
                    ch2 = s - 10 + 'A';
                }
            }
            fputc(ch1, fp);
            fputc(ch2, fp);
          }
          fprintf(fp, "\n");
      }
      fprintf(fp, "\n\nTerrain Types:\n");
      for_all_terrain_types(t) {
          fprintf(fp, "  %c%c  %s\n",
                terrchars[t], terrchars[t], t_type_name(t));
      }
      fprintf(fp, "\n\nUnit Types:\n");
      for_all_unit_types(u) {
          fprintf(fp, "  %c   %s\n", unitchars[u], u_type_name(u));
      }
      fprintf(fp, "\n\nSides:\n");
      for_all_sides(side2) {
          fprintf(fp, "  %d   %s\n", side_number(side2), side_name(side2));
      }
      fclose(fp);
      notify(side, "Dumped area view to \"%s\".", VIEWFILE);
    } else {
      notify(side, "Can't open \"%s\"!!", VIEWFILE);
    }
}

/* Return the type of cell terrain that the given side sees at the given
   location. */

int
terrain_seen_at(Side *side, int x, int y)
{
    if (in_area(x, y)
      && (side->see_all
#ifdef DESIGNERS
          || side->designer
#endif /* DESIGNERS */
          || terrain_view(side, x, y) != UNSEEN)) {
      return terrain_at(x, y);
    } else {
      return NONTTYPE;
    }
}

/* (should remove both of these, only used in ps.c currently) */
/* Return the unit seen by the given side at the given location.  Note
   that this should not be used casually by interfaces, since the result
   is a pointer to a real unit, not a view of one. */
/* (should result depend on contents of stack?) */

Unit *
unit_seen_at(Side *side, int x, int y)
{
    if (!in_area(x, y))
      return NULL;
    if (side->see_all
#ifdef DESIGNERS
      || side->designer
#endif /* DESIGNERS */
      || cover(side, x, y) > 0)
      return unit_at(x, y);
    return NULL;
}

int
utype_seen_at(Side *side, int x, int y)
{
    Unit *unit;
#if 0
    UnitView *uview;
#endif

    if (!in_area(x, y))
      return NONUTYPE;
    unit = unit_seen_at(side, x, y);
    if (unit)
      return unit->type;

#if 0 /* for now */
    uview = unit_view_at(side, x, y);
    if (uview != NULL)
      return view_type(uview);
#endif

    return NONUTYPE;
}

Generated by  Doxygen 1.6.0   Back to index