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

macmap.c

/* Map graphics for the Mac interface to Xconq.
   Copyright (C) 1992-1999 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 consists of
            1. Coordinate transforming and calculating functions.
            2. Map creating, destroying and modifying functions.
            3. draw_map and all the functions it calls.
            4. draw_other_maps & draw_related_maps.
            5. draw_unit_blast, draw_selections etc.
            7. update_cell_display & update_cell.

   draw_row and all functions that it calls are in macrow.c.
   All mouse-driven user interaction code is in macmouse.c. */

#include "conq.h"
#include "macconq.h"

static void xform_flat(Map *map, int x, int y, int *sxp, int *syp);
static void xform_fractional(Map *map, int x, int y, int xf, int yf, int *sxp, int *syp);
static void xform_fractional_flat(Map *map, int x, int y, int xf, int yf, int *sxp, int *syp);
static int at_all_visible(Map *map, int x, int y);
static void m_focus_on_center(Map *map);

static void adjust_meridian_interval(Map *map);
static Map *front_map(void);
static void update_grid_leakage(Map *map);

static void draw_window_background(Map *map);
static void draw_map_content(Map *map);
static void draw_feature_names(Map *map, Rect drawrect);
static void draw_meridians(Map *map, Rect drawrect);
static void draw_area_background(Map *map);
static void draw_info_text(Map *map, int x, int y, int len, char *buf);
static void draw_other_maps(Map *map);
static void draw_other_map(Map *map, Map *map2);
static void draw_selected_unit(Map *map, Unit *unit);
static void draw_unit_names_only(Map *map, int x, int y);

Map *frontmap = NULL;

int conwid = 32;
int tophgt;
int mapnum = 1;
int nummaps = 0;
int lastmaph = -1;
int lastmapv = -1;
int animation_pattern_state;
char *mouseover = "something";

/* Handles of the pictures that display all the map control buttons. */
PicHandle tlcontrols = nil;
PicHandle blcontrols = nil;

/* Adjust viewport for offscreen drawing and go offscreen.
   Then call draw_other_maps to erase other-map boxes */

#define GO_OFFSCREEN(m) {  \
      (m)->vp->sx -= (m)->offsetx + (m)->bufx - (m)->conw; \
      (m)->vp->sy -= (m)->offsety + (m)->bufy - (m)->toph; \
      LockPixels(GetGWorldPixMap((m)->gworldPortPtr)); \
      SetPort((GrafPtr)(m)->gworldPortPtr); \
      if ((m)->vp->draw_other_maps) draw_other_maps(m);\
}

/* Call draw_other_maps to redraw other-map boxes, then
   reset viewport for onscreen drawing and return onscreen. */

#define RETURN_ONSCREEN(m) {  \
      if ((m)->vp->draw_other_maps) draw_other_maps(m);\
      (m)->vp->sx += (m)->offsetx + (m)->bufx - (m)->conw; \
      (m)->vp->sy += (m)->offsety + (m)->bufy - (m)->toph; \
      UnlockPixels(GetGWorldPixMap((m)->gworldPortPtr)); \
      SetPort((GrafPtr)(m)->windowPortPtr); \
  }

/* These three macros are used to handle cases where an item stretches across the */
/* edge of a wrapped gworld. It is then necessary to draw it twice, once at each end. */
/* The macros should be applied to all drawing functions that take an sx value, rect */
/* or poly as a parameter. */

/* Note: In at least one case (longitude text in draw_meridians) it is clearly necessary */
/* to do a double UNWRAP (both add and substract sxmax) in order to cover all cases. */

/* Repeat (function) both if sx < 0 and if sx is within one hex from sxmax. */
#define UNWRAP_SX(map, sx, function) { \
      (function); \
      if (area.xwrap && (sx) > (map)->vp->sxmax - (map)->vp->hw) { \
            (sx) -= (map)->vp->sxmax; \
            (function); \
            (sx) += (map)->vp->sxmax; \
      } else if (area.xwrap && (sx) < 0) { \
            (sx) += (map)->vp->sxmax; \
            (function); \
            (sx) -= (map)->vp->sxmax; \
      } \
}

/* Repeat (function) both if rect.left < 0 and if rect.right > sxmax. */
#define UNWRAP_RECT(map, rect, function) { \
      (function); \
      if (area.xwrap && (rect).right > (map)->vp->sxmax) { \
            OffsetRect(&(rect), -(map)->vp->sxmax, 0); \
            (function); \
            OffsetRect(&(rect), (map)->vp->sxmax, 0); \
      } else if (area.xwrap && (rect).left < 0) { \
            OffsetRect(&(rect), (map)->vp->sxmax, 0); \
            (function); \
            OffsetRect(&(rect), -(map)->vp->sxmax, 0); \
      } \
}

/* Repeat (function) both if polyBBox.left < 0 and if polyBBox.right > sxmax. */
#define UNWRAP_POLY(map, poly, function) { \
      (function); \
      if (area.xwrap && (*(*(poly))).polyBBox.right > (map)->vp->sxmax) { \
            OffsetPoly((poly), -(map)->vp->sxmax, 0); \
            (function); \
            OffsetPoly((poly), (map)->vp->sxmax, 0); \
      } else if (area.xwrap && (*(*(poly))).polyBBox.left < 0) { \
            OffsetPoly((poly), (map)->vp->sxmax, 0); \
            (function); \
            OffsetPoly((poly), -(map)->vp->sxmax, 0); \
      } \
}

/* Given a map and a cell, compute the pixel coords of the cell's UL corner.
   This is the core routine that relates cells and pixels. */

void
xform(map, x, y, sxp, syp)
Map *map;
int x, y, *sxp, *syp;
{
      xform_cell(map->vp, x, y, sxp, syp);
      /* Shift the viewport for map decor. */
      *sxp += map->conw; *syp += map->toph;
}

void
xform_fractional(map, x, y, xf, yf, sxp, syp)
Map *map;
int x, y, xf, yf, *sxp, *syp;
{
      xform_cell_fractional(map->vp, x, y, xf, yf, sxp, syp);
      /* Shift the viewport for map decor. */
      *sxp += map->conw; *syp += map->toph;
}

/* These two functions bypass the vertical offset when viewing at an angle. */

void
xform_flat(map, x, y, sxp, syp)
Map *map;
int x, y, *sxp, *syp;
{
      xform_cell_flat(map->vp, x, y, sxp, syp);
      /* Shift the viewport for map decor. */
      *sxp += map->conw; *syp += map->toph;
}

void
xform_fractional_flat(map, x, y, xf, yf, sxp, syp)
Map *map;
int x, y, xf, yf, *sxp, *syp;
{
      xform_cell_fractional_flat(map->vp, x, y, xf, yf, sxp, syp);
      /* Shift the viewport for map decor. */
      *sxp += map->conw; *syp += map->toph;
}

void
m_xform_unit(map, unit, sxp, syp, swp, shp)
Map *map;
Unit *unit;
int *sxp, *syp, *swp, *shp;
{
      xform_unit(map->vp, unit, sxp, syp, swp, shp);
      /* Shift the viewport for map decor. */
      *sxp += map->conw; *syp += map->toph;
}

void
m_xform_unit_self(map, unit, sxp, syp, swp, shp)
Map *map;
Unit *unit;
int *sxp, *syp, *swp, *shp;
{
      xform_unit_self(map->vp, unit, sxp, syp, swp, shp);
      /* Shift the viewport for map decor. */
      *sxp += map->conw; *syp += map->toph;
}

void
m_xform_occupant(map, transport, unit, sx, sy, sw, sh, sxp, syp, swp, shp)
Map *map;
Unit *transport, *unit;
int sx, sy, sw, sh, *sxp, *syp, *swp, *shp;
{
      /* Transform the coordinates back to relative values. */
      sx -= map->conw;  sy -= map->toph;
      xform_occupant(map->vp, transport, unit, sx, sy, sw, sh, sxp, syp, swp, shp);
      /* Shift the viewport for map decor. */
      *sxp += map->conw; *syp += map->toph;
}

/* Un-transform screen coordinates (as supplied by mouse perhaps) into
   map coordinates.  */

/* Note that only drawing is affected by SetOrigin - mouse locations are
   always window-relative, and thus only need to be adjusted by map decor
   before going to the generic routines. */

int
m_nearest_cell(Map *map, int sx, int sy, int *xp, int *yp)
{
      return nearest_cell(map->vp, sx - map->conw, sy - map->toph, xp, yp, NULL, NULL);
}

/* Find the closest direction of the closest boundary */

int
m_nearest_boundary(Map *map, int sx, int sy, int *xp, int *yp, int *dirp)
{
      return nearest_boundary(map->vp, sx - map->conw, sy - map->toph, xp, yp, dirp);
}

int
m_nearest_unit(Map *map, int sx, int sy, Unit **unitp)
{
      return nearest_unit(dside, map->vp, sx - map->conw, sy - map->toph, unitp);
}

/* This tests whether the given cell might possibly be visible on the given map.
   Not as precise as a real cliprect calculation. */

int
at_all_visible(Map *map, int x, int y)
{
      return cell_is_visible(map->vp, x, y);
}

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

int
in_middle(Map *map, int x, int y)
{
      return cell_is_in_middle(map->vp, x, y);
}

void
m_focus_on_center(Map *map)
{
      focus_on_center(map->vp);
}

/* Put vcx,vcy in the middle of the map. */

void
m_center_on_focus(Map *map)
{
      center_on_focus(map->vp);
      set_content_rect(map);
}

/* Create a new map window and all its paraphernalia.  It is the responsibility of
   the calling function to pass a new map id and increment mapnum! */

Map *
create_map(int power, int id)
{
      int m, t, x, y, mainwidth, mainheight;
      Rect vscrollrect, hscrollrect;
      WindowPtr win;
      Map *map;
      Unit *unit;

      build_optional_terrain_type_menu();
      DGprintf("Creating map, mag power %d\n", power);
      map = (Map *) xmalloc(sizeof(Map));

      /* Use id passed in function call.  The world map is always map #0. */
      if (id == 0)
        worldmap = map;
      map->id = id;
      map->vp = new_vp();
      set_map_power(map, power);
      /* Pick an appropriate focus of the view. */
      pick_a_focus(dside, &x, &y);
      set_view_focus(map->vp, x, y);
      /* Set default values for the display controls. */
      if (map == worldmap) {
            map->conw = 0;          /* No decor for the world map. */
            map->toplineh = 0;
            map->topunith = 0;
            map->sbarwid = floatsbarwid;  /* Turned off later, needed to position sbars. */
      } else {
            map->conw = (default_draw_cpanel ? conwid : 0);
            map->toplineh = (default_draw_topline ? tophgt : 0);
            map->topunith = (default_draw_topunit ? topunithgt : 0);
            map->sbarwid = sbarwid;
      }
      map->toph = map->toplineh + map->topunith;
      /* Mac viewport needs different xform style, let the common code know about this. */
      map->vp->wide_viewport = TRUE;
      map->vp->draw_terrain = TRUE;
      /* Display all types of borders and connections normally. */
      for_all_terrain_types(t) {
            if (!t_is_cell(t)) {
                  map->vp->draw_aux_terrain[t] = TRUE;
            }
      }
      map->vp->draw_cell_pats = TRUE;  /* should get from prefs */
      map->vp->draw_feature_boundaries = TRUE;
      map->vp->draw_units = TRUE;
      map->vp->draw_names = default_draw_names;
      map->vp->draw_people = FALSE;
      map->vp->draw_control = FALSE;
      map->vp->draw_elevations = FALSE;
      for_all_material_types(m) {
            map->vp->draw_materials[m] = FALSE;
      }
      map->vp->num_materials_to_draw = 0;
      map->vp->draw_lighting = TRUE;
      map->vp->draw_temperature = FALSE;
      map->vp->draw_winds = FALSE;
      map->vp->draw_clouds = FALSE;
      map->vp->draw_storms = TRUE;
      map->vp->draw_grid = default_draw_grid;
      /* Display AI info if we have one and draw_ai is on by default. */
      map->vp->draw_ai = default_draw_ai && side_has_ai(dside);
      map->draw_emblems = default_draw_emblems;
      map->draw_transitions = !pref_solid_color_terrain;
      map->sidecolors = default_sidecolors;
      map->iconmasks = default_iconmasks;
      map->boxmasks = default_boxmasks;
      map->textmasks = default_textmasks;
      map->featureborders = default_featureborders;
      map->simple_borders = default_simple_borders;
      map->featurenames = default_featurenames;
      map->shorelines = default_shorelines;
      map->optimize_fonts = default_optimize_fonts;
      map->max_bufx = default_max_bufx;
      map->max_bufy = default_max_bufy;   
      map->vp->draw_meridians = default_draw_meridians;
      map->solid_color_terrain = pref_solid_color_terrain;
      map->draw_topline = default_draw_topline;
      map->draw_topunit = default_draw_topunit;
      map->erase_names = default_erase_names;
      map->blinking_curunit = default_blinking_curunit;
      map->selectionmasks = default_selectionmasks;
      /* Ovveride default and turn on name erasing if it is really needed. */
      for_all_units(unit) {
            if (mobile(unit->type) && unit->name) {
                  map->erase_names = TRUE;
                  break;
            }
      }
      map->xwrap = FALSE;
      update_grid_leakage(map);
      map->vp->draw_other_maps = default_drawothermaps;
      /* Override default and turn on other-map boxes for world map. */
      if (map == worldmap)
            map->vp->draw_other_maps = TRUE;
      map->vp->meridian_interval = default_meridian_interval;
      /* Adjust the default meridian interval so that it is
           appropriate for this magnification. */
      adjust_meridian_interval(map);
      map->autoselect = defaultautoselect;
      map->moveonclick = defaultmoveonclick;
      map->drawsizes = default_drawsizes;
      map->numselections = 0;
      map->maxselections = max(100, numunits + numunits / 2);
      map->selections = (Unit **) xmalloc(map->maxselections * sizeof(Unit *));
      /* Newest map goes on the front of the list. */
      map->next = maplist;
      maplist = map;
      if (hasColorQD) {
            win = GetNewCWindow((map == worldmap ? wFloatResize : wMap), NULL, NULL);
      } else {
            win = GetNewWindow((map == worldmap ? wFloatResize : wMap), NULL, NULL);
      }
      map->window = win;
      
      get_main_screen_size(&mainwidth, &mainheight);
      if (map == worldmap) {
      
            int   winwidth;
            int   winheight;
            Point winTL;
                  
            /* Make worldmapwin just big enough to contain the area. */
            winwidth = min(area.width * hws[power], mainwidth / 4);
            winheight = min(area.height * hcs[power], mainheight / 4);
            SizeWindow(win, winwidth, winheight,      FALSE);
            /* Put it in the right corner of the frontmap if it exists. */
            if (frontmap) {
                  winTL.h = frontmap->window->portRect.right - winwidth + 7;
                  winTL.v = GetMBarHeight() + 15 + 21;
            /* Else put it in the right corner of the main screen. */
            } else {                            
                  winTL.h = mainwidth - winwidth - 3;
                  winTL.v = GetMBarHeight() + 15;
            }
            /* Move it out of the way of game window if necessary. */
            if (IsVisible(gamewin)) {
                  winTL.h -= gamewinw + 8;
            }
            MoveWindow(win, winTL.h, winTL.v, false);
      } else {
            stagger_window(win, &lastmaph, &lastmapv);
            if (first_windows) {
                  /* Use the main screen size if full-sized map desired, else
                     leave room for game window at side and notices at bottom. */
                  SizeWindow(win,
                                 /* Make room for edges under Apple Platinum theme. */
                                 mainwidth - (fullsize_map ? 0 : gamewinw + 12) - 10,
                                 mainheight - GetMBarHeight() - 21 - (fullsize_map ? 0 : 150) - 7, 
                           FALSE);
                  /* Make roon for edges under Apple Platinum theme. */
                  MoveWindow(win, 5, GetMBarHeight() + 21, FALSE);
            }
      }
      SetPort(win);
      /* Make the world map floating. */  
      if (map == worldmap)
          MakeFloat(win);
      /* MakeFloat does not hilite the world map, it only brings it to front. */ 
      SelectTheWindow(win);

      /* Use window title "World Map" if world map, else use the map number. */
      if (map == worldmap) {
            worldmapwin = win;
            SetWTitle(win, "\pWorld Map");
      } else {
            sprintf(spbuf, "Map %d", map->id);
            add_window_menu_item(spbuf, win);
      }
      set_content_rect(map);
      if (map != worldmap)
          m_center_on_focus(map);
      /* Make the scrollbars. */
      vscrollrect = map->window->portRect;
      vscrollrect.top -= 1;
      vscrollrect.bottom -= map->sbarwid - 1;
      vscrollrect.left = vscrollrect.right - map->sbarwid;
      vscrollrect.right += 1;
      map->vscrollbar =
            NewControl(win, &vscrollrect, "\p", 1,
                   map->vp->sy, map->vp->symin, map->vp->symax, scrollBarProc, 0L);
      hscrollrect = win->portRect;
      hscrollrect.top = hscrollrect.bottom - map->sbarwid;
      hscrollrect.bottom += 1;
      hscrollrect.left += map->conw;
      hscrollrect.right -= map->sbarwid - 1;
      map->hscrollbar =
            NewControl(win, &hscrollrect, "\p", 1,
                   map->vp->sx, map->vp->sxmin, map->vp->sxmax, scrollBarProc, 0L);
      /* Turn off scrollbars that should be off now when they have been 
      positioned correctly. */
      if (map == worldmap)    
            map->sbarwid = 0; 
      else  map->sbarwid = (default_draw_scrollbars ? sbarwid : 0);
      if (map->sbarwid == 0) {
            SizeControl(map->vscrollbar, 0, 0);
            SizeControl(map->hscrollbar, 0, 0);
      }
      set_map_scrollbars(map);
      ++nummaps;
      draw_growicon_nolines(map->window);

      /* Save the onscreen gworld */
      GetGWorld(&map->windowPortPtr,&map->deviceHdl);

      /* Create an empty gworld of minimal size to be resized and filled in below */
      map->qdErr = NewGWorld(&map->gworldPortPtr,0, &map->contentrect,0, 0, 0);

      /* Alert and close the map window if we ran out of memory */
      if (map->gworldPortPtr == NULL || map->qdErr != noErr ) {
            /* close_window assumes other-map boxes exist */
            draw_related_maps(map); 
            close_window(win);
            StopAlert(aNoMem, NULL);
            /* Return zero to indicate no map was created */
            return NULL;                  
      }
      /* Make the new map frontmap. */
      if (map != worldmap)
            frontmap = map;   
      /* Optimize gworld size and position and also draw the map */
      update_gworld(map);

      /* Finally draw the new other-map boxes */
      draw_related_maps(map); 
      return map;
}

/* Compute the content part of the map window. */

void
set_content_rect(Map *map)
{
      map->contentrect = map->window->portRect;
      map->contentrect.left += map->conw;  
      map->contentrect.top += map->toph;
      map->contentrect.right -= map->sbarwid;  
      map->contentrect.bottom -= map->sbarwid;
      set_view_size(map->vp,
                          map->contentrect.right - map->contentrect.left,
                          map->contentrect.bottom - map->contentrect.top);
}

/* Adjust the appearance and thumb of the scroll bars to reflect the map.  This is
needed whenever the map is adjusted under program control, such as when magnifying
or scrolling to a specified location. */

void
set_map_scrollbars(Map *map)
{
      int hilite;
      
      /* Scale the scrollbars to circumvent the 32K limit on control values. This hack 
      solves the scrolling problem for big maps at high magnification. Note that all direct
      access of the scrollbar values from now on first must simulate the scaling that is 
      applied below in order to get correct values! */

      int   scaled_sxmin, scaled_sx, scaled_sxmax, scaled_symin, scaled_sy, scaled_symax;

      scaled_sxmin = map->vp->sxmin;
      scaled_sxmax = map->vp->sxmax;
      scaled_sx = map->vp->sx;

      while (scaled_sxmax > 32000) {      /* Keep dividing by 2 as long as necessary */
            scaled_sxmax /= 2;
            scaled_sxmin /= 2;      
            scaled_sx /= 2;
      }
      /* Apply the scaled values */
      SetCtlMin(map->hscrollbar, scaled_sxmin);
      SetCtlMax(map->hscrollbar, scaled_sxmax);
      SetCtlValue(map->hscrollbar, scaled_sx);

      /* Also scale the vertical scrollbar even though no current games are that high. */
      scaled_symin = map->vp->symin;
      scaled_symax = map->vp->symax;
      scaled_sy = map->vp->sy;

      while (scaled_symax > 32000) {      /* Keep dividing by 2 as long as necessary */
            scaled_symax /= 2;
            scaled_symin /= 2;      
            scaled_sy /= 2;
      }
      /* Apply the scaled values */
      SetCtlMin(map->vscrollbar, scaled_symin);
      SetCtlMax(map->vscrollbar, scaled_symax);
      SetCtlValue(map->vscrollbar, scaled_sy);

      set_content_rect(map);
}

/* Given a magnification power, look up and/or calculate the sizes of everything,
   in pixels. */

void
set_map_power(Map *map, int power)
{
      set_view_power(map->vp, power);

      /* Make sure the meridian interval is appropriate for the new
           magnification. */
      adjust_meridian_interval(map);

      if (power >= 4 && cellrgns[power] == nil)
        make_cell_clip(power);
}

void
adjust_meridian_interval(Map *map)
{
      /* Skip if meridians are not defined. */
      if (world.circumference <= 0)
            return;
      /* First set the interval so that it corresponds to 64 pixels. */
      map->vp->meridian_interval = 128 * 21600 / (world.circumference * map->vp->hw);
      /* Then adjust it upwards to an even number of degrees or minutes. */
      if (map->vp->meridian_interval <= 10)
                  map->vp->meridian_interval = 10;
      else if (map->vp->meridian_interval <= 30)
            map->vp->meridian_interval = 30;
      else if (map->vp->meridian_interval <= 60)
            map->vp->meridian_interval = 60;
      else if (map->vp->meridian_interval <= 120)
            map->vp->meridian_interval = 120;
      else if (map->vp->meridian_interval <= 300)
            map->vp->meridian_interval = 300;
      else if (map->vp->meridian_interval <= 600)
            map->vp->meridian_interval = 600;
      else if (map->vp->meridian_interval <= 1200)
            map->vp->meridian_interval = 1200;
      else if (map->vp->meridian_interval <= 3600)
            map->vp->meridian_interval = 3600;
      else  map->vp->meridian_interval = 0;
}

/* This is the old set_focus from maccmd.c moved here and renamed for consistency. */

void
set_map_focus(Map *map, int x, int y)
{
      int   oldsx, oldsy, newsx, newsy;

      if (!inside_area(x, y)) 
        return;
      /* Save the old vp. */
      oldsx = map->vp->sx;
      oldsy = map->vp->sy;
      /* Find the new vp. */
      set_view_focus(map->vp, x, y);
      m_center_on_focus(map);
      newsx = map->vp->sx;
      newsy = map->vp->sy;
      /* Restore the old vp. */
      map->vp->sx = oldsx;
      map->vp->sy = oldsy;
      /* Let scroll_map_window do the rest of the updating work. */     
      scroll_map_window(map, newsx - oldsx, newsy - oldsy);
}

/* This is the old content of do_set_view_angle moved here for consistency. */
/* The BeginUpdate/EndUpdate code has been removed to fix a rare display bug. */

void
set_map_angle(Map *map, int angle)
{
      /* Erase other-map boxes in other windows. */
      draw_related_maps(map);

#if 0       /* No longer works. Should figure out why. */
 
      /* Compute a suitable vertscale value. */
      map->vp->vertscale = max(500, area.maxelev) / 100 ;

#endif

      /* Change the view angle. */
      set_view_angle(map->vp, angle);

      /* Redraw everything. */
      force_map_update(map);

      /* Redraw other-map boxes in other windows. */
      draw_related_maps(map);
      
      /* Focus on active unit(s). */
      do_recenter(dside);
}

void
grow_map(Map *map, int w, int h)    /* Manual resizing by dragging the corner */
{
      Map *map2;

      /* Erase other-map boxes in other windows. */
      draw_related_maps(map);

      /* Set the window to the new size. */
      SizeWindow(map->window, w, h, FALSE);

      /* Resize scrollbars. */
      adjust_map_decor(map);

      /* Dont forget this little guy. */
      if (map->sbarwid < 15)
            draw_growicon_nolines(map->window);
      else  DrawGrowIcon(map->window);

      /* Update maps in background immediately. */
      for_all_maps(map2) {
            if (map2 != map)
              update_window(map2->window);
      }

      /* Update map content from gworld and then redraw gworld. */
      update_resized_map(map);

      /* Redraw other-map boxes in other windows. */
      draw_related_maps(map);
}

void
zoom_map(Map *map, int part)  /* Resizing by clicking in the zoom box */
{
      Map *map2;

      if (part == inZoomOut) {
            set_standard_state(map->window,
                  area.width  * map->vp->hw  + map->conw + map->sbarwid,
                  area.height * map->vp->hch + map->vp->hh - map->vp->hch + map->sbarwid + map->toph);
      }
      
      /* Erase other-map boxes in other windows. */
      draw_related_maps(map);

      /* Zoom the window. */
      ZoomWindow(map->window, part, false);

      /* Resize scrollbars. */
      adjust_map_decor(map);

      /* Dont forget this little guy. */
      if (map->sbarwid < 15)
            draw_growicon_nolines(map->window);
      else  DrawGrowIcon(map->window);

      /* Update maps in background immediately. */
      for_all_maps(map2) {
            if (map2 != map)
              update_window(map2->window);
      }

      /* Update map content from gworld and then redraw gworld. */
      update_resized_map(map);

      /* Redraw other-map boxes in other windows. */
      draw_related_maps(map);
}

/* Move and size the controls to be correct for the map. */

void
adjust_map_decor(Map *map)
{                       
      int w, h;
      WindowPtr mapwin = map->window;

      w = mapwin->portRect.right - mapwin->portRect.left;
      h = mapwin->portRect.bottom - mapwin->portRect.top;
      MoveControl(map->hscrollbar, map->conw - 1, h - map->sbarwid);
      SizeControl(map->hscrollbar, w - map->conw - map->sbarwid + 2, map->sbarwid + 1);
      MoveControl(map->vscrollbar, w - map->sbarwid, -1);
      SizeControl(map->vscrollbar, map->sbarwid + 1, h - map->sbarwid + 1 + 1);
}

void
invert_map(Map *map, int x, int y, int r)
{
      int sx, sy, sw, sh;
      Rect tmprect;
      GrafPtr     oldport;
      
      GetPort(&oldport);

      GO_OFFSCREEN(map);

      xform(map, x, y, &sx, &sy);

      SetRect(&tmprect, sx - (r * map->vp->hw) / 2, 
                                sy - (r * map->vp->hch) / 2,
                                sx + (r * map->vp->hw) / 2, 
                                sy + (r * map->vp->hch) / 2);

      InvertRect(&tmprect);

      RETURN_ONSCREEN(map);

      /* Copy tmprect to the screen */
      copy_from_gworld(map, tmprect);

      SetPort(oldport);
}

void
activate_map(map, activate)
Map *map;
int activate;
{
      EvQEl *myQElPtr;
      int below = FALSE;
      WindowPtr win;
      Rect growRect;
      Map *map2;
 
      if (activate) {
            /* Hilite controls in the activated map. */
            HiliteControl(map->vscrollbar, 0);
            HiliteControl(map->hscrollbar, 0);
            /* Make it the new frontmap. */
            if (map != worldmap) {
                  frontmap = map;
                  /* Unhilite the scrollbars in all maps below this one. This is necessary 
                  as the window manager cheks only if the next window below the current
                  one is active and should receive a deactivate event. Since we always hilite
                  the controls in the frontmap it may escape deactivation if located further 
                  down. */
                  for_all_windows(win) {
                        map2 = map_from_window(win);
                        if (map2 && below) {
                              HiliteControl(map2->vscrollbar, 255);
                              HiliteControl(map2->hscrollbar, 255);
                        }                 
                        /* Set the below flag. */
                        if (map2 == map)
                              below = TRUE;
                  }
            }
      } else {
            /* Check the current frontmap. */
            frontmap = front_map();
            /* Inactivate the map only if it lost frontmap status. */
            if (map != frontmap) {
                  HiliteControl(map->vscrollbar, 255);
                  HiliteControl(map->hscrollbar, 255);
            }
      }
}

/* Given a window, find the map that it belongs to. */

Map *
map_from_window(WindowPtr window)
{
      Map *map;
      
      if (dside == NULL)
        return NULL;
      for_all_maps(map) {
            if (map->window == window)
              return map;
      }
      return NULL;
}

/* Return the frontmost map, whether or not it's the frontmost window. This
function is now called only when the front map actually changes. The variable
frontmap is used everywhere else. Note that frontmap->window does not have
to be the FrontWindow, or even the (non-floating) topWindow. It can be any
window in the stack. Nevertheless, it is always active and responds to keyboard 
commands. This is non-standard behaviour for a Mac window, but it makes the 
play a lot easier. */

Map *
front_map()
{
      WindowPtr win;
      Map *map;

      /* Search all non-floating windows from top down 
      until we find a map. */
      for_all_windows(win) {
            if (IsFloating(win))
                continue;
            map = map_from_window(win);
            /* front_map is called once before the worldmap 
            is made floating! */ 
            if (map && map != worldmap)
                  return map;
      }
      return NULL;
}

void
force_map_update(map)
Map *map;
{
      update_grid_leakage(map);
      force_update(map->window);
}

void
update_grid_leakage(Map *map)
{
      if (map->erase_names                      /* Leaks into the grid. */
          || !grid_matches_unseen                     /* Can't use quick method. */
          || map->vp->angle != 90                     /* Can't use quick method.  */
          || map->vp->draw_plans                      /* Leaks into the grid. */    
          || map->vp->draw_ai                         /* Leaks into the grid. */
          || map->vp->draw_elevations)                /* Leaks into the grid. */
            map->leaky_grid = TRUE;
      else  map->leaky_grid = FALSE;
}

void
print_map(map)
Map *map;
{
      /* (should send postscript output from ps.c to printer port) */
}

/* Remove and destroy the map object. */

void
destroy_map(map)
Map *map;
{
      Map *map2;

      /* Erase other-map boxes in other windows */
      /* (must be done before disconnecting from list). */
      draw_related_maps(map);

      /* Disconnect it from the list. */  
      if (maplist == map) {
            maplist = map->next;
      } else {
            for_all_maps(map2) {
                  if (map2->next == map) {
                        map2->next = map->next;
                  }
                  /* Clean up the map and window numbers. */
                  /* Destroying the World Map does not change the map numbers. */
                  if (map != worldmap && map2->id > map->id) {
                        remove_window_menu_item(map2->window);
                        --(map2->id);
                        sprintf(spbuf, "Map %d", map2->id);
                        add_window_menu_item(spbuf, map2->window);
                  }
            }
      }
      free_vp(map->vp);
      free(map->selections);

      /* World map crash fix. */
      if (worldmap == map)
        worldmap = NULL;
      /* Decrement mapnum, but only if this was a normal map. */
      else 
         --mapnum;
      /* Always decrement nummaps. */
      --nummaps;
      /* Trash the gworld. */
      DisposeGWorld(map->gworldPortPtr);
      /* This is NOT done by close_window! */
      DisposeTheWindow(map->window);
      /* Find the new frontmap if necessary. */
      if (frontmap == map) {
          frontmap = front_map();
          /* Hilite its scrollbars if it exists. */
          if (frontmap) {
            HiliteControl(frontmap->vscrollbar, 0);
            HiliteControl(frontmap->hscrollbar, 0);
           }
      }
      free(map);
}

/* Display a map and all of its paraphernalia. */

void
draw_map(Map *map)
{
      GrafPtr     oldport;
      
      GetPort(&oldport);

      set_map_scrollbars(map);            
      set_content_rect(map);

      /* Draw map decor and info boxes */
      if (map->conw > 0)
        draw_control_panel(map);                            
      if (map->toplineh > 0)
        draw_top_line(map);
      if (map->topunith > 0)
        draw_unit_info(map);

      GO_OFFSCREEN(map);

      /* Draw grey area outside the world. */
      draw_window_background(map);
      /* Draw the grid/unseen color. */
      draw_area_background(map);
      /* Draw the actual content. */
      draw_map_content(map);

      RETURN_ONSCREEN(map);

      /* Copy the whole map content to the window. */
      copy_from_gworld(map, map->contentrect);                    
      
      /* Draw selected units.  This handles offscreen drawing on
         its own. */    
      draw_selections(map);

      SetPort(oldport);
}

/*
This draws the background color under both the grid/unseen color and the map cells
It is normally visible only outside the map area, where grid/unseen was never drawn
Note: map->contenrect replaced by portRect everywhere in draw_window_background!
*/

void
draw_window_background(Map *map)
{
      /* Replaces contentrect everywhere */
      Rect portRect = map->gworldPortPtr->portRect;

      /* If part of the window is entirely outside the world, we draw its shape on
         top of gray, otherwise window starts out all white. */
/*
      This is the normal option where a gray background is drawn. It assumes that 
      the grid/unseencolor later is drawn on top of it by draw_area_background.
*/
      if (area.width * map->vp->hw < 32000) {
            if (hasColorQD) {
                  RGBForeColor(&backcolor);
                  FillRect(&portRect, QDPat(black));
                  ForeColor(blackColor);
            } else
            switch (bggray) {
                  case blackgray:
                        FillRect(&portRect, QDPat(black));  break;
                  case darkgray:
                        FillRect(&portRect, QDPat(dkGray));  break;
                  case mediumgray:
                        FillRect(&portRect, QDPat(gray));  break;
                  case lightgray:
                        FillRect(&portRect, QDPat(ltGray));  break;
                  case whitegray:
                        FillRect(&portRect, QDPat(white));  break;
            }
/*
      This option is a hack for large maps at max magnification, where draw_area_background fails 
      to draw the grid/unseen color. The latter is therefore drawn as window background instead. 
*/
      } else {
            if (hasColorQD) {
                  /* Use gridcolor as background if everything is visible. */
                  if (grid_matches_unseen) {
                         RGBForeColor(&gridcolor);
                  } else RGBForeColor(&unseencolor);
                  PaintRect(&portRect);
                  RGBForeColor(&blackcolor);
            } else {
                  switch ((grid_matches_unseen ? gridgray : unseengray)) {
                        case blackgray:
                              FillRect(&portRect, QDPat(black));  break;
                        case darkgray:
                              FillRect(&portRect, QDPat(dkGray));  break;
                        case mediumgray:
                              FillRect(&portRect, QDPat(gray));  break;
                        case lightgray:
                              FillRect(&portRect, QDPat(ltGray));  break;
                        case whitegray:
                              FillRect(&portRect, QDPat(white));  break;
                  }
            }
      }
}

/* Draw the actual map data.  This is basically a matter of drawing n rows of terrain,
   each of an appropriate length, but it's a bit of trouble to get the number and
   lengths right.  Actually, it's easy to get approximate sizes, but it's important
   to try to draw as few cells as humanly possible. */

/* Note: In addition to the minor modification described below, draw_map_content has also been 
extensively trimmed down by deleting a lot of code for drawn-area-minimization which was 
incompatible with the offscreen graphics. */ 

void
draw_map_content(map)
Map *map;
{
      int   y1, y2, y, x1, x2, vx, vy, vw, vh;

      /* Compute the size of the region to render as the basic size of the viewport
         plus the scrolling buffer region. */ 
      vw = (map->vp->pxw + 2 * map->bufx) / map->vp->hw + 2;
      vh = (map->vp->pxh + 2 * map->bufy) / map->vp->hch + 4;

      /* Compute the bottom row to render. */
      vy = ((map->vp->totsh - map->vp->sy + map->toph) / map->vp->hch) - vh + 2;
      /* Now adjust the bottom row so it doesn't go outside the area. */
      if (vy < 0)
        vy = 0;
      if (vy > area.height - vh)
        vy = area.height - vh;
      /* Compute the leftmost "column". */
      vx = (map->vp->sx - map->conw) / map->vp->hw - vy / 2 - 1;
      DGprintf("Set %dx%d viewport at %d,%d\n", vw, vh, vx, vy);

      /* Compute top and bottom rows to be displayed. */
      y1 = min(vy + vh, area.height - 1);
      y2 = vy;

      /* Draw the rows, going from top to bottom. */
      for (y = y1; y >= y2; --y) {
            /* Adjust the right and left bounds to fill the viewport as
               much as possible, without going too far (the drawing code
               will clip, but clipped drawing is still expensive). */
            /* could test by drawing viewport rect as lines... */

            /* (-1) fixes occasional undrawn cells at left end of map. */
            x1 = vx - (y - vy) / 2 - 1;
            x2 = x1 + vw + 2;
            /* If the area doesn't wrap, then we might have to stop
               drawing before we reach the edge of the viewport. */
            if (area.xwrap) {
                  /* (should clip to visrgn, but tricky to avoid wrapping bugs) */
            } else {
                  /* Truncate x's to stay within the area. */
                  x1 = max(0, min(x1, area.width-1));
                  x2 = max(0, min(x2, area.width));
                  /* If this row is entirely in the NE corner, don't draw anything. */
                  if (x1 + y > area.width + area.halfheight)
                    continue;
                  /* If this row is entirely in the SW corner, don't draw anything. */
                  if (x2 + y < area.halfheight)
                    continue;
                  /* If the row ends up in the NE corner, shorten it. */
                  if (x2 + y > area.width + area.halfheight)
                    x2 = area.width + area.halfheight - y;
                  /* If the row starts out in the SW corner, shorten it. */
                  if (x1 + y < area.halfheight)
                    x1 = area.halfheight - y;
            }
            draw_row(map, x1, y, x2 - x1, FALSE);
      }
      /* Draw feature names if asked to. */
      if (features_defined() && map->featurenames && map->vp->hh > 3)
            draw_feature_names(map, map->gworldPortPtr->portRect);
      /* Now draw the lat-long grid if asked to do so. */
      if (map->vp->draw_meridians && map->vp->meridian_interval > 0)
             draw_meridians(map, map->gworldPortPtr->portRect);
}

/* This feature-based name drawing is more efficient unless thousands of features exist. */
/* Draws any feature names that might extend into the specified rect. */

void
draw_feature_names(Map *map, Rect drawrect)
{
      char        *str, buf[BUFSIZE];
      Rect        portrect;
      Feature     *feature;
      int         sx, sy;

      RGBForeColor(&featurecolor);
      for_all_features(feature) {

            /* This also catches all that junk at (0, 0)! */
            if (!inside_area(feature->x, feature->y))
                  continue;
            /* Don't reveal invisible features! */
            if (!terrain_visible(dside, wrapx(feature->x), feature->y))
                  continue;
            str= feature_desc(feature, buf); 
            /* Don't write empty names. */
            if (str == NULL)
                  continue;
            xform(map, feature->x, feature->y, &sx, &sy);

            /* Wrap sx if necessary. */
            while (area.xwrap && sx < 0)
                  sx += map->vp->sxmax;
            while (area.xwrap && sx > map->vp->sxmax)
                  sx -= map->vp->sxmax;

            /* Trigger drawing for any names that may extend into drawrect. */
            if (sx > drawrect.left - 100 && sx < drawrect.right + 100 
                && sy > drawrect.top - map->vp->hh && sy < drawrect.bottom) {
                  /* Use sy + hh/4 to prevent drawing on top of unit names. */
                  UNWRAP_SX(map, sx, 
                        draw_legend_text(sx + map->vp->hw/2, sy + map->vp->hh/4, 2 * map->vp->uh, 
                                             str, 0, map->textmasks, map->optimize_fonts));
            }
      }
      ForeColor(blackColor);
}

/* Now only draws meridians that fall within the passed drawrect. */

void
draw_meridians(Map *map, Rect drawrect)
{
      int   latmin, latcen, latmax, lonmin, loncen, lonmax, lat, lon;
      int   xfmin, xfmax, yfmin, yfmax, xmin, ymin, xmax, ymax;
      int   right, left, top, bottom, xend, xcen, ycen, incr, interval;
      int   latdeg, latmit, londeg, lonmit, x, y, xf, yf, sx, sy;
      char  mitbuf[4];
      Rect  portrect; 

      /* Don't draw meridians for flat or undefined worlds. */
      if (world.circumference <= 0)
            return;
      /* Compute the interval between meridians in pixels. */
      incr = map->vp->meridian_interval;
      interval = (incr * world.circumference * map->vp->hw) / 21600;
      
      /* Return if this interval is too small. */
      if (interval < 16)
            return;
            
      /* Set meridian color if not debugging graphics. */ 
      /* (in which case update_cell passes redColor). */
      if (!DebugG)
            RGBForeColor(&meridiancolor);
      
      /* Limit drawing to the map if it is smaller than the port. */
      portrect = map->gworldPortPtr->portRect;
      portrect.bottom   = min(portrect.bottom, map->vp->totsh);

      /* Compute min/max x & y values for drawrect. */
      nearest_cell(map->vp, drawrect.left - map->conw, 
                              drawrect.bottom -map->toph, 
                              &xmin, &ymin, &xfmin, &yfmin);
      nearest_cell(map->vp, drawrect.right - map->conw, 
                              drawrect.top - map->toph, 
                              &xmax, &ymax, &xfmax, &yfmax);

      /* Then convert the x & y values to longitudes and latitudes. */
      xy_to_latlong(xmin, ymin, xfmin, yfmin, &latmin, &lonmin);
      xy_to_latlong(xmax, ymax, xfmax, yfmax, &latmax, &lonmax);

      /* Adjust grid so that origin is at Greenwich & Equator. */
      lonmin -= lonmin % incr;
      latmin  -= latmin % incr;
      lonmax -= lonmax % incr;
      latmax  -= latmax % incr;

      /* Start longitude drawing at Greenwich if the area is wrapped. */
      if (area.xwrap) {
            lonmin = 0;
            lonmax = 21600 - 21600 % incr;
      }

      /* Handles non-cylindrical areas that cross the dateline. */
      else if (lonmax < lonmin)
            lonmax += 21600;

      /* Used in clipping code. */
      xend = area.width - 1;
      xcen = area.halfwidth;
      ycen = area.halfheight;
      
      
      /* Compute latitiude and longitude of area center. */
      xy_to_latlong(xcen, ycen, 0, 0, &latcen, &loncen);

      /* Draw the latitiudes. */
      for (lat = latmin; lat <= latmax; lat += incr) {
            /* loncen is used since all latitudes cross it. */
            latlong_to_xy(lat, loncen, &x, &y, &xf, &yf);
            /* Skip if not inside area. */
            if (!inside_area(wrapx(x), y))
                  continue;
            xform_fractional_flat(map, x, y, xf, yf, &sx, &sy);
            /* Skip latitudes that fall outside drawrect. */ 
            if (sy < drawrect.top || sy > drawrect.bottom)
                  continue;
            /* Don't draw latitudes where the longitude text will be. */
            if (sy - portrect.top < 16 || portrect.bottom - sy < 16)
                  continue;
            /* Clip corners to area for non-wrapped worlds. */
            if (!area.xwrap) {
                  /* Compute left endpoint. */
                  if (in_area(0,y)) {                                         
                         xform(map, 0, y, &left, 0);
                         left += 32;
                  } else if (in_area(ycen - y, y)) {                          
                         xform(map, ycen - y, y, &left, 0);
                         left += 32;
                  }
                  /* Compute right endpoint. */
                  if (in_area(xend + ycen - y, y)) {                    
                         xform(map, xend + ycen - y, y, &right, 0);
                         right -= 32 - map->vp->hw;
                  } else if (in_area(xend, y)) {                              
                         xform(map, xend, y, &right, 0);
                         right -= 32 - map->vp->hw;
                  }
                  /* Make room for text at left end of gworld. */
                  left = max(left, portrect.left + 32);
                  /* Make room for text at right end of gworld. */
                  right = min(right, portrect.right - 32);
            } else { 
                  /* Always make room at left end of gworld. */
                  left = portrect.left + 32;
                  /* Make room at right end unless the map is wrapped. */
                  if (!map->xwrap) 
                        right = portrect.right - 32;
                  else  right = portrect.right;
            }

            /* Also clip to drawrect. */
            left = max(left, drawrect.left);
            right = min(right, drawrect.right);

            /* Draw the latitude. */
            MoveTo(left, sy);
            LineTo(right, sy);
            
            /* Skip the rest if no latitude text is necessary. */       
            if (left == drawrect.left && right == drawrect.right)
                  continue;
            /* Build the text string. */
            latdeg = abs(lat) / 60;
            latmit = abs(lat) % 60;
            mitbuf[0] = '\0';
            if (latmit != 0) sprintf(mitbuf, "%d", latmit);
            sprintf(tmpbuf, "%d%s", latdeg, mitbuf);
            if (!lat) strcat(tmpbuf, "  ");
            else if (lat > 0) strcat(tmpbuf, "N");
            else if (lat < 0) strcat(tmpbuf, "S");
            /* Draw latitude text at left edge if there is room for it. */
            if (left != drawrect.left)
                  draw_legend_text(left - 3, sy, 12, tmpbuf, +1, 0, 0);
            /* Draw latitude text at right edge if there is room for it. */
            if (right != drawrect.right)
                  draw_legend_text(right + 3, sy, 12, tmpbuf, -1, 0, 0);
      }

      /* Draw the longitudes. */
      for (lon = lonmin; lon <= lonmax; lon += incr) {
            /* Save correct longitude for text if crossing dateline. */
            int loncor = lon;
            /* Make room for top and bottom text. */
            top = portrect.top + 16; 
            bottom = portrect.bottom - 16;
            /* latcen is used since all longitudes cross it. */
            latlong_to_xy(latcen, lon, &x, &y, &xf, &yf);
            /* Skip if not inside area. */
            if (!inside_area(wrapx(x), y))
                  continue;
            xform_fractional_flat(map, x, y, xf, yf, &sx, &sy);
            /* Wrap sx if necessary. */
            while (area.xwrap && sx < 0)
                  sx += map->vp->sxmax;
            while (area.xwrap && sx > map->vp->sxmax)
                  sx -= map->vp->sxmax;
            /* Skip longitudes outside drawrect. */ 
            if ((sx < drawrect.left || sx > drawrect.right))
                  continue;
            /* Clip corners to area for non-wrapped maps. */
            if (!area.xwrap) {
                  /* Compute top endpoint. */
                  if (in_area(0,  ycen + 2 * x - 1))                                
                         xform(map, 0, ycen + 2 * x - 1, 0, &top);
                  else if (in_area(2 * x - xend, ycen + 2 * (xend - x) - 1))  
                         xform(map, 2 * x - xend, ycen + 2 * (xend - x) - 1, 0, &top);
                  /* Compute bottom endpoint. */
                  if (in_area(2 * x, ycen - 2 * x))                           
                         xform(map, 2 * x, ycen - 2 * x, 0, &bottom);   
                  else if (in_area(xend, ycen - 2 * (xend - x)))                    
                         xform(map, xend, ycen - 2 * (xend - x), 0, &bottom);
            }
            /* Make sure again there is room for top and bottom text. */
            top = max(portrect.top + 16, top);
            bottom = min(portrect.bottom - 16, bottom);

            /* Clip to the redrawn area. */
            top = max(drawrect.top, top);
            bottom = min(drawrect.bottom, bottom);
            /* Fixes garbage outside area. */
            if (top > bottom)
                  continue;

            /* Draw the longitude. */
            MoveTo(sx, top);
            LineTo(sx, bottom);
            
            /* Skip the rest if no longitude text is necessary. */            
            if (top == drawrect.top && bottom == drawrect.bottom)
                  continue;
            /* Restore correct longitude if we passed the dateline. */        
            if (lon > 10800)
                 loncor -= 21600;         
            /* Build the text string. */
            londeg = abs(loncor) / 60;
            lonmit = abs(loncor) % 60;
            mitbuf[0] = '\0';
            if (lonmit != 0) sprintf(mitbuf, "%d", lonmit);
            sprintf(tmpbuf, "%d%s", londeg, mitbuf);
            /* Use restored correct longitude. */
            if (loncor > 0) strcat(tmpbuf, "E");
            if (loncor < 0) strcat(tmpbuf, "W");
            /* Draw longitude text at top. */
            if (top == portrect.top + 16) {
                  draw_legend_text(sx, top - 6, 12, tmpbuf, 0, 0, 0);
                  /* Repeat on BOTH sides (double UNWRAP). */
                  if (area.xwrap && sx < 32) 
                        draw_legend_text(sx + map->vp->sxmax, top - 6, 12, tmpbuf, 0, 0, 0);
                  else if (area.xwrap && sx + 32 > map->vp->sxmax)
                        draw_legend_text(sx - map->vp->sxmax, top - 6, 12, tmpbuf, 0, 0, 0);
            }
            /* Draw longitude text at bottom. */
            if (bottom == portrect.bottom - 16){
                  draw_legend_text(sx, bottom + 6, 12, tmpbuf, 0, 0, 0);
                  /* Repeat on BOTH sides (double UNWRAP). */
                  if (area.xwrap && sx < 32) 
                        draw_legend_text(sx + map->vp->sxmax, bottom + 6, 12, tmpbuf, 0, 0, 0);
                  else if (area.xwrap && sx + 32 > map->vp->sxmax)
                        draw_legend_text(sx - map->vp->sxmax, bottom + 6, 12, tmpbuf, 0, 0, 0);
            }
      }
      ForeColor(blackColor);
}

/* This draws a hexagon or rectangle that covers the totality of the area, whether
   discovered or not. */

void 
draw_area_background(Map *map)
{
      int sx, sy, llx, lly, lrx, lry, rx, ry, urx, ury, ulx, uly, lx, ly;
      PolyHandle poly;
      Rect arearect;

      /* Don't bother if area magnified greatly. */
      /* (should fix to not try to draw giant rects, but still draw something reasonable.
         note that otherwise grid color may be wrong) */
/*
      LineTo uses shorts and does not work over 32K, so a giant hex cannot be drawn.
      If the area.wrap option is used instead with a large map, xform will freak out!
      The grid/unseen color for large maps is therfore drawn as window background
      instead (see draw_window_background).
*/
      if (area.width * map->vp->hw > 32000)
        return;
      if (area.xwrap) {
            /* Area is cylinder; draw a rectangle. */
            xform(map, 0, area.height-1, &sx, &sy);
            /* Make sure the whole gworld gets a background. */
            arearect.left = map->gworldPortPtr->portRect.left;
        arearect.top = sy;
            xform(map, 0, 0, &sx, &sy);
            /* Make sure the whole gworld gets a background. */
            arearect.right = map->gworldPortPtr->portRect.right;
            arearect.bottom = sy;
            /* If there's scrolling, then draw background under the extra scrolling
               area. */
            if (map->vp->sxmin != map->vp->sxmax)
              arearect.right += map->vp->pxw;
            /* Adjust so that edges of the rect pass through the middles of top/bottom edge cells. */
            OffsetRect(&arearect, 0, map->vp->hh / 2);
            if (hasColorQD) {
                  /* If everything is visible, use gridcolor for background. */
                  if (grid_matches_unseen) {
                         RGBForeColor(&gridcolor);
                  } else {
                        RGBForeColor(&unseencolor);
                  }
                  PaintRect(&arearect);
                  RGBForeColor(&blackcolor);
            } else {
                  /* Try to use gray patterns if b/w. */
                  switch (gridgray) {
                        case blackgray:
                              FillRect(&arearect, QDPat(black));
                              break;
                        case darkgray:
                              FillRect(&arearect, QDPat(dkGray));
                              break;
                        case mediumgray:
                              FillRect(&arearect, QDPat(gray));
                              break;
                        case lightgray:
                              FillRect(&arearect, QDPat(ltGray));
                              break;
                        case whitegray:
                              FillRect(&arearect, QDPat(white));
                              break;
                  }
            }
      } else {
            /* Area is hexagon; draw a hexagon. */
            /* (should make once and save?) */
            poly = OpenPoly();            
            xform(map, 0 + area.halfheight, 0, &llx, &lly);
            MoveTo(llx, lly);
            xform(map, area.width-1, 0, &lrx, &lry);
            LineTo(lrx, lry);
            xform(map, area.width-1, area.halfheight, &rx, &ry);
            LineTo(rx, ry);
            xform(map, area.width-1 - area.halfheight, area.height-1, &urx, &ury);
            LineTo(urx, ury);
            xform(map, 0, area.height-1, &ulx, &uly);
            LineTo(ulx, uly);
            xform(map, 0, area.halfheight, &lx, &ly);
            LineTo(lx, ly);
            LineTo(llx, lly);
            ClosePoly();
            /* Adjust so that edges of the polygon pass through the middles of edge cells. */
            OffsetPoly(poly, map->vp->hw/2, map->vp->hh/2);
            if (hasColorQD) {
                  if (grid_matches_unseen) {
                         RGBForeColor(&gridcolor);
                  } else {
                        RGBForeColor(&unseencolor);
                  }
                  PaintPoly(poly);
                  RGBForeColor(&blackcolor);
            } else {
                  switch (gridgray) {
                        case blackgray:
                              FillPoly(poly, QDPat(black));
                              break;
                        case darkgray:
                              FillPoly(poly, QDPat(dkGray));
                              break;
                        case mediumgray:
                              FillPoly(poly, QDPat(gray));
                              break;
                        case lightgray:
                              FillPoly(poly, QDPat(ltGray));
                              break;
                        case whitegray:
                              FillPoly(poly, QDPat(white));
                              break;
                  }
            }
            /* Free up the space for this hexagon. */
            KillPoly(poly);
      }
#if 0  /* The idea of a shaded border seems nice, but it doesn't look very good in practice. */
      for (x = 0; x < area.width; ++x) {
            xform(map, x, 0, &sx, &sy);
            draw_border_line(sx, sy, SW, map->vp->power, -1);
            draw_border_line(sx, sy, SE, map->vp->power, -2);
      }
      PenNormal();
#endif
}

/* Draw the map control panel as a pair of PICTs. */

void
draw_control_panel(map)
Map *map;
{
      int winhgt, basev;
      Rect tmprect;
      GrafPtr oldport;

      GetPort(&oldport);
      SetPort(map->window);
      winhgt = (map->window->portRect).bottom - (map->window->portRect).top;
      SetRect(&tmprect, 0, 0, map->conw, winhgt);
      FillRect(&tmprect, QDPat(white));
      MoveTo(map->conw - 1, 0);  Line(0, winhgt);
      if (tlcontrols == nil) {
            tlcontrols = (PicHandle) GetResource('PICT', pMapControlsTL);
      }
      if (tlcontrols != nil) {
            SetRect(&tmprect, 0, 0,
                        picture_width(tlcontrols), picture_height(tlcontrols));
            DrawPicture(tlcontrols, &tmprect);
      }
      if (blcontrols == nil) {
            blcontrols = (PicHandle) GetResource('PICT', pMapControlsBL);
      }
      if (blcontrols != nil) {
            SetRect(&tmprect, 0, winhgt - picture_height(blcontrols) + 1,
                        picture_width(blcontrols), winhgt);
            DrawPicture(blcontrols, &tmprect);
      }
      if (map->moveonclick && map->autoselect) {
            SetRect(&tmprect, 4, 5, 26, 26);
            InvertRect(&tmprect);
      }
      /* (should modify appearance of top left arrow buttons to reflect abilities) */
      basev = 32 + 5*15 + 2 + 5/*why?*/ + 1;
      SetRect(&tmprect, 0, basev, 30, basev + 10);
      if (map->vp->draw_grid) {
            InvertRect(&tmprect);
      }
      OffsetRect(&tmprect, 0, 11);
      if (map->vp->draw_names) {
            InvertRect(&tmprect);
      }
      OffsetRect(&tmprect, 0, 11);
      if (map->vp->draw_people) {
            InvertRect(&tmprect);
      } else if (!people_sides_defined()) {
            gray_out_rect(&tmprect);
      }
      OffsetRect(&tmprect, 0, 11);
      if (map->vp->draw_control) {
            InvertRect(&tmprect);
      } else if (!control_sides_defined()) {
            gray_out_rect(&tmprect);
      }
      OffsetRect(&tmprect, 0, 11);
      if (map->vp->draw_plans) {
            InvertRect(&tmprect);
      }
      OffsetRect(&tmprect, 0, 11);
      if (map->vp->draw_ai) {
            InvertRect(&tmprect);
      } else if (!side_has_ai(dside)) {
            /* (should ensure that this is updated when side gets an AI) */
            gray_out_rect(&tmprect);
      }
      OffsetRect(&tmprect, 0, 11);
      if (map->see_all) {
            InvertRect(&tmprect);
      } else if (!dside->may_set_show_all) {
            gray_out_rect(&tmprect);
      }
      OffsetRect(&tmprect, 0, 11);
      if (map->sidecolors) {
            InvertRect(&tmprect);
      }
      OffsetRect(&tmprect, 0, 11);
      if (map->iconmasks) {
            InvertRect(&tmprect);
      }
      /* Draw state of bottom left control buttons. */
      if (map->vp->power <= 0) {
            SetRect(&tmprect, 0, winhgt - 15, 15, winhgt);
            gray_out_rect(&tmprect);
      }
      if (map->vp->power >= NUMPOWERS - 1) {
            SetRect(&tmprect, 16, winhgt - 15, 30, winhgt);
            gray_out_rect(&tmprect);
      }
      SetPort(oldport);
}

/* Draw a single line of the most important info - the current date or turn,
   and a textual description of what the mouse is over. */

void
draw_top_line(Map *map)
{
      int numchars, strwid;
      GrafPtr oldport;
      Rect tmprect;

      /* Set the port. */
      GetPort(&oldport);
      SetPort(map->window);
      /* Set up to draw the current date. */
      TextSize(small_font_size);
      TextFont(small_font_id);
      numchars = strlen(curdatestr);
      strwid = TextWidth(curdatestr, 0, numchars);
      /* Clear the whole topline area. */
      SetRect(&tmprect, map->conw, 0, map->conw + map->vp->pxw, map->toplineh - 1);
      FillRect(&tmprect, QDPat(white));
      /* Now draw the date. */
      MoveTo(tmprect.right - strwid - 3, tmprect.top + 11);
      DrawText(curdatestr, 0, numchars);
      if (mouseover != NULL) {
            /* Draw description of what the mouse is over. */
            /* (should clip to avail space) */
            numchars = strlen(mouseover);
            MoveTo(map->conw + 3, tmprect.top + 11);
            DrawText(mouseover, 0, numchars);
      }
      /* Draw a line dividing this from other map content. */
      MoveTo(tmprect.left, tmprect.bottom);
      Line(tmprect.right - tmprect.left, 0);
      /* Restore grafport. */
      SetPort(oldport);
}

void
draw_unit_info(Map *map)
{
      int mrow, u, m;
      Rect tmprect;
      Unit *unit;
      char infobuf[100];
      GrafPtr oldport;
      RgnHandle tmprgn;

      if (map->topunith <= 0)
        return;
      GetPort(&oldport);
      SetPort(map->window);
      /* Save the current clipping region. */
      tmprgn = NewRgn();
      GetClip(tmprgn);
      SetRect(&tmprect, map->conw, map->toplineh, map->conw + map->vp->pxw, map->toph);
      ClipRect(&tmprect);
      /* Clear the whole area. */
      FillRect(&tmprect, QDPat(white));
      if (map->numselections == 1) {
            unit = map->selections[0];
            if (in_play(unit)) {
                  u = unit->type;
                  sprintf(infobuf, "%s", unit_handle(dside, unit));
                  draw_info_text(map, 0, 0, 0, infobuf);
                  /* Describe its current location. */
                  location_desc(infobuf, dside, unit, unit->type, unit->x, unit->y);
                  draw_info_text(map, 0, 1, 0, infobuf);
                  /* Very briefly list the numbers and types of the occupants. */
                  occupants_desc(infobuf, unit);
                  draw_info_text(map, 0, 2, 0, infobuf);
                  /* Describe the "important" parameters. */
            hp_desc(infobuf, unit, TRUE);
            strcat(infobuf, "   ");
            acp_desc(infobuf + strlen(infobuf), unit, TRUE);
                  draw_info_text(map, 50, 0, 0, infobuf);
                  /* List other stack members that are here. */
                  others_here_desc(infobuf, unit);
                  draw_info_text(map, 50, 1, 0, infobuf);
                  mrow = 0;
                  while (supply_desc(infobuf, unit, mrow)) {
                        draw_info_text(map, 50, mrow + 2, 0, infobuf);
                        ++mrow;
                  }
                /* Describe the current plan and task agenda. */
                if (unit->plan) {
                        int row = 3, n;
                        Task *task;

                        plan_desc(infobuf, unit);
                        draw_info_text(map, 0, row++, 0, infobuf);
                        n = 0;
                        infobuf[0] = '\0';
                        for_all_tasks(unit->plan, task) {
                              if (n > 0 && n % 2 == 0) {
                                    draw_info_text(map, 0, row, 0, infobuf);
                                    infobuf[0] = '\0';
                                    row++;
                              }
                              task_desc(infobuf+strlen(infobuf), unit->side, unit, task);
                              if (task->next != NULL)
                                strcat(infobuf, "; ");
                              ++n;
                        }
                        if (strlen(infobuf) > 0)
                          draw_info_text(map, 0, row, 0, infobuf);
                }
            }
      } else if (map->numselections > 1) {
            draw_info_text(map, 0, 0, 0, "(multiple units)");
      }
      /* Draw a line dividing this from the map content. */
      MoveTo(tmprect.left, tmprect.bottom - 1);
      Line(tmprect.right - tmprect.left, 0);
      /* Restore clipping region. */
      SetClip(tmprgn);
      DisposeRgn(tmprgn);
      /* Restore grafport. */
      SetPort(oldport);
}

static void
draw_info_text(map, x, y, len, buf)
Map *map;
int x, y, len;
char *buf;
{
      int sx, sy;
      Rect tmprect;

    if (empty_string(buf))
      return;
      SetRect(&tmprect, map->conw, map->toplineh, map->conw + map->vp->pxw, map->toph);
    /* Translate a 0-100 value for x to pixels. */
    if (x == 50)
      sx = tmprect.left + (tmprect.right - tmprect.left) / 2;
    else
      sx = tmprect.left + 3;
    sy = tmprect.top + small_line_spacing + y * (small_line_spacing + 1);
    if (len > 0 && strlen(buf) > len)
      buf[len-1] = '\0';
      MoveTo(sx, sy);
      TextFont(small_font_id);
      TextSize(small_font_size);
      DrawText(buf, 0, strlen(buf));
}

/* This function is now only called when the current port is an offscreen gworld. It it called by  GO_OFFSCREEN, to 
   erase the other-map boxes, and then again by RETURN_ONSCREEN, to redraw the other-map boxes. This makes 
   sure that the other-map boxes always are preserved during offscreen drawing. */

void
draw_other_maps(map)
Map *map;
{
      Map *map2;

      for_all_maps(map2) {
            if (map != map2 /* && reasonable to show? */) {
                  draw_other_map(map, map2);
            }
      }
}

/* Draws both offscreen and on screen! */

/* This and the selection animation are the only two cases where things are drawn directly
   to the screen. draw_related_maps now also clips drawing correctly to the map content. */

void
draw_related_maps(Map *map)
{
      Map *map2;
      RgnHandle tmprgn;
      Rect tmpRect;
      GrafPtr oldport;

      /* Very important here (unlike most other drawing functions) */
      /* because of the SetPort(map2->window) call below! */
      GetPort(&oldport);

      for_all_maps(map2) {
            if (map != map2 && map2->vp->draw_other_maps) {

                  SetPort(map2->window);

                  /* Clip related map box to content of other window */
                  tmprgn = NewRgn();
                  GetClip(tmprgn);
                  ClipRect(&map2->contentrect);

                  /* Draw related map box onscreen */
                  draw_other_map(map2, map);

                  GO_OFFSCREEN(map2);

                  /* Always draw it offscreen as well! */
                  draw_other_map(map2, map);

                  RETURN_ONSCREEN(map2);

                  SetClip(tmprgn);
                  DisposeRgn(tmprgn);
            }
      }
      SetPort(oldport);
}

void
draw_other_map(Map *map, Map *map2)
{
      int sx, sy, sw, sh, wsx;
      Rect tmpRect;

      /* Scale the viewports */
      scale_vp(map->vp, map2->vp, &sx, &sy, &sw, &sh);
      /* Handle non-xwrapped world */
      wsx = sx;
      /* Now draw the other map's box */
      SetRect(&tmpRect, wsx, sy, wsx + sw, sy + sh);
      OffsetRect(&tmpRect, map->conw, map->toph);
      if (map->vp->hw < 8)
        PenSize(2, 2);
      PenMode(patXor);
      FrameRect(&tmpRect);

      /* UNWRAP the box. */
      if (area.xwrap) {
            /* Draw one extra copy to the left if necessary. */
            if (tmpRect.right > map->vp->sxmax) {
                  OffsetRect(&tmpRect, -map->vp->sxmax, 0);
                  FrameRect(&tmpRect);
                  OffsetRect(&tmpRect, map->vp->sxmax, 0);
            }           
            /* Draw one extra copy to the right if necessary. */
            if (tmpRect.left < 0) {
                  OffsetRect(&tmpRect, map->vp->sxmax, 0);
                  FrameRect(&tmpRect);
            }           
            /* Draw more copies to the right ON SCREEN if necessary. */
            if (QD(thePort) == map->window) {
                  int rest = map->vp->pxw - tmpRect.left;
                  while (rest > 0) {
                        OffsetRect(&tmpRect, map->vp->sxmax, 0);
                        FrameRect(&tmpRect);
                        rest -= map->vp->sxmax;
                  } 
            }
      }
      PenNormal();
}

void
draw_unit_blast(Map *map, Unit *unit, int blast)
{
      int sx, sy, sw, sh;
      Rect destRect;
      GrafPtr     oldport;
      
      /* Skip this for undrawn units. */
      if (!cell_is_in_gworld(map, unit->x, unit->y))
            return;

      GetPort(&oldport);

      GO_OFFSCREEN(map);

      /* Find offscreen coordinates of the unit rect */
      m_xform_unit(map, unit, &sx, &sy, &sw, &sh);

      /* Make sure the blast stays within the cell. */
      if (sw > map->vp->uw) {
            sw = map->vp->uw;
            sh = map->vp->uh;
            sx += (map->vp->hw - map->vp->uw) / 2;
            sy += (map->vp->hw - map->vp->uw) / 2;
      }
      draw_blast_image(map->window, sx, sy, sw, sh, blast);

      RETURN_ONSCREEN(map);
      
      /* Dont try to copy a blasted unit outside the map! */
      if (!in_area(unit->x, unit->y))
        return;
      /* Find onscreen coordinates of the unit rect */
      m_xform_unit(map, unit, &sx, &sy, &sw, &sh);

      /* Set copy destination to the unit rect */
      SetRect(&destRect, sx, sy, sx + sw, sy + sh);
      /* Now copy offscreen map to window */
      copy_from_gworld(map, destRect);                

      SetPort(oldport);
}

/* Draw all the selections of all the units. */

void
draw_selections(Map *map)
{
      int sx, sy, sw, sh, i;
      Unit *unit;
      Rect destRect;
      GrafPtr     oldport;

      GetPort(&oldport);

      GO_OFFSCREEN(map);

      for (i = 0; i < map->numselections; ++i) {
            unit = map->selections[i];
            draw_selected_unit(map, unit);
      }

      RETURN_ONSCREEN(map);
      
      for (i = 0; i < map->numselections; ++i) {
            unit = map->selections[i];

            /* Skip this also for undrawn units within the map. */
            if (!cell_is_in_gworld(map, unit->x, unit->y))
                  break;
            /* Find the pixel coordinates of (x, y). */
            xform(map, unit->x, unit->y, &sx, &sy);
            /* Copy the whole cell in case this was an occ within a big box. */
            SetRect(&destRect, sx, sy, sx + map->vp->hw, sy + map->vp->hh);
            /* Now copy offscreen map to window. */
            copy_from_gworld(map, destRect);                
      }
      /* Update the unit info row. */
      if (map->numselections > 0) {
            draw_unit_info(map);
      }
      SetPort(oldport);
}

/* Draw all the selected units in the given cell. */

void
draw_selections_at(Map *map, int x, int y)
{
      int xw, sx, sy, i;
      Unit *unit;
      Rect destRect;
      GrafPtr     oldport;

      /* Skip this for undrawn units. */
      if (!cell_is_in_gworld(map, x, y))
            return;

      xw = wrapx(x);
      GetPort(&oldport);

      GO_OFFSCREEN(map);

      for (i = 0; i < map->numselections; ++i) {
            unit = map->selections[i];
            if (unit && unit->x == xw && unit->y == y) {
                  draw_selected_unit(map, unit);
            }
      }

      RETURN_ONSCREEN(map);
      
      /* Find the pixel coordinates of (x, y) */
      xform(map, x, y, &sx, &sy);
      /* Set destRect to enclose the redrawn cell */
      SetRect(&destRect, sx, sy, sx + map->vp->hw, sy + map->vp->hh);
      /* Now copy offscreen map to window */
      copy_from_gworld(map, destRect);                

      /* Update unit info line */
      if (map->numselections > 0) {
            unit = map->selections[0];
            if (in_play(unit) && unit->x == xw && unit->y == y) {
                  draw_unit_info(map);
            }
      }
      SetPort(oldport);
}

/*

The code now handles case with transport + occ and also third unit correctly, by using the transports 
enclosing box rather than the whole cell as selection box. Moreover, the selected occ is drawn at normal
magnification, instead of filling up the whole box (the latter caused a clutter of icons on top of each other).
The fact that a specific occ is selected is instead indicated by drawing the emblem for that particular occ, 
in addition to the transports emblem. The magnification lines have been removed since they are not needed
anymore, and since they caused problems with leakage into the grid. 

*/

void
draw_selected_unit(map, unit)
Map *map;
Unit *unit;
{
      int sx, sy, sw, sh, size;
      Rect tmprect;

      if (!in_play(unit))
        return; /* unselect it too? */
      if (map->vp->uw > 16
          || g_bigicons() == TRUE) {

            /* Make sure the image is drawn in smaller self box */
            m_xform_unit_self(map, unit, &sx, &sy, &sw, &sh);

            /* This is to make sure occs are drawn exactly as in draw_unit_occss where
            a vertical offset is used to accomodate a smaller box. */
            if (unit->transport)
                   sy -= 2;

            /* Draw the selected unit. */
            UNWRAP_SX(map, sx, 
                  draw_unit_image(map->window, sx, sy, sw, sh, unit->type, side_number(unit->side), 
                                       map->sidecolors, TRUE, !completed(unit), map->draw_emblems));

            /* Draw unit size on top of advanced unit. */
            if (u_advanced(unit->type) && map->drawsizes) { 
                  UNWRAP_SX(map, sx, 
                        draw_unit_size(unit, map->window, sx, sy, sw, map->vp->uh));
            }
            /* Then use bigger box enclosing also occs as for size and selection box. */
            m_xform_unit(map, unit, &sx, &sy, &sw, &sh);
            
            /* Also use the box enclosing transport + occs for selected occ, NOT the whole cell */
            if (map->numselections == 1
                && sw < 16
                && (unit->transport != NULL) ) {
                  m_xform_unit(map, unit->transport, &sx, &sy, &sw, &sh);
            }
      } else {
            xform(map, unit->x, unit->y, &sx, &sy);
            /* Adjust to unit part of cell. */
            sx += (map->vp->hw - map->vp->uw) / 2;  
            sy += (map->vp->hh - map->vp->uh) / 2;
            sw = map->vp->uw;  sh = map->vp->uh;
            /* Be sure the selected unit is drawn. */
            UNWRAP_SX(map, sx, 
                  draw_unit_image(map->window, sx, sy, sw, sh, unit->type, side_number(unit->side), 
                                       map->sidecolors, TRUE, !completed(unit), map->draw_emblems));
                                                                              
            /* Draw unit size on top of advanced unit. */
            if (u_advanced(unit->type) && map->drawsizes) { 
                  UNWRAP_SX(map, sx, 
                        draw_unit_size(unit, map->window, sx, sy, sw, sh));
            }
      }
      /* We are now done if using selection masks. */
      if (map->selectionmasks)
          return;
      if (0 /* not actually within visible area */)
        return;
      /* Indicate a unit's plans/tasks in some useful fashion. */
      if (map->vp->draw_plans
            && unit->plan
            && unit->plan->tasks) {
            int sx2, sy2, sx3, sy3, rad;
            Task *task = unit->plan->tasks, *nexttask;

            if (task != NULL) {
                  if ((nexttask = task->next) != NULL) {
                        /* Draw the next task first so it can be overwritten by info
                           about the current task. */
                        switch (nexttask->type) {
                              case TASK_MOVE_TO:
                              case TASK_HIT_UNIT:
                                    if (in_area(nexttask->args[0], nexttask->args[1])) {
                                          xform(map, nexttask->args[0], nexttask->args[1], &sx3, &sy3);
                                          if (task->type == TASK_MOVE_TO) {
                                                xform(map, task->args[0], task->args[1], &sx2, &sy2);
                                                sx2 += map->vp->hw/2;  sy2 += map->vp->hh/2;
                                          } else {
                                                sx2 = sx + sw/2;  sy2 = sy + sh/2;
                                          }
                                          PenPat(QDPat(ltGray));
                                          MoveTo(sx2, sy2);
                                          LineTo(sx3 + map->vp->hw/2, sy3 + map->vp->hh/2);
                                          rad = nexttask->args[3];
                                          if (nexttask->type == TASK_MOVE_TO && rad > 1) {
                                                Rect tmprect;

                                                MoveTo(sx3 - rad * map->vp->hh + map->vp->hw/2, sy3 + map->vp->hh/2);
                                                LineTo(sx3 + rad * map->vp->hh + map->vp->hw/2, sy3 + map->vp->hh/2);
                                                MoveTo(sx3 + map->vp->hw/2, sy3 - rad * map->vp->hw + map->vp->hh/2);
                                                LineTo(sx3 + map->vp->hw/2, sy3 + rad * map->vp->hw + map->vp->hh/2);
                                                tmprect.left  = sx3 - rad * map->vp->hh;  tmprect.top    = sy3 - rad * map->vp->hw;
                                                tmprect.right = sx3 + rad * map->vp->hh;  tmprect.bottom = sy3 + rad * map->vp->hw;
                                                OffsetRect(&tmprect, map->vp->hw/2, map->vp->hh/2);
                                                UNWRAP_RECT(map, tmprect, 
                                                      FrameOval(&tmprect));
                                          }
                                          PenNormal();
                                    }
                        }
                  }
                  switch (task->type) {
                        case TASK_MOVE_TO:
                        case TASK_HIT_UNIT:
                              if (in_area(task->args[0], task->args[1])) {
                                    xform(map, task->args[0], task->args[1], &sx2, &sy2);
                                    PenPat(QDPat(dkGray));
                                    MoveTo(sx + sw/2, sy + sh/2);
                                    LineTo(sx2 + map->vp->hw/2, sy2 + map->vp->hh/2);
                                    rad = task->args[3];
                                    if (task->type == TASK_MOVE_TO && rad > 1) {
                                          Rect tmprect;

                                          MoveTo(sx2 - rad * map->vp->hh + map->vp->hw/2, sy2 + map->vp->hh/2);
                                          LineTo(sx2 + rad * map->vp->hh + map->vp->hw/2, sy2 + map->vp->hh/2);
                                          MoveTo(sx2 + map->vp->hw/2, sy2 - rad * map->vp->hw + map->vp->hh/2);
                                          LineTo(sx2 + map->vp->hw/2, sy2 + rad * map->vp->hw + map->vp->hh/2);
                                          tmprect.left  = sx2 - rad * map->vp->hh;  tmprect.top    = sy2 - rad * map->vp->hw;
                                          tmprect.right = sx2 + rad * map->vp->hh;  tmprect.bottom = sy2 + rad * map->vp->hw;
                                          OffsetRect(&tmprect, map->vp->hw/2, map->vp->hh/2);
                                          UNWRAP_RECT(map, tmprect, 
                                                FrameOval(&tmprect));
                                    }
                                    PenNormal();
                              }
                  }
            }
      }
      /* Draw a highlighting rectangle. */
      SetRect(&tmprect, sx, sy, sx + sw, sy + sh);
       /*Make box 1 pixel less tall to avoid leakage of its lower left corner into the grid.
       The grouping box can also be 1 pixel wider without problems. This makes things
       look much better since the unit images now fit entirely within the box. */
      if (map->vp->uw > 16) {
            tmprect.bottom -= 1;
            tmprect.right += 1;     
      }
      /* First, draw an outer frame, for contrast. */
      if (map->autoselect
            /* Don't use animation pattern with blinking curunit. */
            && map->blinking_curunit != TRUE
            && unit->act
            && unit->act->initacp > 0
            && has_acp_left(unit)) {
            PenPat(&animation_patterns[animation_pattern_state]);
      } else {
            PenPat(QDPat(white));
      }
      /* Draw selected enemy units in red. */
      if (!side_controls_unit(dside, unit))
            ForeColor(redColor);
      UNWRAP_RECT(map, tmprect, 
            FrameRect(&tmprect));
      InsetRect(&tmprect, 1, 1);
      /* Black is for units that can still act, dark gray for actors, gray if the
         unit can't do anything. */
      PenPat((unit->act && unit->act->initacp > 0) ?
                  (has_acp_left(unit) ? QDPat(black) : QDPat(dkGray)) : QDPat(gray));
      /* Wide border if awake, narrow if asleep or napping. */
      size = ((unit->plan && (unit->plan->asleep || unit->plan->reserve)) ? 1 : 2);
      PenSize(size, size);
      UNWRAP_RECT(map, tmprect, 
            FrameRect(&tmprect));
      PenNormal();
      ForeColor(blackColor);
      DGprintf("draw selection of %s at %d,%d\n", unit_desig(unit));
}

/* The code now handles case with transport + occ and also third unit correctly,
   by using the transports enclosing box rather than the whole cell as selection
   box when either transport or occ is selected. The new definition of contentrect
   is also handled. The black enclosing box is now drawn here as well. */

void
draw_selection_animation(map, unit)
Map *map;
Unit *unit;
{
      int sx, sy, sw, sh, wholecell = FALSE, drawmag = FALSE;
      int sx1, sy1, sw1, sh1, size;
      Rect tmprect, tmpRect;
      GrafPtr oldport;
      RgnHandle tmprgn;

      if (!in_play(unit))
        return; /* unselect it too? */
      GetPort(&oldport);
      SetPort(map->window);
      tmprgn = NewRgn();
      GetClip(tmprgn);

      /* Clip to map content */
      tmpRect = map->contentrect;
      ClipRect(&tmpRect);

      if (map->vp->uw >= 32) {
            /* Use bigger box enclosing also occs for selection box */
            m_xform_unit(map, unit, &sx, &sy, &sw, &sh);
            if (map->numselections == 1
                  && sw < 16
                  && (unit->transport != NULL) ) {
                  m_xform_unit(map, unit->transport, &sx, &sy, &sw, &sh);
                  sx1 = sx;  sy1 = sy;  sw1 = sw;  sh1 = sh;
            }
      } else {
            xform(map, unit->x, unit->y, &sx, &sy);
            /* Adjust to unit part of cell. */
            sx += (map->vp->hw - map->vp->uw) / 2;  
            sy += (map->vp->hh - map->vp->uh) / 2;
            sw = map->vp->uw;  
            sh = map->vp->uh;
      }
      /* Draw a highlighting rectangle. */
      SetRect(&tmprect, sx, sy, sx + sw, sy + sh);
       /*Make box 1 pixel less tall to avoid leakage of its lower left corner into the grid.
       The grouping box can also be 1 pixel wider without problems. This makes things
       look much better since the unit images now fit entirely within the box. */
      if (map->vp->uw > 16) {
            tmprect.bottom -= 1;
            tmprect.right += 1;     
      }
      /* First, draw an outer white frame, for contrast. */
      if (unit->act && unit->act->initacp > 0 && has_acp_left(unit)) {
            PenPat(&animation_patterns[animation_pattern_state]);
      } else {
            PenPat(QDPat(white));
      }
      /* Draw selected enemy units in red. */
      if (!side_controls_unit(dside, unit))
            ForeColor(redColor);
      FrameRect(&tmprect);
      if (area.xwrap) {
            int rest = map->vp->pxw - tmprect.left;
            /* Draw one extra copy to the left if necessary */
            if (tmprect.right > map->vp->sxmax) {
                  OffsetRect(&tmprect, -map->vp->sxmax, 0);
                  FrameRect(&tmprect);
                  OffsetRect(&tmprect, map->vp->sxmax, 0);
            }           
            /* Draw more copies to the right if necessary*/
            while (rest > 0) {
                  OffsetRect(&tmprect, map->vp->sxmax, 0);
                  FrameRect(&tmprect);
                  rest -= map->vp->sxmax;
            } 
      }
      /* This is important or the black box will disappear for transports at low mag. */
      InsetRect(&tmprect, 1, 1);
      /* Black is for units that can still act, dark gray for actors, gray if the unit can't do anything. */
      PenPat((unit->act && unit->act->initacp > 0) ?
                  (has_acp_left(unit) ? QDPat(black) : QDPat(dkGray)) : QDPat(gray));
      /* Wide border if awake, narrow if asleep or napping. */
      size = ((unit->plan && (unit->plan->asleep || unit->plan->reserve)) ? 1 : 2);
      PenSize(size, size);
      FrameRect(&tmprect);
      if (area.xwrap) {
            int rest = map->vp->pxw - tmprect.left;
            /* Draw one extra copy to the left if necessary */
            if (tmprect.right > map->vp->sxmax) {
                  OffsetRect(&tmprect, -map->vp->sxmax, 0);
                  FrameRect(&tmprect);
                  OffsetRect(&tmprect, map->vp->sxmax, 0);
            }           
            /* Draw more copies to the right if necessary*/
            while (rest > 0) {
                  OffsetRect(&tmprect, map->vp->sxmax, 0);
                  FrameRect(&tmprect);
                  rest -= map->vp->sxmax;
            } 
      }
      PenNormal();
      PenPat(QDPat(black));
      ForeColor(blackColor);

      SetClip(tmprgn);
      DisposeRgn(tmprgn);
      SetPort(oldport);
}

/* This function is called by the kernel whenever something needs to be updated. */

void  
update_cell_display(Side *side, int x, int y, int flags)
{
      Map *map;
      
      if (!active_display(side))
        return;

      for_all_maps(map) {
            if (((flags & UPDATE_ALWAYS)
                 || ((flags & UPDATE_COVER) && map->vp->draw_cover)
                 || ((flags & UPDATE_TEMP) && map->vp->draw_temperature)
                 || ((flags & UPDATE_CLOUDS) && map->vp->draw_clouds)
                 || ((flags & UPDATE_WINDS) && map->vp->draw_winds)
                 )) {
                  update_cell(map, x, y);
            }
      }
}

/* This consolidated function now does all the offscreen drawing formerly done by 
   clear_unit_blast, erase_selection, erase_selections and update_cell_display.
   The new name-ersing algorithm is faster and also catches units that just died. */

void
update_cell(Map *map, int x, int y)
{
      int namelength = 1, cells = 1, sx, sy, i, j;          
      Rect destRect, sourceRect;
      GrafPtr     oldport;
      Unit *unit;

      /* Don't update cells outside the gworld. */ 
      /* Also prevents erase-dead-unit crashes! */
      if (!cell_is_in_gworld(map, x, y))
         return;

      /* Save the current port. */
      GetPort(&oldport);
      
      /* Find max length of a unit name (skip at 4 x 4 or below). */
      if (map->vp->draw_names && map->vp->power > 2) {
            /* This formula works for most magnifications. */
            namelength = 9 - map->vp->power;
            /* Special case for 8 x 8 magnification. */
            if (map->vp->power == 3)
                  namelength = 8;
      }

      /* Then check if an erasing row really is needed. */
      if (namelength > 1 && map->erase_names) {
            for_all_units(unit) {
                  /* Only check named units. */
                  if (!unit->name)
                        continue;
                  /* First check if the unit is located at (x, y).*/
                  if ((unit->x == wrapx(x) && unit->y == y) || 
                  /* Also check if it just left (x, y). Catches unit that died!*/
                       (unit->prevx == wrapx(x) && unit->prevy == y)) {
                        /* Make an erasing row if either is true. */
                        cells = namelength;
                        break;
                  }
            }
      }

      /* Find the pixel coordinates of (x, y). */
      xform(map, x, y, &sx, &sy);

      /* Set destRect to enclose the redrawn row of cells. */
      SetRect(&destRect, 
                  sx - 2, sy - 2,                     /* Add 2 for W & N shorelines. */   
                  sx + cells * map->vp->hw + 2, /* Add 2 more pixels. */ 
                  sy + 2 * map->vp->hh);        /* Add the entire row below. */

      GO_OFFSCREEN(map);

      /* First draw a row starting at (x, y) of length cells to
         erase the name of any unit that moved out of the cell. */
      draw_row(map, x, y, cells, TRUE);

      /* Draw grid explicitly if asked to and the quick method would leak. */
      if (map->vp->draw_grid && map->leaky_grid)
            for (i = x; i < x + cells + 1; i++)
                  draw_gridlines(map, i, y - 1);
      /* Redraw partially erased shorelines for cells below the row. */                   
      if (map->shorelines) {
            for (i = x; i < x + cells + 1; i++)
                  draw_shores(map, i, y - 1);
      }
      /* Redraw any partially erased names of units just W of (x, y). */
      /* Note: this is done even if erase_names is not on! */
      if (map->vp->draw_names 
            && map->vp->power > 2) {
            for (i = x - namelength + 1; i <  x; i++) {
                  for_all_stack(wrapx(i), y, unit) {
                        if (unit->name) {                                     
                              draw_unit_names_only(map, i, y);
                              /* Only do this once for each cell. */
                              break;
                        }
                  }
            }
      }                             
      /* Redraw any selected units within the updated row. */
      for (i = 0; i < map->numselections; ++i) {
            unit = map->selections[i];
            for (j = 0; j < cells; j++) {
                  if (unit && unit->x ==  wrapx(x + j) && unit->y == y) {
                        draw_selected_unit(map, unit);
                  }                                   
            }
      }
      /* Use gworld source for destRect. */
      sourceRect = destRect;
      /* Find its correct position in the gworld. */
      OffsetRect(&sourceRect, map->offsetx + map->bufx - map->conw, 
                                           map->offsety + map->bufy - map->toph);

      /* Adjust sourcerect to be at least partially within the gworld. */
      while (area.xwrap && sourceRect.right < 0)
            OffsetRect(&sourceRect, map->vp->sxmax, 0);
      while (area.xwrap && sourceRect.left > map->vp->sxmax)
            OffsetRect(&sourceRect, -map->vp->sxmax, 0);

      /* Redraw any partially erased feature names. */
      if (features_defined() && map->featurenames && map->vp->hh > 3)
            UNWRAP_RECT(map, sourceRect, 
                              draw_feature_names(map, sourceRect));

      /* Redraw any meridians that will fall within destRect. */ 
      if (map->vp->draw_meridians && map->vp->meridian_interval > 0) {
            /* Use red color if debugging the graphics. */
            /* (black color is always restored by draw_meridians). */
            if (DebugG)
                  ForeColor(redColor);
            UNWRAP_RECT(map, sourceRect, 
                              draw_meridians(map, sourceRect));
      }
      
      RETURN_ONSCREEN(map);

      /* Now copy redrawn rect to the window. */
      if (DebugG)
            /* Make whole gworld visible when debugging. */
            copy_from_gworld(map, map->contentrect);              
      else  copy_from_gworld(map, destRect);                

      /* Update unit info line. */
      if (map->numselections > 0) {
            unit = map->selections[0];
            if (in_play(unit) 
                 && unit->x == wrapx(x) 
                 && unit->y == y
                 /* Don't draw unit info in the middle of a blink when unit->cp 
                 is set to zero. */
                 && animation_pattern_state >= 0) {
                  draw_unit_info(map);
            }
      }
      /* Restore the port. */
      SetPort(oldport);
}

/* Called by update_cell. Redraws the names only for any named units in (x, y).
   Based on merged draw_units & draw_unit_and_occs that have been trimmed down
   Unlike draw_unit_and_occs there is no recursion since names of occs are not shown. */
            
void
draw_unit_names_only(Map *map, int x, int y)
{
      int xw = wrapx(x), sx, sy, sw, sh, sx2, sy2, sw2, sh2;
      Rect tmprect;
      Unit *unit;

      if (!map->see_all &! units_visible(dside, xw, y))
        return;
      unit = unit_at(xw, y);
      if (unit == NULL)
        return;
      if (!inside_area(x, y))
        return;
      xform(map, x, y, &sx, &sy);

      /* Draws unit names at small mags where contents of grouping boxes is not shown. */
      if (map->vp->uw <= 16 
            && g_bigicons() != TRUE) {
            /* Adjust to unit part of cell. */
            sw = map->vp->uw;  
            sh = map->vp->uh;
            sx += (map->vp->hw - sw) / 2; 
            sy += (map->vp->hh - sw) / 2;
            UNWRAP_SX(map, sx,
                  draw_unit_name(unit, sx, sy, sw, sh, 
                                     map->textmasks, map->optimize_fonts));
      /* Draw unit names at medium and high mags. */
      } else {
            for_all_stack(xw, y, unit) {
                  /* Don't draw names of occupants. */
                  if (!unit->transport) {
                        m_xform_unit(map, unit, &sx, &sy, &sw, &sh);
                        UNWRAP_SX(map, sx, 
                              draw_unit_name(unit, sx, sy, sw, sh, 
                                                 map->textmasks, map->optimize_fonts));
                  }
            }
      }
}


Generated by  Doxygen 1.6.0   Back to index